使用并发检查多次调用SaveChanges时的EntityFramework 4.3问题

时间:2012-03-06 13:22:47

标签: entity-framework-4.3

- 更改了问题的范围,因为这段代码更容易解​​释,但它与让我编写问题的第一个版本的行为相同......

在多次调用SaveChanges()时,EF 4.3.1并发检查和自动缓存有些奇怪。

当使用EF 4.3(带有硬编码列类型的bug和所有)时,同一段代码工作正常(可能是因为没有并发检查)。使用4.3.1时,第二个SaveChanges()调用会崩溃。

以下是课程:

public class Concurrent
{
   public byte[] Version { get; set; }
}

public class Operation : Concurrent
{
   public virtual int CustomIdName { get; set; }
   public virtual C1 C1 { get; set; }
   public virtual C2 C2 { get; set; }
   // other properties
}

public class C1 : Concurrent
{
   public virtual string CustomIdName { get; set; }
   public virtual ICollection<C2> C2 { get; set; }
   // other properties
}

public class C2 : Concurrent
{
   public virtual string CustomIdName { get; set; }
   // other properties...
}

public class Repository : DbContext, IMyCustomGenericRepository
{
   public void Save() { SaveChanges(); }
   public T Find<T>(object id) { return Set<T>().Find(id); }
   // other methods from the interface and all...

   public Repository Recreate() { return new Repository(); }
}

一些编码......

public void TestMethod(IMyCustomGenericRepository Repository)
{
   var operation = Repository.Find<Operation>(1); // ok
   operation.C1 = Repository.Find<C1>("1"); // ok
   operation.C2 = Repository.Find<C2>("1"); // ok
   Repository.Save(); // ok

   operation = Repository.Find<Operation>(1); // ok
   operation.C1 = Repository.Find<C1>("1"); // ok
   operation.C2 = Repository.Find<C2>("2"); // ok
   Repository.Save(); // fails
}

public void OtherTestMethod(IMyCustomGenericRepository Repository)
{
   var operation = Repository.Find<Operation>(1); // ok
   operation.C1 = Repository.Find<C1>("1"); // ok
   operation.C2 = Repository.Find<C2>("1"); // ok
   Repository.Save(); // ok

   Repository = Repository.Recreate();

   operation = Repository.Find<Operation>(1); // ok
   operation.C1 = Repository.Find<C1>("1"); // ok
   operation.C2 = Repository.Find<C2>("2"); // ok
   Repository.Save(); // ok
}

My Fluent API如下所示:

modelBuilder.Entity<Operation>().HasKey(_o => _o.CustomIdName);
modelBuilder.Entity<Operation>().Property(_o => _o.Version).HasColumnType("timestamp").IsConcurrencyToken();

modelBuilder.Entity<C1>().HasKey(_c1 => _c1.CustomIdName);
modelBuilder.Entity<C1>().Property(_c1 => _c1.Version).HasColumnType("timestamp").IsConcurrencyToken();

modelBuilder.Entity<C2>().HasKey(_c2 => _c2.CustomIdName);
modelBuilder.Entity<C2>().Property(_c2 => _c2.Version).HasColumnType("timestamp").IsConcurrencyToken();

从第一种方法到第二种方法的唯一区别是调用Repository.Recreate()方法,该方法返回一个全新的存储库。

更新2

在Context类中,我将SaveChanges()方法重写为:

public override int SaveChanges()
{
   // saves pending changes
   var _returnValue = base.SaveChanges();


   // updates EF local entities (cache)
   foreach (var item in Set<Operation>().Local)
      (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);
   foreach (var item in Set<C1>().Local)
      (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);
   foreach (var item in Set<C2>().Local)
      (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);


   // returns the saving result
   return _returnValue;
}

这段代码“解决”了这个问题所以 IS 正如我所想:SQL服务器自动更改TimeStamp值(并发令牌)但EF不更新由于某种原因,我仍然不知道该属性版本的值。

问题是,如果版本错误,我无法再次保存,因为我违反了EF的并发检查。

刷新本地实体(EF自动缓存的实体)可防止此问题 BUT 产生重大副作用:我必须手动刷新每个更改的条目。这根本不好笑,因为:

  1. 检测本地实体(演员和其他事物)
  2. 迭代到每个本地实体并刷新其状态
  3. 如果忘记这件事,问题会再次发生。
  4. 为什么EF不会像PK列那样自动更新版本colunm(设置为IsConcurrencyToken())(如果PK是标识列,则在插入后更新)?

    更新3(可能的解决方案?)

    从DBContext中覆盖SaveChanges()方法并放置此代码似乎使一切正常工作:

    public override int SaveChanges()
    {
       var entities = ChangeTracker.Entries().Where(_entry => _entry.State != System.Data.Entity.EntityState.Detached && _entry.State != System.Data.Entity.EntityState.Unchanged && _entry.State != System.Data.Entity.EntityState.Deleted).Select(_entry => _entry.Entity).ToArray();
    
       if (!Configuration.AutoDetectChangesEnabled)
          ChangeTracker.DetectChanges();
    
       var result = base.SaveChanges();
    
       foreach (var entity in entities)
          (this as IObjectContextAdapter).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.StoreWins, entity);
    
       return result;
    }
    

    这不是一个完美的解决方案。 但它现在是自动的。 应该做一段时间的技巧,直到出现更好的答案。

2 个答案:

答案 0 :(得分:0)

我也有类似的问题与EF 4.3.1。 刚刚进行了升级,它基于4.1打破了我的工作代码。

我无法添加似乎有关系的新实体。例如与Country对象关系的新场所对象。 我的所有实体都有一个Concurrency属性:

  

this.Property(t =&gt;   t.lastChangedDate).HasColumnName( “lastChangedDate”)IsConcurrencyToken()HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

我在CRUD之前自我更新此属性,例如

virtual public void Save(T target)
{
    target.ID = GuidUtils.GenerateComb();
    target.creationDate = DateTime.Now;
    target.lastChangedDate = DateTime.Now;

    sessionManager.Set<T>().Add(target);
    sessionManager.SaveChanges();
}

当我保存新的地方物品时,我得到:

  

存储更新,插入或删除语句会影响意外   行数(0)。自那以后,实体可能已被修改或删除   实体已加载。刷新ObjectStateManager条目。

嗯...我提到这是工作代码吗?

如果我删除国家地图中的并发部分,它可以工作...... 对于记录,用于关联场所对象的国家在存储场所对象之前正确地存储在数据库中。我可以看到它的PK和并发属性。

因此它似乎是4.3.1中的引用并发问题。

答案 1 :(得分:0)

好的,这里有一个小小的更新。

  1. 说它在以前的版本中有效是不对的。我们使用4.1与PostgreSQL数据库和Devart连接器...这有用。奇怪,我不明白。

  2. LordALMMa的解决方法也适用于我。但我不想使用解决方法....

  3. @LordALMMa你找到了解决方案吗?

    更新7-4-12

    我改变了我的并发代码。首先我使用DateTime属性:

    this.Property(t => t.lastChangedDate).IsConcurrencyToken().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
    

    在avery更新或插入时,我手动将此属性更新为DateTime.Now。 这与PostgreSQL和devart连接器一起工作正常。迁移到MS SQL后我遇到了问题。我现在改为:

    this.Property(t => t.timestamp).HasColumnName("timestamp").IsRowVersion(); 
    

    这个有效!奇怪....因为我没有做违法的事情.. MS数据库中的此列的类型为rowversion。

    因此,某个非数据库生成的DateTime类型的时间戳在MS SQL中无法使用关键字IsConcurrencyTokenHasDatabaseGeneratedOption(DatabaseGeneratedOption.None)