EF Core 与 MySQL:事务和并发处理详解
<div class="_7d763a7"><div class="_48edb25">
<div class="e1675d8b ds-think-content">
<div class="ds-markdown">
<p class="ds-markdown-paragraph">本文将详细讲解EF Core与MySQL的事务和并发处理,分为三个部分:使用事务、处理并发冲突(乐观并发)以及悲观并发(MySQL中使用锁)。</p>
<ul>
<li>
<p class="ds-markdown-paragraph">使用事务<br>在EF Core中,可以使用事务来确保一系列操作要么全部成功,要么全部失败。EF Core支持多种事务管理方式,包括自动事务(SaveChanges自动包装事务)和显式事务。</p>
</li>
<li>
<p class="ds-markdown-paragraph">处理并发冲突(乐观并发)<br>乐观并发假设多个事务很少冲突,因此不会立即锁定资源。而是在更新时检查数据是否被其他事务修改过。EF Core通过配置并发令牌(Concurrency Token)来实现乐观并发。</p>
</li>
<li>
<p class="ds-markdown-paragraph">悲观并发(MySQL中使用锁)<br>悲观并发假设冲突经常发生,因此在读取数据时就会锁定资源,防止其他事务修改。在MySQL中,可以通过使用事务和SELECT ... FOR UPDATE语句来实现悲观并发。</p>
</li>
</ul>
<h2>1. 使用事务</h2>
<h3>基本事务用法</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用DbContext的Database.BeginTransaction()</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction =<span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransaction())
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行多个数据库操作</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">new</span> Product { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New Product</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Price = <span style="color: rgba(128, 0, 128, 1)">99.99m</span>, Stock = <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)"> };
context.Products.Add(product);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> order = <span style="color: rgba(0, 0, 255, 1)">new</span> Order { CustomerId = <span style="color: rgba(128, 0, 128, 1)">1</span>, OrderDate =<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now };
context.Orders.Add(order);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存更改</span>
<span style="color: rgba(0, 0, 0, 1)"> context.SaveChanges();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交事务</span>
<span style="color: rgba(0, 0, 0, 1)"> transaction.Commit();
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回滚事务</span>
<span style="color: rgba(0, 0, 0, 1)"> transaction.Rollback();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h3>使用TransactionScope(分布式事务)</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 需要安装System.Transactions包</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> scope = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 多个数据库操作</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> context1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationDbContext())
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> context2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationDbContext())
{
context1.Products.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Product { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Product 1</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Price = <span style="color: rgba(128, 0, 128, 1)">10.99m</span><span style="color: rgba(0, 0, 0, 1)"> });
context2.Orders.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Order { CustomerId = <span style="color: rgba(128, 0, 128, 1)">1</span>, OrderDate =<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now });
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context1.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context2.SaveChangesAsync();
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 完成事务</span>
<span style="color: rgba(0, 0, 0, 1)"> scope.Complete();
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 事务会自动回滚</span>
<span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h3>设置事务隔离级别</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction =<span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行操作</span>
context.Products.Add(<span style="color: rgba(0, 0, 255, 1)">new</span> Product { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New Product</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Price = <span style="color: rgba(128, 0, 128, 1)">99.99m</span><span style="color: rgba(0, 0, 0, 1)"> });
context.SaveChanges();
transaction.Commit();
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
transaction.Rollback();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h3>异步事务处理</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>> ProcessOrderAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> productId, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> quantity)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransactionAsync())
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查库存</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Products
.FirstOrDefaultAsync(p </span>=> p.Id ==<span style="color: rgba(0, 0, 0, 1)"> productId);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (product == <span style="color: rgba(0, 0, 255, 1)">null</span> || product.Stock <<span style="color: rgba(0, 0, 0, 1)"> quantity)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 减少库存</span>
product.Stock -=<span style="color: rgba(0, 0, 0, 1)"> quantity;
context.Products.Update(product);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建订单</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> order = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Order
{
ProductId </span>=<span style="color: rgba(0, 0, 0, 1)"> productId,
Quantity </span>=<span style="color: rgba(0, 0, 0, 1)"> quantity,
OrderDate </span>=<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now
};
context.Orders.Add(order);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.CommitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<h2>2. 处理并发冲突(乐观并发)</h2>
<h3>配置并发令牌</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在实体中定义并发令牌属性</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Product
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">decimal</span> Price { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Stock { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用时间戳作为并发令牌</span>
<span style="color: rgba(0, 0, 0, 1)"> 1777992846
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[] Version { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 或者使用自定义的并发令牌
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> public uint RowVersion { get; set; }</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在DbContext中配置并发令牌</span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity</span><Product><span style="color: rgba(0, 0, 0, 1)">()
.Property(p </span>=><span style="color: rgba(0, 0, 0, 1)"> p.Version)
.IsRowVersion()
.IsConcurrencyToken();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 或者使用非时间戳字段作为并发令牌</span>
modelBuilder.Entity<Product><span style="color: rgba(0, 0, 0, 1)">()
.Property(p </span>=><span style="color: rgba(0, 0, 0, 1)"> p.Name)
.IsConcurrencyToken();
}</span></pre>
</div>
<h3>处理并发异常</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> UpdateProductAsync(Product updatedProduct)
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
context.Products.Update(updatedProduct);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (DbUpdateConcurrencyException ex)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理并发冲突</span>
<span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> entry <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> ex.Entries)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (entry.Entity <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> Product)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取数据库中的当前值</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> databaseValues = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> entry.GetDatabaseValuesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (databaseValues == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实体已被删除</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换为实体</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> databaseProduct =<span style="color: rgba(0, 0, 0, 1)"> (Product)databaseValues.ToObject();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 决定如何解决冲突
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 选项1: 使用客户端值
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> entry.OriginalValues.SetValues(databaseValues);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> await context.SaveChangesAsync();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 选项2: 使用数据库值
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> entry.CurrentValues.SetValues(databaseValues);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 选项3: 合并值</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> currentValues =<span style="color: rgba(0, 0, 0, 1)"> entry.CurrentValues;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> resolvedValues =<span style="color: rgba(0, 0, 0, 1)"> currentValues.Clone();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 合并逻辑示例</span>
resolvedValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name</span><span style="color: rgba(128, 0, 0, 1)">"</span>] = currentValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name</span><span style="color: rgba(128, 0, 0, 1)">"</span>]; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保留新名称</span>
resolvedValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Price</span><span style="color: rgba(128, 0, 0, 1)">"</span>] = databaseValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Price</span><span style="color: rgba(128, 0, 0, 1)">"</span>]; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用数据库价格</span>
resolvedValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Stock</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> Math.Max(
(</span><span style="color: rgba(0, 0, 255, 1)">int</span>)currentValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Stock</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
(</span><span style="color: rgba(0, 0, 255, 1)">int</span>)databaseValues[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Stock</span><span style="color: rgba(128, 0, 0, 1)">"</span>]); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用较大库存
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置解析后的值</span>
<span style="color: rgba(0, 0, 0, 1)"> entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 重试保存</span>
<span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h3>自定义并发冲突解决策略</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ConcurrencyHandler
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> ResolveConcurrencyAsync(
DbUpdateConcurrencyException ex,
ApplicationDbContext context)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> saved = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> entry <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> ex.Entries)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取数据库当前值</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> databaseValues = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> entry.GetDatabaseValuesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (databaseValues == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实体已被删除</span>
entry.State =<span style="color: rgba(0, 0, 0, 1)"> EntityState.Detached;
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取原始值和当前值</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> originalValues =<span style="color: rgba(0, 0, 0, 1)"> entry.OriginalValues;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> currentValues =<span style="color: rgba(0, 0, 0, 1)"> entry.CurrentValues;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据实体类型应用不同的解决策略</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (entry.Entity <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> Product product)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> ResolveProductConcurrency(entry, databaseValues);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (entry.Entity <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> Order order)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> ResolveOrderConcurrency(entry, databaseValues);
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 重试保存</span>
<span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
saved </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (DbUpdateConcurrencyException)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果再次失败,可能需要递归处理或放弃</span>
<span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> saved;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task ResolveProductConcurrency(
EntityEntry entry,
PropertyValues databaseValues)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> databaseProduct =<span style="color: rgba(0, 0, 0, 1)"> (Product)databaseValues.ToObject();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> currentProduct =<span style="color: rgba(0, 0, 0, 1)"> (Product)entry.Entity;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解决策略:保留较高的库存值,其他字段使用最新值</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> resolvedStock =<span style="color: rgba(0, 0, 0, 1)"> Math.Max(currentProduct.Stock, databaseProduct.Stock);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新实体</span>
<span style="color: rgba(0, 0, 0, 1)"> entry.OriginalValues.SetValues(databaseValues);
currentProduct.Stock </span>=<span style="color: rgba(0, 0, 0, 1)"> resolvedStock;
}
}</span></pre>
</div>
<h2>3. 悲观并发(MySQL中使用锁)</h2>
<h3>使用SELECT ... FOR UPDATE</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用原始SQL进行行级锁定</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<Product> GetProductWithLockAsync(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> productId)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开始事务</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用SELECT ... FOR UPDATE锁定行</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Products
.FromSqlRaw(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SELECT * FROM Products WHERE Id = {0} FOR UPDATE</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, productId)
.AsNoTracking()
.FirstOrDefaultAsync();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行需要原子性的操作
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.CommitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> product;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<h3>使用EF Core的锁机制</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在EF Core中模拟行级锁</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>> ReserveProductAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> productId, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> quantity)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取产品并锁定行</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Products
.FirstOrDefaultAsync(p </span>=> p.Id ==<span style="color: rgba(0, 0, 0, 1)"> productId);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (product == <span style="color: rgba(0, 0, 255, 1)">null</span> || product.Stock <<span style="color: rgba(0, 0, 0, 1)"> quantity)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新库存</span>
product.Stock -=<span style="color: rgba(0, 0, 0, 1)"> quantity;
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.CommitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<h3>处理锁超时</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在MySQL连接字符串中设置锁等待超时</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> connectionString = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">server=localhost;database=efcoredb;user=root;password=yourpassword;</span><span style="color: rgba(128, 0, 0, 1)">"</span> +
<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Connection Timeout=30;Default Command Timeout=30;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 或者在DbContext中配置</span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
options </span>=> options.EnableRetryOnFailure().CommandTimeout(<span style="color: rgba(128, 0, 128, 1)">30</span><span style="color: rgba(0, 0, 0, 1)">));
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理锁超时异常</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>> TryUpdateWithLockAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> productId, Action<Product><span style="color: rgba(0, 0, 0, 1)"> updateAction)
{
</span><span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> maxRetries = <span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> attempt = <span style="color: rgba(128, 0, 128, 1)">0</span>; attempt < maxRetries; attempt++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取并锁定产品</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Products
.FirstOrDefaultAsync(p </span>=> p.Id ==<span style="color: rgba(0, 0, 0, 1)"> productId);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (product == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行更新操作</span>
<span style="color: rgba(0, 0, 0, 1)"> updateAction(product);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.CommitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span> (MySqlException ex) when (ex.Number == <span style="color: rgba(128, 0, 128, 1)">1205</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Lock wait timeout</span>
<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 锁等待超时,重试</span>
<span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (attempt == maxRetries - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 指数退避策略</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> Task.Delay(TimeSpan.FromMilliseconds(<span style="color: rgba(128, 0, 128, 1)">100</span> * Math.Pow(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">, attempt)));
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<h3>表级锁定</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用表级锁定(谨慎使用,影响性能)</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> PerformMaintenanceAsync()
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> transaction = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 锁定整个表</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> context.Database.ExecuteSqlRawAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">LOCK TABLES Products WRITE</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行维护操作</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> context.Database.ExecuteSqlRawAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">UPDATE Products SET Price = Price * 1.1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解锁表</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> context.Database.ExecuteSqlRawAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">UNLOCK TABLES</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.CommitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span> context.Database.ExecuteSqlRawAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">UNLOCK TABLES</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> transaction.RollbackAsync();
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<h2>4. 高级并发控制模式</h2>
<h3>使用版本号实现乐观并发</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> VersionedEntity
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">uint</span> Version { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用无符号整数作为版本号</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置版本号并发令牌</span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity</span><Product><span style="color: rgba(0, 0, 0, 1)">()
.Property(p </span>=><span style="color: rgba(0, 0, 0, 1)"> p.Version)
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate();
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在更新时自动增加版本号</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> SaveChanges()
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> entries =<span style="color: rgba(0, 0, 0, 1)"> ChangeTracker.Entries()
.Where(e </span>=> e.State == EntityState.Modified || e.State ==<span style="color: rgba(0, 0, 0, 1)"> EntityState.Added);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> entry <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> entries)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (entry.Entity <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> VersionedEntity entity)
{
entity.Version</span>++<span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.SaveChanges();
}</span></pre>
</div>
<h3>使用自定义锁机制</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实现简单的应用级锁机制</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationLockService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, SemaphoreSlim> Locks =
<span style="color: rgba(0, 0, 255, 1)">new</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, SemaphoreSlim><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<T> ExecuteWithLockAsync<T>(<span style="color: rgba(0, 0, 255, 1)">string</span> lockKey, Func<Task<T>><span style="color: rgba(0, 0, 0, 1)"> operation)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> lockObject = Locks.GetOrAdd(lockKey, key => <span style="color: rgba(0, 0, 255, 1)">new</span> SemaphoreSlim(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> lockObject.WaitAsync();
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> operation();
}
</span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)">
{
lockObject.Release();
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用应用级锁</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>> UpdateProductSafelyAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> productId, Action<Product><span style="color: rgba(0, 0, 0, 1)"> updateAction)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> lockService = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationLockService();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> lockKey = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">product_{productId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span> lockService.ExecuteWithLockAsync(lockKey, <span style="color: rgba(0, 0, 255, 1)">async</span> () =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationDbContext())
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> product = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.Products.FindAsync(productId);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (product == <span style="color: rgba(0, 0, 255, 1)">null</span>) <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
updateAction(product);
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.SaveChangesAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
});
}</span></pre>
</div>
<h2>5. 监控和诊断并发问题</h2>
<h3>记录并发冲突</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ConcurrencyLogger
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<ConcurrencyLogger><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ConcurrencyLogger(ILogger<ConcurrencyLogger><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> LogConcurrencyConflict(DbUpdateConcurrencyException ex, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> operation)
{
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">并发冲突发生在操作: {Operation}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, operation);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> entry <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> ex.Entries)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> databaseValues =<span style="color: rgba(0, 0, 0, 1)"> entry.GetDatabaseValues();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> currentValues =<span style="color: rgba(0, 0, 0, 1)"> entry.CurrentValues;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> originalValues =<span style="color: rgba(0, 0, 0, 1)"> entry.OriginalValues;
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实体: {EntityType}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, entry.Metadata.Name);
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">数据库值: {DatabaseValues}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, databaseValues?<span style="color: rgba(0, 0, 0, 1)">.Properties);
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">当前值: {CurrentValues}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, currentValues?<span style="color: rgba(0, 0, 0, 1)">.Properties);
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">原始值: {OriginalValues}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, originalValues?<span style="color: rgba(0, 0, 0, 1)">.Properties);
}
}
}</span></pre>
</div>
<h3>性能计数器监控</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监控并发冲突率</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ConcurrencyMonitor
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> _totalOperations;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> _concurrencyExceptions;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> RecordOperation(<span style="color: rgba(0, 0, 255, 1)">bool</span> hadConcurrencyException = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
{
Interlocked.Increment(</span><span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> _totalOperations);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (hadConcurrencyException)
{
Interlocked.Increment(</span><span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> _concurrencyExceptions);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">double</span><span style="color: rgba(0, 0, 0, 1)"> GetConcurrencyExceptionRate()
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> total = Interlocked.Read(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> _totalOperations);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> exceptions = Interlocked.Read(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> _concurrencyExceptions);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> total > <span style="color: rgba(128, 0, 128, 1)">0</span> ? (<span style="color: rgba(0, 0, 255, 1)">double</span>)exceptions / total : <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h2>总结</h2>
<p class="ds-markdown-paragraph">本文详细介绍了EF Core与MySQL中的事务和并发处理,包括:</p>
<ol>
<li class="ds-markdown-paragraph"><strong>事务管理:</strong></li>
</ol><ol start="1">
<li style="list-style-type: none">
<ul>
<li>
<p class="ds-markdown-paragraph">基本事务用法和TransactionScope</p>
</li>
<li>
<p class="ds-markdown-paragraph">设置事务隔离级别</p>
</li>
<li>
<p class="ds-markdown-paragraph">异步事务处理</p>
</li>
</ul>
</li>
<li>
<p class="ds-markdown-paragraph"><strong>乐观并发控制:</strong></p>
<ul>
<li>
<p class="ds-markdown-paragraph">配置并发令牌(时间戳或自定义字段)</p>
</li>
<li>
<p class="ds-markdown-paragraph">处理DbUpdateConcurrencyException</p>
</li>
<li>
<p class="ds-markdown-paragraph">实现自定义冲突解决策略</p>
</li>
</ul>
</li>
<li>
<p class="ds-markdown-paragraph"><strong>悲观并发控制:</strong></p>
<ul>
<li>
<p class="ds-markdown-paragraph">使用SELECT ... FOR UPDATE进行行级锁定</p>
</li>
<li>
<p class="ds-markdown-paragraph">处理锁超时和重试机制</p>
</li>
<li>
<p class="ds-markdown-paragraph">表级锁定(谨慎使用)</p>
</li>
</ul>
</li>
<li>
<p class="ds-markdown-paragraph"><strong>高级并发模式:</strong></p>
<ul>
<li>
<p class="ds-markdown-paragraph">版本号实现乐观并发</p>
</li>
<li>
<p class="ds-markdown-paragraph">应用级锁机制</p>
</li>
<li>
<p class="ds-markdown-paragraph">监控和诊断并发问题</p>
</li>
</ul>
</li>
</ol>
<p class="ds-markdown-paragraph">在实际应用中,应根据具体场景选择合适的并发控制策略:</p>
<ul>
<li>
<p class="ds-markdown-paragraph">对于读多写少的场景,乐观并发通常更高效</p>
</li>
<li>
<p class="ds-markdown-paragraph">对于写密集或需要强一致性的场景,悲观并发可能更合适</p>
</li>
<li>
<p class="ds-markdown-paragraph">总是要考虑超时和重试机制,以提高系统的健壮性</p>
</li>
<li>
<p class="ds-markdown-paragraph">监控并发冲突率,及时发现和解决性能瓶颈</p>
</li>
</ul>
<p class="ds-markdown-paragraph">正确实施事务和并发控制是构建高并发、高可用应用程序的关键。建议在生产环境中进行充分的压力测试,确保并发控制策略能够满足实际需求。</p>
</div>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/jixingsuiyuan/p/19097867
頁:
[1]