ObjectContext.SavingChanges处理中的Entity Framework 4.1错误(?)

时间:2011-05-04 11:28:28

标签: entity-framework event-handling

我遇到的问题似乎是Entity Framework 4.1中的一个错误:我在ObjectContext.SavingChanges上添加了一个处理程序,只要在数据库中添加或修改了对象,就会更新属性“LastModified”。然后我做以下事情:

  1. 向数据库添加两个对象,然后提交(call SaveChanges()
  2. 修改已添加的第一个对象
  3. 提取LastModified排序的两个对象
  4. 生成的对象以错误的顺序返回。查看对象,我可以看到LastModified属性已更新。换句话说,SavingChanges事件已正确触发。但是查看数据库,LastModified列尚未更改。也就是说,EF的缓存对象与数据库中的行之间存在差异。

    我尝试在重写的“SaveChanges”方法中对LastModified执行相同的更新:

    public override int SaveChanges()
    {
      SaveChangesHandler();//updating LastModified property on all objects
      return base.SaveChanges();
    }
    

    这样做会导致数据库正确更新,并且查询以正确的顺序返回对象。

    这是一个显示错误的整个测试程序:

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    
    namespace TestApplication
    {
      class Program
      {
        private PersistenceContext context;
    
        private static void Main(string[] args)
        {
          var program = new Program();
          program.Test();
        }
    
        public void Test()
        {
          SetUpDatabase();
          var order1 = new Order {Name = "Order1"};
          context.Orders.Add(order1);
          var order2 = new Order {Name = "Order2"};
          context.Orders.Add(order2);
          context.SaveChanges();
    
          Thread.Sleep(1000);
          order1 = GetOrder(order1.Id); // Modified 1.
          order1.Name = "modified order1";
          context.SaveChanges();
    
          List<Order> orders = GetOldestOrders(1);
          AssertEquals(orders.First().Id, order2.Id);//works fine - this was the oldest object from the beginning
    
    
          Thread.Sleep(1000);
          order2 = GetOrder(order2.Id); // Modified 2.
          order2.Name = "modified order2";
          context.SaveChanges();
    
          orders = GetOldestOrders(1);
          AssertEquals(orders.First().Id, order1.Id);//FAILS - proves that the database is not updated with timestamps
        }
    
        private void AssertEquals(long id1, long id2)
        {
          if (id1 != id2) throw new Exception(id1 + " != " + id2);
        }
    
        private Order GetOrder(long id)
        {
          return context.Orders.Find(id);
        }
    
        public List<Order> GetOldestOrders(int max)
        {
          return context.Orders.OrderBy(order => order.LastModified).Take(max).ToList();
        }
    
        public void SetUpDatabase()
        {
          //Strategy for always recreating the DB every time the app is run.
          var dropCreateDatabaseAlways = new DropCreateDatabaseAlways<PersistenceContext>();
    
          context = new PersistenceContext();
    
          dropCreateDatabaseAlways.InitializeDatabase(context);
        }
      }
    
    
      ////////////////////////////////////////////////
      public class Order
      {
        public virtual long Id { get; set; }
        public virtual DateTimeOffset LastModified { get; set; }
        public virtual string Name { get; set; }
      }
    
      ////////////////////////////////////////////////
      public class PersistenceContext : DbContext
      {
        public DbSet<Order> Orders { get; set; }
    
        public PersistenceContext()
        {
          Init();
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    
        public void Init()
        {
          ((IObjectContextAdapter) this).ObjectContext.SavingChanges += SavingChangesHandler;
          Configuration.LazyLoadingEnabled = true;
        }
    
        private void SavingChangesHandler(object sender, EventArgs e)
        {
          DateTimeOffset now = DateTimeOffset.Now;
    
          foreach (DbEntityEntry entry in ChangeTracker.Entries()
            .Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
          {
            SetModifiedDate(now, entry);
          }
        }
    
        private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
        {
          if (modifiedEntity.Entity == null)
          {
            return;
          }
    
          PropertyInfo propertyInfo = modifiedEntity.Entity.GetType().GetProperty("LastModified");
    
          if (propertyInfo != null)
          {
            propertyInfo.SetValue(modifiedEntity.Entity, now, null);
          }
        }
      }
    }
    

    我应该补充一点,在我们升级到EF4.1并使用Code-First之前,SavingChanges处理程序工作正常(也就是说,它在EF4.0中使用模型优先)

    问题是:我在这里发现了一个错误,或者我做错了什么?

1 个答案:

答案 0 :(得分:2)

我不确定这是否可以被视为Bug。似乎发生的情况是,您操纵LastModified属性的方式不会触发INotifyPropertyChanged,因此更改不会填充到您的数据库。

证明它的使用:

    order2.Name = "modified order2";
    ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(order2).SetModifiedProperty("LastModified");

要在您的SavingChangesHandler中使用这些知识:

private void SavingChangesHandler(object sender, EventArgs e)
{
  DateTimeOffset now = DateTimeOffset.Now;

  foreach (DbEntityEntry entry in ChangeTracker.Entries()
    .Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
  {
    SetModifiedDate(now, entry);
    if (entry.State == EntityState.Modified)
    {
      ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity).SetModifiedProperty("LastModified");
    }
  }
}

修改

我更多地研究了这一点你是对的。出于某种原因,MS决定在使用PropertyInfo.SetValue时不再触发PropertyChanged事件。只有一种方法可以确定这是错误还是设计决策:提交错误报告/发布到msdn论坛。

虽然直接通过CurrentValue更改属性似乎工作正常:

private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
  if (modifiedEntity.Entity == null)
  {
    return;
  }

  modifiedEntity.Property("LastModified").CurrentValue = now;
}