EF6事务回滚&并发问题
EF6 Transaction rollback & concurrency issue
一旦我回滚事务并重试,它就失败了 DbUpdateConcurrencyException。
查看 SQL 探查器后,我看到第一次发送 INSERT 查询,正如预期的那样,而第二次它尝试 更新,即使第一次被回滚了?!
第一次查询:
exec sp_executesql N'INSERT [dbo].[FiscalReceipt]([PurchaseTime], [ReceiptNumber], [Cash], [Card], [Bank])
VALUES (@0, @1, @2, @3, @4)
SELECT [FiscalReceiptID]
FROM [dbo].[FiscalReceipt]
WHERE @@ROWCOUNT > 0 AND [FiscalReceiptID] = scope_identity()',N'@0 datetime2(7),@1 int,@2 decimal(18,2),@3 decimal(18,2),@4 decimal(18,2)',@0='2016-02-08 15:05:43.9145089',@1=666,@2=1.70,@3=0,@4=0
第二次查询:
exec sp_executesql N'UPDATE [dbo].[FiscalReceipt]
SET [PurchaseTime] = @0, [Cash] = @1
WHERE ([FiscalReceiptID] = @2)
',N'@0 datetime2(7),@1 decimal(18,2),@2 int',@0='2016-02-08 15:12:11.8101261',@1=555.00,@2=2042
P.S。 SQL Profiler table 的 EventClass 列中不应该有 TM:Rollback 或 TM:Commit 当提交某些内容时?
C#代码:
注意: OutOfPaperException 旨在被忽略,并且该范围内的事务是有意提交的。任何其他异常都应回滚更改。
var transaction = DatabaseContext.Database.BeginTransaction();
try
{
// Commented for debugging
//IFiscalPrinter fPrinter = DeviceManager.GetFiscalPrinter();
//var lastReceiptNumber = fPrinter.GetLastReceiptNumber();
// false data for debugging
var lastReceiptNumber = 666;
Receipt.ReceiptNumber = lastReceiptNumber++;
Receipt.PurchaseTime = DateTime.Now;
DatabaseContext.SaveChanges();
//fPrinter.PrintFiscalReceipt(Receipt);
// Exception for debugging purpose
if (Receipt.Cash < 500)
{
throw new Exception();
}
transaction.Commit();
Close();
}
catch (OutOfPaperException)
{
if (transaction != null)
{
transaction.Commit();
Close();
}
MessageBoxService.ShowMessage("Promenite papir pre sledećeg štampanja!", "Nema više papira!", MessageButton.OK, MessageIcon.Warning);
}
catch (Exception ex)
{
// I added this but this doesn't help
Receipt.PurchaseTime = new DateTime();
Receipt.ReceiptNumber = 0;
//Receipt.FiscalReceiptID = 0; <- I'm not allowed to do this
if (transaction != null)
{
transaction.Rollback();
}
MessageBoxService.ShowMessage(ex.Message, "Greška!", MessageButton.OK, MessageIcon.Error);
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}
DatabaseContext 的生命周期等于 ViewModel 的生命周期。
编辑: 将适当的入口状态更改为 EntryState.Added 结果操作成功,但感觉很脏。条目不应该在事务 rollback/fail 中保持已添加状态吗?
EDIT2: 在 运行 这个代码之后:
using (MetalShopDB ctx = new MetalShopDB())
using (var transaction = ctx.Database.BeginTransaction())
{
var receipt = new FiscalReceipt()
{
ReceiptNumber = 555,
PurchaseTime = DateTime.Now,
Cash = 100
};
ctx.FiscalReceipts.Add(receipt);
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
ctx.SaveChanges();
Console.WriteLine("Saved changes");
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
transaction.Rollback();
Console.WriteLine("Rolled back");
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
}
我得到了这个输出,我觉得这很奇怪,因为人们希望上下文与数据库同步,所以当你回滚时,上下文应该跟进变化。
Has changes True
Added
Saved changes
Has changes False
Unchanged
Rolled back
Has changes False
Unchanged
为您的 EDIT2
当调用 SaveChanges 方法时,如果保存时没有发现错误(这是你回滚之后的情况),则调用 ObjectContext.AcceptAllChanges() 方法它接受更改并填充主键、外键和更改条目状态。
回滚只回滚事务而不是对象上下文/更改跟踪器。
现在再次调用 SaveChanges 已经太晚了,因为即使您进行了回滚,实体也已经填充了数据库信息。
原题
您在 SaveChanges 成功完成
后抛出错误(用于调试)
if (Receipt.Cash < 500)
{
throw new Exception();
}
所以按照前面的逻辑,AcceptAllChanges 已经被调用了。
编辑
您可以通过使用 ObjectContext 保存来控制 AcceptAllChanges
var objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
objectContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);
transaction.Commmit();
objectContext.AcceptAllChanges();
一旦我回滚事务并重试,它就失败了 DbUpdateConcurrencyException。
查看 SQL 探查器后,我看到第一次发送 INSERT 查询,正如预期的那样,而第二次它尝试 更新,即使第一次被回滚了?!
第一次查询:
exec sp_executesql N'INSERT [dbo].[FiscalReceipt]([PurchaseTime], [ReceiptNumber], [Cash], [Card], [Bank])
VALUES (@0, @1, @2, @3, @4)
SELECT [FiscalReceiptID]
FROM [dbo].[FiscalReceipt]
WHERE @@ROWCOUNT > 0 AND [FiscalReceiptID] = scope_identity()',N'@0 datetime2(7),@1 int,@2 decimal(18,2),@3 decimal(18,2),@4 decimal(18,2)',@0='2016-02-08 15:05:43.9145089',@1=666,@2=1.70,@3=0,@4=0
第二次查询:
exec sp_executesql N'UPDATE [dbo].[FiscalReceipt]
SET [PurchaseTime] = @0, [Cash] = @1
WHERE ([FiscalReceiptID] = @2)
',N'@0 datetime2(7),@1 decimal(18,2),@2 int',@0='2016-02-08 15:12:11.8101261',@1=555.00,@2=2042
P.S。 SQL Profiler table 的 EventClass 列中不应该有 TM:Rollback 或 TM:Commit 当提交某些内容时?
C#代码:
注意: OutOfPaperException 旨在被忽略,并且该范围内的事务是有意提交的。任何其他异常都应回滚更改。
var transaction = DatabaseContext.Database.BeginTransaction();
try
{
// Commented for debugging
//IFiscalPrinter fPrinter = DeviceManager.GetFiscalPrinter();
//var lastReceiptNumber = fPrinter.GetLastReceiptNumber();
// false data for debugging
var lastReceiptNumber = 666;
Receipt.ReceiptNumber = lastReceiptNumber++;
Receipt.PurchaseTime = DateTime.Now;
DatabaseContext.SaveChanges();
//fPrinter.PrintFiscalReceipt(Receipt);
// Exception for debugging purpose
if (Receipt.Cash < 500)
{
throw new Exception();
}
transaction.Commit();
Close();
}
catch (OutOfPaperException)
{
if (transaction != null)
{
transaction.Commit();
Close();
}
MessageBoxService.ShowMessage("Promenite papir pre sledećeg štampanja!", "Nema više papira!", MessageButton.OK, MessageIcon.Warning);
}
catch (Exception ex)
{
// I added this but this doesn't help
Receipt.PurchaseTime = new DateTime();
Receipt.ReceiptNumber = 0;
//Receipt.FiscalReceiptID = 0; <- I'm not allowed to do this
if (transaction != null)
{
transaction.Rollback();
}
MessageBoxService.ShowMessage(ex.Message, "Greška!", MessageButton.OK, MessageIcon.Error);
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}
DatabaseContext 的生命周期等于 ViewModel 的生命周期。
编辑: 将适当的入口状态更改为 EntryState.Added 结果操作成功,但感觉很脏。条目不应该在事务 rollback/fail 中保持已添加状态吗?
EDIT2: 在 运行 这个代码之后:
using (MetalShopDB ctx = new MetalShopDB())
using (var transaction = ctx.Database.BeginTransaction())
{
var receipt = new FiscalReceipt()
{
ReceiptNumber = 555,
PurchaseTime = DateTime.Now,
Cash = 100
};
ctx.FiscalReceipts.Add(receipt);
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
ctx.SaveChanges();
Console.WriteLine("Saved changes");
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
transaction.Rollback();
Console.WriteLine("Rolled back");
Console.WriteLine("Has changes " + ctx.ChangeTracker.HasChanges());
Console.WriteLine(ctx.Entry(receipt).State);
}
我得到了这个输出,我觉得这很奇怪,因为人们希望上下文与数据库同步,所以当你回滚时,上下文应该跟进变化。
Has changes True
Added
Saved changes
Has changes False
Unchanged
Rolled back
Has changes False
Unchanged
为您的 EDIT2
当调用 SaveChanges 方法时,如果保存时没有发现错误(这是你回滚之后的情况),则调用 ObjectContext.AcceptAllChanges() 方法它接受更改并填充主键、外键和更改条目状态。
回滚只回滚事务而不是对象上下文/更改跟踪器。
现在再次调用 SaveChanges 已经太晚了,因为即使您进行了回滚,实体也已经填充了数据库信息。
原题
您在 SaveChanges 成功完成
后抛出错误(用于调试)if (Receipt.Cash < 500)
{
throw new Exception();
}
所以按照前面的逻辑,AcceptAllChanges 已经被调用了。
编辑
您可以通过使用 ObjectContext 保存来控制 AcceptAllChanges
var objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
objectContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);
transaction.Commmit();
objectContext.AcceptAllChanges();