【发布时间】: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 自动缓存的实体)可以防止这个问题但是有一个主要的副作用:我必须手动刷新每个更改的条目。这一点都不好笑,因为:
- 检测本地实体(演员表和其他东西)
- 迭代每个本地实体并刷新其状态
- 如果忘记这一点,问题将再次发生。
为什么 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,但它们都还处于计划状态,因为我们还有一些其他优先事项需要处理。但是,真的,谢谢你的提示。