简体   繁体   English

EF6交易回滚和并发问题

[英]EF6 Transaction rollback & concurrency issue

Once I rollback my transaction and try again, it fails with DbUpdateConcurrencyException . 一旦我回滚事务并重试,它将失败,并显示DbUpdateConcurrencyException

After looking at SQL profiler, I see that first time INSERT query is sent, as expected, while second time it tries to update , even though first one was rolled back?! 看完SQL事件探查器后,我看到第一次按预期发送INSERT查询,而第二次尝试更新 ,即使第一个已回滚?

First query: 第一个查询:

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

Second query: 第二个查询:

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

PS Shouldn't there be TM:Rollback or TM:Commit in the EventClass column in SQL Profiler table when something is commited? PS提交某些内容后,SQL Profiler表的EventClass列中应该没有TM:Rollback或TM:Commit吗?

C# Code: C#代码:

NOTE: OutOfPaperException is intended to be ignored and transaction inside that scope is being commited on purpose. 注意: OutOfPaperException旨在被忽略,并且该范围内的事务是有意提交的。 Changes should be rolled back on any other exception. 更改应回滚到任何其他异常。

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 lifetime is equal to ViewModel's lifetime. DatabaseContext的生存期等于ViewModel的生存期。

EDIT: Changing appropriate entry states to EntryState.Added results in successful operation, but it feels so dirty. 编辑:将适当的条目状态更改为EntryState.Added可成功完成操作,但感觉很脏。 Shouldn't entries remain in Added state on transaction rollback/fail? 事务回滚/失败时,条目是否不应该保持“已添加”状态?

EDIT2: After running this code: 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);
}

I get this output, which I find really strange, because one would expect context to be in sync with db, so when you rollback, context should follow up with the changes. 我得到了这个输出,我发现这真的很奇怪,因为人们希望上下文与db同步,因此回滚时,上下文应该跟进更改。

Has changes True
Added
Saved changes
Has changes False
Unchanged
Rolled back
Has changes False
Unchanged

For your EDIT2 为了您的EDIT2

When SaveChanges methods is called, if no error is found when saving (which is the case since you rollback after), the ObjectContext.AcceptAllChanges() methods is invoked which accept changes and populate primary keys, foreign keys and change entry state. 调用SaveChanges方法时,如果保存时未发现错误(自从您回滚后就是这种情况),则调用ObjectContext.AcceptAllChanges()方法,该方法接受更改并填充主键,外键并更改输入状态。

The rollback only rollback the transaction and not on the object context / change tracker. 回滚仅回滚事务,而不回滚对象上下文/更改跟踪器。

It's to late at this point to call SaveChanges again since entities are already populated with database information even if you did a rollback. 至此,再次调用SaveChanges已经很晚了,因为即使您进行了回滚,实体也已经填充了数据库信息。

Original Question 原始问题

You throw an error (for debugging) after the SaveChanges has been successfully completed 成功完成 SaveChanges后,您将引发错误(用于调试)

if (Receipt.Cash < 500)
{
    throw new Exception();
}

So by following the previous logic, the AcceptAllChanges is already invoked. 因此,按照前面的逻辑,已经调用了AcceptAllChanges。

EDIT 编辑

You can control the AcceptAllChanges by saving using the ObjectContext 您可以通过使用ObjectContext保存来控制AcceptAllChanges

var objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
objectContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);

transaction.Commmit();
objectContext.AcceptAllChanges();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM