这是旧的,但它仍然是新 EF 开发人员的相关讨论,值得解释。
OP 的示例使用了两个不同的 DBContext,实际上 OP 定义了两个不同的工作单元,重要的是,它们都不知道另一个存在。
假设"Logins" 设置的当前值为5
出于本演练的目的,我们将从Settings 请求的两个实例保存到相关数据库上下文范围之外的变量中:
Setting setting1 = null;
Setting setting2 = null;
using(var db = new Database()) {
// DB: 5, Setting1: null, Setting2: null
// Load the value of setting1 from the database
setting1 = db.Settings.FirstOrDefault(x => x.Name == "Logins");
// DB: 5, Setting1: 5, Setting2: null
// Increment the value of setting1
setting1.Counter++;
// at this point, no changes have been saved yet, the DB still holds the original value for "Logins"
// DB: 5, Setting1: 6, Setting2: null
// Create a new context called DB2
using(var db2 = new Database()) {
// load setting2 from the DB
setting2 = db2.Settings.FirstOrDefault(x => x.Name == "Logins");
// right now setting2 still has a value of 5, the previous change was not yet committed
// DB: 5, Setting1: 6, Setting2: 5
setting2.Counter++;
// DB: 5, Setting1: 6, Setting2: 6
// Save the value of Setting2 back to the database
db2.SaveChanges();
// DB: 6, Setting1: 6, Setting2: 6
// At this point setting1, setting2, and the DB all agree the value is 6.
}
// The context is only aware that we previously set the value of setting1 to 6
// so it issues an update to the DB
db.SaveChanges();
// ultimately this update would not actually change anything.
}
Entity Framework、Unit of Work 和 Repository 数据访问模式都表现出这种行为,当您创建 new em> DbContext IRepository 或 IUnitOfWork 这样做是为了隔离可能在同一时间点存在的任何其他上下文,在同一方法或不同线程或不同线程中实例化新上下文之间没有区别甚至在完全不同的服务器上执行。如果您需要实现计数器或增量值,当我们首先缓存字段的值,然后递增值,然后将该值写回数据库时,总是存在一定程度的不确定性。
为了最大程度地减少潜在冲突,请读取记录并在之后立即保存,然后通常在使用此设置之前重新查询它的值。
您可以在逻辑中多次调用.SaveChanges(),在此示例中,只需在实例化第二个上下文之前保存,或者至少在第二个上下文加载之前从数据库中加载记录就足以看到值增加了两次:
using(var db = new Database()) {
db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db.SaveChanges(); // save it back as soon as we've made the change
using(var db2 = new Database()) {
db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db2.SaveChanges();
}
db.SaveChanges();
}
在可能的情况下,如果您可以避免需要递增或计数器字段的架构,您会发现代码更简单,而是可以将计数逻辑转换为基于查询的解决方案。
计数器当然是一种特殊情况,您始终可以直接对数据库进行 SQL 调用,无论是读取还是递增,以确保我们绕过任何可能通过 EF 记录的潜在缓存。
您可以将其作为单行来增加值:
dbContext.Database.ExecuteSqlCommand("UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1 WHERE[Name] = 'Logins'");
或者如果你想检查新值:
int newCount = dbContext.Database.SqlQuery<int>(@"
UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1
OUTPUT inserted.[Counter]
WHERE [Name] = 'Logins'").First();
如果您需要获取当前值,并且知道它是最新的,那么您可以以相同的方式从任何上下文中简单地查询它:
int logins = dbContext.Database.SqlQuery<int>(@"
SELECT [Counter] FROM Setting
WHERE [Name] = 'Logins'").First();
我希望这能阐明为什么你的代码只增加了一次值,这不是 EF 的错误,只是我们需要注意的一点,一旦 EF 从数据库中读取值,它们可能已经 陈旧或过时。如果乐观并发不适合您的用例,那么您需要跳出框框思考一下;)