2

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

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

当使用 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;
}

这段代码“解决”了这个问题,所以它就像我想的那样:SQL server 自动更改 TimeStamp 值(并发令牌),但 EF 出于某种我仍然不知道的原因没有更新属性 Version 上的该值。

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

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

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

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

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

4

2 回答 2

0

我对 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 中的引用并发问题。

于 2012-04-06T14:44:42.853 回答
0

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

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

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

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

2012 年 7 月 4 日更新

我更改了并发代码。首先,我使用的是 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)

.

于 2012-04-06T15:16:09.897 回答