gpt4 book ai didi

c# - 如何使用 Entity Framework 选择正确的并发/锁定机制

转载 作者:太空宇宙 更新时间:2023-11-03 14:58:46 25 4
gpt4 key购买 nike

我正在尝试实现一个简单的服务(使用 C#、SQL Server、 Entity Framework )来处理来自客户的付款,并预先进行多次检查(例如,一天内不能购买超过 10 次的单一产品等)

代码的简化版本如下:

public void ExecutePayment(int productId, PaymentInfo paymentInfo)
{
using (var dbContext = new MyDbContext())
{
var stats = dbContext.PaymentStatistics.Single(s => s.ProductId== productId);
var limits = dbContext.Limits.Single(l => l.ProductId == productId);
int newPaymentCount = stats.DailyPaymentCount + 1;
if (newPaymentCount > limits.MaxDailyPaymentCount)
{
throw new InvalidOperationException("Exceeded payment count limit");
}

// other limits here...

var paymentResult = ProcessPayment(paymentInfo); <-- long operation, takes 2-3 seconds
if (paymentResult.Success)
{
stats.DailyPaymentCount = newPaymentCount;
}

dbContext.SaveChanges();
}
}

我担心的是可能的并发问题。我需要确保没有 2 个线程/进程同时开始检查/更新 stats.PaymentCount,否则统计信息将不同步。

我正在考虑将整个方法包装到分布式锁中(例如使用 this implementation ),如下所示:

string lockKey = $"processing-payment-for-product-{productId}";
var myLock = new SqlDistributedLock(lockKey);
using (myLock.Acquire())
{
ExecutePayment(productId, paymentInfo);
}

但这种方法的问题是 ProcessPayment 非常慢(2-3 秒),这意味着同一产品的任何并发付款请求都必须等待 2-3 秒才能开始限制检查。

谁能为这种情况建议一个好的锁定解决方案?

最佳答案

与其对每个事务使用锁(悲观并发)——您最好使用乐观并发来检查 DailyPaymentCount

使用原始 SQL(因为原子增量在 EF 中很难)- 假定列名:

// Atomically increment dailyPaymentCount. Fail if we're over the limit.
private string incrementQuery = @"UPDATE PaymentStatistics p
SET dailyPaymentCount = dailyPaymentCount + 1
FROM PaymentStatistics p
JOIN Limits l on p.productId = l.productId
WHERE p.dailyPaymentCount < l.maxDailyPaymentCount
AND p.productId = @givenProductId";

// Atomically decrement dailyPaymentCount
private string decrementQuery = @"UPDATE PaymentStatistics p
SET dailyPaymentCount = dailyPaymentCount - 1
FROM PaymentStatistics p
WHERE p.productId = @givenProductId";

public void ExecutePayment(int productId, PaymentInfo paymentInfo)
{
using (var dbContext = MyDbContext()) {

using (var dbContext = new MyDbContext())
{
// Try to increment the payment statistics for the given product
var rowsUpdated = dbContext.Database.ExecuteSqlCommand(incrementQuery, new SqlParameter("@givenProductId", productId));

if (rowsUpdated == 0) // If no rows were updated - we're out of stock (or the product/limit doesn't exist)
throw new InvalidOperationException("Out of stock!");

// Note: there's a risk of our stats being out of sync if the program crashes after this point
var paymentResult = ProcessPayment(paymentInfo); // long operation, takes 2-3 seconds

if (!paymentResult.Success)
{
dbContext.Database.ExecuteSqlCommand(decrementQuery, new SqlParameter("@givenProductId", productId));
}
}
}
}

这有效地将特定产品的“飞行中”付款包含在您的统计数据中 - 并将其用作障碍。在处理付款之前 - 尝试(自动)增加您的统计信息 - 如果 productsSold + paymentsPending > stock,则付款失败。如果付款失败,则减少 paymentsPending - 这将允许后续付款请求成功。

如评论中所述 - 如果付款失败,则存在统计数据与已处理付款不同步的风险,并且应用程序会在 dailyPaymentCount 减少之前崩溃。如果这是一个问题(即您无法在应用程序重新启动时重建统计信息)——您可以使用 RepeatableRead 事务,该事务将在应用程序崩溃时回滚——但随后您又回到只能处理每个 productId 同时付款,因为产品的 PaymentStatistic 行在增加后将被锁定 - 直到交易结束。这是不可避免的 - 在您知道您有库存之前您无法处理付款,并且在您处理/失败机上付款之前您无法确定您是否有库存。

this answer 中对乐观/悲观并发有很好的概述。 .

关于c# - 如何使用 Entity Framework 选择正确的并发/锁定机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47422352/

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