【问题标题】:EntityFramework 4.3 issue when calling SaveChanges multiple times with Concurrency Check使用并发检查多次调用 SaveChanges 时出现 EntityFramework 4.3 问题
【发布时间】:2012-03-06 13:22:47
【问题描述】:

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

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

当使用 EF 4.3(具有硬编码的列类型错误和所有)时,相同的代码可以正常工作(可能是因为没有并发检查)。使用 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
}

我的 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 服务器自动更改时间戳值(并发令牌)但 EF 不更新出于某种原因,我仍然不知道属性版本上的那个值。

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

刷新本地实体(由 EF 自动缓存的实体)可以防止这个问题但是有一个主要的副作用:我必须手动刷新每个更改的条目。这一点都不好笑,因为:

  1. 检测本地实体(演员表和其他东西)
  2. 迭代每个本地实体并刷新其状态
  3. 如果忘记这一点,问题将再次发生。

为什么 EF 不会像 PK 列那样自动更新版本列(设置为 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;
}

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

【问题讨论】:

  • 你能展示这个问题的实体和代码吗?我想测试一下。
  • @Slauma 刚刚添加了一些代码。看起来 EF 的缓存与并发检查相混淆。这是我迄今为止所做的测试中唯一能想到的......
  • 我在简单的数据读取中遇到了一个非常相似的问题,其中一个页面将属性设置为实体,而下一页显示的网格直到 7 分钟左右才一致地反映更改。网格允许您单击重新加载,它会发布 AJAX 帖子以获取新数据,但该属性会在前 7 到 10 分钟内随机切换新/旧值。
  • 我有同样的问题@diegohb,但这个问题是关于一个稍微不同的问题。您描述的问题已通过在网格页面上使用 .AsNoTracking() 得到修复。 stackoverflow.com/questions/10202733/…blog.staticvoid.co.nz/2012/04/…
  • 我正准备试一试。还要用它的新项目测试新的 EF 5,但它们都还处于计划状态,因为我们还有一些其他优先事项需要处理。但是,真的,谢谢你的提示。

标签: entity-framework-4.3


【解决方案1】:

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

我无法添加看起来有关系的新实体。例如与 Country 对象相关的新地点对象。 我所有的实体都有一个并发属性:

this.Property(t => 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 中的引用并发问题。

【讨论】:

    【解决方案2】:

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

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

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

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

    7-4-12 更新

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

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

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

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

    这行得通!奇怪..因为我没有做违法的事情.. MS 数据库中的该列是 rowversion 类型。

    因此,不知何故,使用关键字 IsConcurrencyTokenHasDatabaseGeneratedOption(DatabaseGeneratedOption.None),在 MS SQL 中,DateTime 类型的非数据库生成时间戳无法正常工作

    .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-04
      • 1970-01-01
      • 2019-04-12
      • 1970-01-01
      • 2011-02-26
      • 2017-02-06
      • 2014-11-14
      • 1970-01-01
      相关资源
      最近更新 更多