如何全局设置合并选项 = 覆盖更改

本文关键字:覆盖 选项 全局设置 合并 | 更新日期: 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 监督MergeOptionDbSet中无法访问

在默认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以确保您拥有最新的实体。