-- 改变了问题的范围,因为这另一段代码更容易解释,但它的行为与让我编写问题的第一个版本的行为相同......
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 自动缓存的实体)可以防止这个问题,但有一个主要的副作用:我必须手动刷新每个更改的条目。这一点都不好笑,因为:
- 检测本地实体(演员表和其他东西)
- 迭代每个本地实体并刷新它的状态
- 如果要忘记这一点,那么问题将再次发生。
为什么 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;
}
这不是一个完美的解决方案。但它现在是自动的。应该做一段时间,直到出现更好的答案。