我在数据库中有一行来计算用户总登录次数 我试图通过获取行并为其添加+1来增加数量 在我尝试这个之后,我不确定并发性,计数器增加了1而不是2,因为它“应该”(如果许多用户将同时登录)
using(var db = new Database()) {
db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
using(var db2 = new Database()) {
db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
db2.SaveChanges();
}
db.SaveChanges();
}
答案 0 :(得分:0)
为什么不创建单个表来存储已登录的人数,当有人成功登录时会增加该字段,并在用户注销时减少。例如登录:
_Users = context.Users.First(aa => aa.UserName.ToUpper() == _UserName.ToUpper() && aa.MDesktop == true);
if (_Users != null)
{
context.LogEntry.FirstOrDefault().Counter++;
context.SaveChanges();
}
答案 1 :(得分:0)
这是旧的,但对于新的 EF 开发人员来说仍然是一个相关的讨论,值得解释。
OP 的示例使用了两个不同的 DBContext,实际上 OP 定义了两个不同的工作单元,重要的是,它们都不知道另一个存在。
让我们假设 "Logins"
设置的 current 值为 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.
}
实体框架、工作单元和存储库数据访问模式都表现出这种行为,当您创建一个新 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 从数据库读取值,它们可能已经< em>陈旧 或过时。如果乐观并发不适合您的用例,那么您需要跳出框框思考;)
答案 2 :(得分:0)
简单的方法?
那么我建议在 EF Core 中使用手动事务
ef core transaction docs
一定要添加某种 eb 的唯一约束。 (设置 ID + 登录计数器)
using(var transaction = _context.Database.BeginTransaction())
{
try
{
var totalLoginsCounter = _context.Settings.FirstOrDefault(x => x.Name == "Logins").Counter;
totalLoginsCounter += 1;
await _context.SaveChanges();
transaction.Commit();
}
catch
{
commit.RollBack();
}
}
如果并发发生,请求将失败。因为它会尝试放置重复的键,这是不可能的。那么强烈建议您然后实施重试模式,以避免人们无法实际登录,因为您的数据库中的数字未更新。