如何全局设置MergeOption = OverwriteChanges?

时间:2014-11-12 22:36:21

标签: c# entity-framework caching orm dbcontext

我们的WinForms应用程序有各种对话框和&屏幕用自己的DbContext打开。我们通常希望屏幕重新加载其数据或部分数据,这些数据是使用不同的DbContext在不同的表单上修改的,只需重新执行对数据库的查询即可。默认情况下,MergeOption = PreserveChanges因此数据库更改不会反映在DbContext中。

我们考虑的选项:

为相关对象调用Refresh(RefreshMode.StoreWins)

ObjectContext.Refresh(RefreshMode.StoreWins, models)

这是丰富的复杂模型结构和集合,因为它必须在每个模型上单独调用。

在每个查询中单独设置MergeOption

((ObjectQuery)query).MergeOption = MergeOption.OverwriteChanges

这可以工作,但必须手动完成每个容易忘记的查询。

各种手动破解反射

有几个SO页面暗示这样的黑客攻击,然而EF7即将出现,并且我不想走这条道路的风险。

问题:如何为DbContext中的所有内容设置MergeOption = OverwriteChanges?或者甚至更好,全球?

2 个答案:

答案 0 :(得分:0)

OverwriteChanges上的MSDN:

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实例执行相同操作,则需要对每个实例执行相同操作。

编辑:就你执行的每个查询设置默认的MergeOption,据我所知,thias是不可能的。似乎使用ObjectContext上的反射来获取所有ObjectQueries的旧技巧不适用于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()。

答案 1 :(得分:0)

我已经提出了一个基于T4的解决方案来解决MergeOption无法访问DbSet周围的EF疏忽问题

在默认的DataContext中,你有一些由T4模板生成的实体DBS集:

public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<Address> Addresses { get; set; }
etc.

修改模板,为每个IQueryable添加Entity个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以确保您拥有最新的实体。