Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/331.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# EF Core DbUpdateConcurrencyException未按预期工作_C#_.net_Ef Code First_Entity Framework Core_Ef Core 2.0 - Fatal编程技术网

C# EF Core DbUpdateConcurrencyException未按预期工作

C# EF Core DbUpdateConcurrencyException未按预期工作,c#,.net,ef-code-first,entity-framework-core,ef-core-2.0,C#,.net,Ef Code First,Entity Framework Core,Ef Core 2.0,我有以下代码,我正在尝试使用ef core更新ClientAccount,但并发性检查失败: public class ClientAccount { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } [Required] [ConcurrencyCheck]

我有以下代码,我正在尝试使用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属性标记属性应该可以完成这项工作。

[Timestamp]
属性添加到
DateTimeUpdated
。在那之后应该可以工作,但不可否认,我还没有使用这个功能。但是我认为类型应该是
byte[]

找到参考链接:

没有按预期工作

当然会,但您的代码几乎不会产生并发异常

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

如何修复它 如果希望显示并发冲突,则应将原始值存储在
ClientAccount
对象中,并将其分配给上下文中的原始值。例如:

班级:

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; }
    
    ...
}
在update方法中,为了简洁起见,假设我们有可用的上下文:

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
。由于您使用的是存储库,因此必须添加一个将原始值提供给包装上下文的方法

更好的方法? 现在,所有这些都只是为了一处房产。对于乐观并发控制,更常见的是使用一个特殊属性,即“版本”属性,或者数据库中的字段。某些数据库(其中包括Sql Server)会在每次更新时自动递增此版本字段,这意味着在更新记录的任何值时,该字段总是不同的

因此,让您的类具有以下属性:

public byte[] Rowversion { get; set; }
以及映射:

modelBuilder.Entity<ClientAccount>().Property(c => c.Rowversion).IsRowVersion();
。。。并且用户将了解任何并发冲突

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

示例代码 这是上次更新时执行的SQL:

exec sp_executesql N'SET NOCOUNT ON;
更新[ClientAccount]设置[Balance]=@p0
其中[ID]=@p1和[Balance]=@p2;
选择@@ROWCOUNT;
“,N'@p1 int,@p0 float,@p2 float',@p1=6,@p0=636473049969058940,@p2=1234”

有人可以帮忙吗?在评论中寻求帮助并没有什么用处。这并没有把这个问题推到头版。编辑你的问题就行了。例如,添加执行实际修改的代码。这一部分对于回答问题至关重要。添加了完整的代码感谢您的帮助。我希望避免使用此解决方案,并使用ConcurrencyCheck attributeThanks作为您的建议。我通过添加一个if条件来模拟ms场景,等待一个特定的平衡值,然后线程睡眠。在线程进入睡眠模式后,我启动一个新线程来更新客户端余额。当第一个线程恢复并尝试更新它不再存在的值时,
DbUpdateConcurrencyException
不会触发。同一个表中还有十几个其他属性需要检查并发性。不仅仅是平衡,我在上面发布了我的代码的一个小样本。正如我在上面的回答中提到的,我希望避免使用Rowversion解决方案,并使用ConcurrencyCheck属性,因为它是EF core提供的一个功能。谢谢,谢谢。不知道发生了什么,但它似乎没有任何问题,现在没有改变任何事情。我上面的代码似乎是正确的。也许这是测试的线程问题
db.Entry(existingClient).OriginalValues["Rowversion"] = clientAccount.Rowversion;
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
}