gpt4 book ai didi

c# - EF Core DbUpdateConcurrencyException 未按预期工作

转载 作者:行者123 更新时间:2023-11-30 14:46:53 26 4
gpt4 key购买 nike

我有以下代码,我正在尝试使用 ef core 更新 ClientAccount,但它们并发检查失败:

    public class ClientAccount
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }

[Required]
[ConcurrencyCheck]
public double Balance { get; set; }

[Required]
public DateTime DateTimeCreated { get; set; }

[Required]
public DateTime DateTimeUpdated { get; set; }
}

public class ClientRepository
{
private readonly MyContext context;

public ClientRepository(MyContext context)
{
this.context = context;
}

public ClientAccount GetClientAccount()
{
return (from client in context.ClientAccount
select client).SingleOrDefault();
}

public void Update(ClientAccount client)
{
context.Update(client);
context.Entry(client).Property(x => x.DateTimeCreated).IsModified = false;
}
}

public class ClientService
{
private readonly ClientRepository clientRepository;
private readonly IUnitOfWork unitOfWork;

public ClientService(ClientRepository clientRepository,
IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
this.clientRepository = clientRepository;
}

public void Update(ClientAccount clientAccount)
{
if (clientAccount == null)
return;

try
{
ClientAccount existingClient = clientRepository.GetClientAccount();
if (existingClient == null)
{
// COde to create client
}
else
{
existingClient.AvailableFunds = clientAccount.Balance;
existingClient.DateTimeUpdated = DateTime.UtcNow;

clientRepository.Update(existingClient);
}

unitOfWork.Commit();
}
catch (DbUpdateConcurrencyException ex)
{

}
}
}

问题是当两个线程试图同时更新它时,DbUpdateConcurrencyException 不会被触发,因此我没有预期的功能。我不明白这里有什么问题,因为用 ConcurrencyCheck 属性标记属性应该可以解决问题。

最佳答案

does not work as expected

当然会,但您的代码几乎不会引起并发异常。

Update 方法中,现有客户端从数据库中提取、修改并立即保存。当刚从数据库中获取时,客户端(显然)具有最新的 Balance 值,而不是它进入 UI 时的值。整个操作是毫秒级的问题,其他用户在这么短的时间内保存同一个客户端的可能性很小。

如何修复

如果您希望出现并发冲突,您应该将原始值存储在 ClientAccount 对象中,并在上下文中将其分配给原始值。例如像这样:

类(class):

public class ClientAccount
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }

[Required]
[ConcurrencyCheck]
public double Balance { get; set; }

[NotMapped]
public double OriginalBalance { get; set; }

...
}

在更新方法中,为简洁起见,假设我们在那里有可用的上下文:

ClientAccount existingClient = db.ClientAccount.Find(clientAccount.Id);

db.Entry(existingClient).OriginalValues["Balance"] = clientAccount.OriginalBalance;

existingClient.Balance = clientAccount.Balance; // assuming that AvailableFunds is a typo
db.SaveChanges();

您还需要在用户编辑的对象中设置OriginalBalance。由于您使用存储库,因此您必须添加一个方法,将原始值提供给包装的上下文。

更好的方法?

现在所有这一切都只针对一处属性(property)。更常见的是使用一种特殊属性来进行乐观并发控制,一种“版本”属性——或数据库中的字段。某些数据库(其中包括 Sql Server)会在每次更新时自动增加此版本字段,这意味着当记录的任何值被更新时,它总是不同的。

所以让你的类拥有这个属性:

public byte[] Rowversion { get; set; }

和映射:

modelBuilder.Entity<ClientAccount>().Property(c => c.Rowversion).IsRowVersion();

(或使用 [System.ComponentModel.DataAnnotations.Timestamp] 属性)。

现在您无需存储原始余额并在以后使用它,您可以简单地执行...

db.Entry(existingClient).OriginalValues["Rowversion"] = clientAccount.Rowversion;

...并且用户将了解任何并发冲突。

您可以在 EF-core 中阅读有关并发控制的更多信息 here ,但请注意(令人惊讶的是)他们错误地使用了 IsConcurrencyToken() 而不是 IsRowVersion。正如我描述的那样,这会导致不同的行为 here对于 EF6,但它仍然适用于 EF-core。

示例代码

using (var db = new MyContext(connectionString))
{
var editedClientAccount = db.ClientAccounts.FirstOrDefault();
editedClientAccount.OrgBalance = editedClientAccount.Balance;
// Mimic editing in UI:
editedClientAccount.Balance = DateTime.Now.Ticks;

// Mimic concurrent update.
Thread.Sleep(200);
using (var db2 = new MyContext(connectionString))
{
db2.ClientAccounts.First().Balance = DateTime.Now.Ticks;
db2.SaveChanges();
}
Thread.Sleep(200);

// Mimic return from UI:
var existingClient = db.ClientAccounts.Find(editedClientAccount.ID);
db.Entry(existingClient).OriginalValues["Balance"] = editedClientAccount.OrgBalance;
existingClient.Balance = editedClientAccount.Balance;

db.SaveChanges(); // Throws the DbUpdateConcurrencyException
}

这是上次更新执行的SQL:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [ClientAccount] SET [Balance] = @p0
WHERE [ID] = @p1 AND [Balance] = @p2;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 float,@p2 float',@p1=6,@p0=636473049969058940,@p2=1234

关于c# - EF Core DbUpdateConcurrencyException 未按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47392764/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com