我们有一个电子商务网站,人们可以在那里购买礼品卡。这些卡存储在表中,并按整数排序以用于后勤目的。如果有人购买礼品卡,我们会在数据库中插入采购订单并编辑第一张符合条件的卡片的字段,以便知道它不再可用。
但有时当2个用户同时购买一张卡时,同时给出两张卡。所以我们与IsolationLevel.ReadCommitted
进行了一次无效的交易。对于4次并发点击,2个Card
有1个PurchaseOrder
,2个Card
有PurchaseOrder
。
TransactionOptions transOption = new TransactionOptions();
transOption.IsolationLevel = IsolationLevel.ReadCommitted;
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, transOption))
{
PurchaseOrder purchaseOrder = GetFull(request.OrderId);
purchaseOrder.ChangeTracker.ChangeTrackingEnabled = true;
purchaseOrder.IdStatus = DbConstants.RefPurchaseOrderStatus.Completed;
//select the quantity of purchased gift cards and update them with ObjectContext.SaveChanges()
List<Card> cards = _cardBusiness.AssignCards(purchaseOrder.Quantity, purchaseOrder.IdProduct, purchaseOrder.IdUserAccount, purchaseOrder.IdChannel);
Card[] arrCards = cards.ToArray();
int count = 0;
//Set a link between a PurchaseOrder Line and the Card
foreach(PurchaseOrderLine line in purchaseOrder.PurchaseOrderLines)
{
line.ChangeTracker.ChangeTrackingEnabled = true;
line.IdCard = arrCards[count].Id;
count++;
}
PurchaseOrder po = Update(purchaseOrder);
scope.Complete();
return true;
}
快速评论:在交易中,我们获取之前插入的PurchaseOrder
,更改其状态,然后选择可用的礼品卡,编辑一些属性,SaveChanges()
并返回礼品卡( s)链接到PurchaseOrderLine
。
但我们显然没有锁定卡片选择。我们怎么能这样做?
更新
public static List<Card> AssignCards(int qty, int idProduct, Guid idUserAccount, int? idChannel)
{
using (RestopolitanEntities context = GetContext())
{
try
{
var Xcards = (from c in context.Cards
select c);
List<Card> cards = Xcards.OrderBy(c => c.IdOrder).Take(qty).ToList();
foreach (Card card in cards)
{
card.ChangeTracker.ChangeTrackingEnabled = true;
card.UseDate = DateTime.Now;
}
context.SaveChanges();
return cards.ToList();
}
catch (Exception ex)
{
HandleException(ex);
}
}
return null;
}
这次我尝试使用IsolationLevel.RepeatableRead
,Card
不再归因于2 PurchaseOrder
。但是Card
没有返回AssignCards()
。对于4次并发点击,有2个PurchaseOrder
,1个不同的Card
和2个PurchaseOrder
没有Card
。
答案 0 :(得分:1)
在交易开始时获取应用程序锁定,
context.Database.ExecuteSqlCommand("exec sp_getapplock 'AssignCards','Exclusive';");
或者在查询上使用一些锁定提示来识别要使用的卡片:
var sql = "select top (@qty) CardId from Cards with (rowlock, updlock, readpast) where UseDate is null";
var cardIds = context.Database.SqlQuery<int>(sql,qty).ToList();
List<Card> cards = context.Cards.Where(c => cardIds.Contains(c.CardId)).ToList();
如果使用锁定提示,则可以使用READPAST提示允许并发会话跳过其他会话锁定的卡。如果您使用Applciation Lock,您可以在交易过程中释放锁(选择卡后)。