如何全局设置合并选项 = 覆盖更改
本文关键字:覆盖 选项 全局设置 合并 | 更新日期: 2023-09-27 18:30:20
我们的WinForms应用程序有各种对话框和屏幕,每个对话框和屏幕都有自己的DbContext。通常,我们希望屏幕重新加载其数据或部分数据,这些数据是在具有不同 DbContext 的不同表单上修改的,只需通过重新执行针对数据库的查询即可。缺省情况下,MergeOption = PreserveChanges 因此,数据库更改不会反映在 DbContext 中。
我们考虑过的选项:
调用 Refresh(RefreshMode.StoreWins) for Related Object
ObjectContext.Refresh(RefreshMode.StoreWins, models)
这对于丰富复杂的模型结构和集合来说是笨拙的,因为它必须在每个模型上单独调用。
在每个查询上单独设置合并选项
((ObjectQuery)query).MergeOption = MergeOption.OverwriteChanges
这有效,但必须手动完成每个容易忘记的查询。
通过反射进行各种手动破解
几个 SO 页面建议这样的黑客,但是随着 EF7 的出现和一般风险,我不想走这条路。
问:如何为 DbContext 中的所有内容设置 MergeOption=OverwriteChanges?甚至更好,在全球范围内?
MSDN on OverwriteChanges:
All current values on the client are overwritten with current values from the data service regardless of whether they have been changed on the client.
如果要重新加载所有内容,请删除上下文并再次重新加载数据。这是重新加载所有数据最安全、最快捷的方法。
如果要对应用程序中的所有 DbContext 实例执行相同的操作,则需要对每个实例执行相同的操作。
编辑:就为您执行的每个查询设置默认的合并选项而言,据我所知,这是不可能的。似乎在 ObjectContext 上使用反射来获取所有 ObjectQuery 的老技巧不适用于 DbContext。一种可能的选择是改进您正在使用的一些代码,并将其包装到始终应用 OverwriteChanges 的方法中,如下所示:
protected ObjectSet<T> GetObjectSet<T>() where T : class
{
var objectContext = ((IObjectContextAdapter)Context).ObjectContext;
ObjectSet<T> set = objectContext.CreateObjectSet<T>();
set.MergeOption = MergeOption.OverwriteChanges;
return set;
}
并调用此方法而不是 Context.Set()。
我想出了一个基于 T4 的解决方案来解决 EF 监督MergeOption
在DbSet
中无法访问
在默认DataContext
中,您有一些由 T4 模板生成的实体 DBSets:
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<Address> Addresses { get; set; }
etc.
编辑模板以为每个Entity
添加IQueryable
getter:
public IQueryable<Person> GetPersons(MergeOption mergeOption = MergeOption.AppendOnly, bool useQueryImplentation = true)
{
return useQueryImplementation ? GetSet<Person>(mergeOption).QueryImplementation() : GetSet<Person>(mergeOption);
}
然后在基础DataContext
public class DataContextBase
{
/// <summary>
/// Gets or sets the forced MergeOption. When this is set all queries
/// generated using GetObjectSet will use this value
/// </summary>
public MergeOption? MergeOption { get; set; }
/// <summary>
/// Gets an ObjectSet of type T optionally providing a MergeOption.
/// <remarks>Warning: if a DataContext.MergeOption is specified it will take precedence over the passed value</remarks>
/// </summary>
/// <typeparam name="TEntity">ObjectSet entity Type</typeparam>
/// <param name="mergeOption">The MergeOption for the query (overriden by DataContext.MergeOption)</param>
protected IQueryable<TEntity> GetObjectSet<TEntity>(MergeOption? mergeOption = null) where TEntity : class
{
var set = Context.CreateObjectSet<TEntity>();
set.MergeOption = MergeOption ?? mergeOption ?? MergeOption.AppendOnly;
return set;
}
通过为IQueryable
创建默认的 Extension 方法,如下所示,您可以选择为每个表/类型添加自己的QueryImplementation<T>
实现,以便表的所有用户都可以排序或包含等。(不需要回答问题,但它很有用)
因此,例如,您可以添加以下内容以在呼叫时始终包含地址GetPersons()
public static class CustomQueryImplentations
{
public static IQueryable<Person> QueryImplementation(this IQueryable<Person> source)
{
return source
.Include(r => r.Addresses)
.OrderByDescending(c => c.Name);
}
}
用法
加载一个没有跟踪的简单列表(快!
var people = Database.GetPersons(MergeOption.NoTracking);
编辑Person
,附加并使用最新的数据库值进行更新(慢)
var peson = Database.GetPersons(MergeOption.OverwriteChanges).FirstOrDefault(p => p.PersonID = 1);
在另一台计算机上:
var people = Database.GetPersons(MergeOption.OverwriteChanges);
或者,回答您的原始问题以全局设置MergeOption
Database.MergeOption = MergeOption.OverwriteChanges;
使用现有 get 方法获取实体...
Database.MergeOption = null;
注意:需要注意的是,如果您在进行更改之前加载MergeOption.AsNoTracking
则需要附加或更好地重新加载MergeOption.OverwriteChanges
以确保您拥有最新的实体。