假设我MyContext
派生DbContext
DbSet<Item> Items
,其中Item
的定义如下。
public class Item
{
[Key]
public string Key { get; set; }
[ConcurrencyCheck]
public int Value { get; set; }
}
给定一个键,我想原子地递增相应的值,其中表中尚未存在的键的隐含值为0.也就是说,原子地递增表中不存在的键会导致值为1这是我目前的做法:
public static async Task IncrementAsync(string key)
{
using (var context = new MyContext())
{
while (true)
{
var item = await context.Items.FindAsync(key);
if (item == null)
{
context.Items.Add(new Item { Key = key, Value = 1 });
}
else
{
item.Value++;
}
try
{
await context.SaveChangesAsync();
break;
}
catch (DbUpdateException)
{
continue;
}
}
}
}
当许多对IncrementAsync
的调用同时运行时,这会因为实时锁定而失败。
while
循环是否应该在using
之外,以便每次尝试都获得新的上下文?我尝试了这一点,它使一切正常,但我觉得我创造和破坏如此多的背景效率低下。我的实体框架体验基本上只是查询,所以如果你能解释一下我在这段代码中做错的细节,我真的很感激。
修改
因为所选答案没有明确说明,所以我会在这里放置更正后的代码。请注意context
之后永远不会重复使用DbUpdateException
。
public static async Task IncrementAsync(string key)
{
while (true)
{
using (var context = new MyContext())
{
var item = await context.Items.FindAsync(key);
if (item == null)
{
context.Items.Add(new Item { Key = key, Value = 1 });
}
else
{
item.Value++;
}
try
{
await context.SaveChangesAsync();
break;
}
catch (DbUpdateException)
{
continue;
}
}
}
}
答案 0 :(得分:2)
给定答案的替代方法是使用存储过程完成所有工作,如下例所示。然后,您可以在一行中从应用程序中调用它,而不是上面的代码。
CREATE PROCEDURE SP_IncrementValue
@ValueKey NVARCHAR(100)
AS
BEGIN
BEGIN TRAN
UPDATE Item WITH (SERIALIZABLE) SET Value = Value + 1
WHERE [Key] = @ValueKey
IF @@ROWCOUNT = 0
BEGIN
INSERT Item ([Key], Value) VALUES (@ValueKey, 1)
END
COMMIT TRAN
END
GO
这种方法可以提供更好的性能,减少错误。
修改强> 要从C#调用存储过程,请在EF ApplicationDbContext类
中添加以下方法 public int IncrementValue(string valueKey)
{
return this.Database.ExecuteSqlCommand("EXEC SP_IncrementValue @ValueKey", new SqlParameter("@ValueKey", valueKey));
}
然后,您可以从DBContext类的任何实例中调用它
答案 1 :(得分:1)
您需要在尝试之间不共享上下文。如果没有对具有DbUpdateException
的上下文做任何事情,就像你没有明确地清理上下文一样,它可能永远不会返回到正常状态。
我希望外部环境会引发问题。如果根据时间发生对单个键的并发调用,则可能会创建一个错误的上下文设置(由于您的错误处理程序,它会不断被忽略。
除非我弄错了,数据库中存在Key
的事实不会删除“待添加”版本。您将最终得到以下其中一种情况:
Add "1", 2
或
Add "1", 1
Update "1", 2
取决于您的第二次迭代是否抓取第一个迭代的对象或新的对象。
这些都不会成功,所以最终会出现连续错误。