【问题标题】:EntityFramework Transaction Scope - not rolling back 1st transaction实体框架事务范围 - 不回滚第一个事务
【发布时间】:2013-01-24 18:51:26
【问题描述】:

我正在尝试创建一个封装 4 个数据库表插入和 2 个更新的事务。

我“大部分时间”都在工作。我的意思是,如果我在这 6 个 db 交互中的任何一个中遇到错误,则会发生先前的回滚......除了第一个。第一个是插入表头...随后插入明细表甚至另一个表头等...所有回滚...但是如果在回滚后检查表,它们都将没有记录,除了第一个。

//Create receipt, ic, printq; update pod, poh
        public List<ActionConfirmation<int>> CreateReceipt(
            IEnumerable<ReceiptDetailPalletListViewModel> viewModelList,
            int intUserId,
            int intFacilityId,
            int intLocationId
        )
        {
            var dbContext = new InventoryMgmtContext();

            //Opening connection
            dbContext.Database.Connection.Open();

            int intReceiptHdrId = 0;
            int intICHdrId = 0;

            var results = new List<ActionConfirmation<int>>();

            foreach (ReceiptDetailPalletListViewModel viewModel in viewModelList)
            {
                if (viewModel.ReceivedQty > 0)
                {
                    using (TransactionScope transaction = new TransactionScope())
                    {
                        //Create Receipt Header
                        ActionConfirmation<int> rcptHdrResult = CreateReceiptHeader(
                            dbContext,
                            intUserId,
                            intFacilityId); <===== This Tran never rolls back. Insert occured.

                        results.Add(rcptHdrResult);

                        if (!rcptHdrResult.WasSuccessful) //Recp Hdr create failed.
                        {
                            CloseFailedTrans(dbContext, transaction);

                            return results;
                        }

                        intReceiptHdrId = rcptHdrResult.Value;

                        //Create new ICHeader
                        ActionConfirmation<int> icHdrResult = CreateICHeader(
                            dbContext,
                            intUserId,
                            intFacilityId,
                            intLocationId,
                            intReceiptHdrId
                        );

                        results.Add(icHdrResult);

                        if (!icHdrResult.WasSuccessful)
                        {
                            CloseFailedTrans(dbContext, transaction);

                            return results;
                        }

                        intICHdrId = icHdrResult.Value;

                        //Create new ICDetail
                        ActionConfirmation<int> icDtlResult = CreateICDetail(
                            dbContext,
                            intICHdrId,
                            viewModel.ItemId,
                            viewModel.PODetailId,
                            viewModel.ReceivedQty,
                            intUserId
                        );

                        results.Add(icDtlResult);

                        if (!icDtlResult.WasSuccessful)
                        {
                            CloseFailedTrans(dbContext, transaction);

                            return results;
                        }

                        //Create new Recpt Detail
                        ActionConfirmation<int> rcptDtlResult = CreateReceiptDetail(
                            dbContext,
                            intReceiptHdrId,
                            viewModel.PODetailId,
                            viewModel.ReceivedQty,
                            intUserId
                        );

                        results.Add(rcptDtlResult);

                        if (!rcptDtlResult.WasSuccessful)
                        {
                            CloseFailedTrans(dbContext, transaction);

                            return results;
                        }

                        //Update PO Detail qty and Header status
                        List<ActionConfirmation<int>> poResults = UpdatePODetail(
                            dbContext,
                            viewModel.PODetailId,
                            viewModel.ReceivedQty,
                            intUserId
                        );

                        foreach (ActionConfirmation<int> poResult in poResults)
                        {
                            results.Add(poResult);

                            if (!poResult.WasSuccessful)
                            {
                                CloseFailedTrans(dbContext, transaction);

                                return results;
                            }
                        }

                        //Create new Print Q
                        ActionConfirmation<int> printqResult = CreatePrintQRecords(
                            dbContext,
                            intICHdrId,
                            intFacilityId,
                            intUserId
                        );

                        results.Add(printqResult);

                        if (!printqResult.WasSuccessful)
                        {
                            CloseFailedTrans(dbContext, transaction);

                            return results;
                        }

                        //Everything inserted correctly
                        CloseSuccessTrans(dbContext, transaction);

                    } //using statement
                } //if rcv qty > 0
            } // for each loop

            dbContext.Database.Connection.Dispose();

            return results;
        }

这里是交易相关的方法:

// Close DB Connections and transaction
        private void CloseFailedTrans(InventoryMgmtContext dbContext, TransactionScope transaction)
        {
            //TODO: logging
            CloseTrans(dbContext, transaction);
        }

        // Close DB Connections and transaction
        private void CloseSuccessTrans(InventoryMgmtContext dbContext, TransactionScope transaction)
        {
            transaction.Complete();
            CloseTrans(dbContext, transaction);
        }
        // Close DB Connections and transaction
        private void CloseTrans(InventoryMgmtContext dbContext, TransactionScope transaction)
        {
            transaction.Dispose();
        }

这是执行插入的方法之一的示例。它们都遵循相同的模式:

//Create Receipt Header
        private ActionConfirmation<int> CreateReceiptHeader(
            InventoryMgmtContext dbContext,
            int intUserId,
            int intFacilityId
        )
        {
            //var repository = new Repository<ReceiptHeader>(dbContext);
            var repository = new ReceiptHeaderRepository(dbContext);

            //Create new Receipt Header
            ReceiptHeader rcptHdr = new ReceiptHeader()
            {
                FacilityId = intFacilityId,
                StatusId = 1,
                CreatedById = intUserId,
                CreatedOn = DateTime.Now,
                ModifiedById = intUserId,
                ModifiedOn = DateTime.Now
            };

            return repository.Insert(rcptHdr);
        }

这里是存储库插入方法:

public virtual ActionConfirmation<int> Insert(TRepository entity)
        {
            try
            {
                _dataContext.Entry(entity).State = System.Data.EntityState.Added;
                _dataContext.SaveChanges();

                return CRUDMessage(true, "saved", entity);
            }
            catch (Exception ex)
            {
                return CRUDMessage(false, "save", entity, ex);
            }
        }

【问题讨论】:

  • 如果 Recp Hdr 创建失败,如何在 Recp 头表中看到记录?
  • 不,你不会。问题是如果 Recpt Header 成功,但任何其他后续失败,Recpt Header 行仍然存在。 IE。有 6 个 db 交互,如果最后一个发生故障,则之前的 4 个全部回滚,但第一个 Recpt Header 没有。
  • 在我看来,如果你创建一个新的上下文,你当前开始一个TransactionScope,你根本不需要后者。删除多个退出点 (return),最后只调用一次SaveChanges 并捕获异常。
  • 所以对于 EF,上下文本质上就是事务,因此您可以进行所有上下文条目修改,例如 _dataContext.Entry(entity).State = System.Data.EntityState.Added;然后如果都成功调用 SaveChanges(),如果没有,你调用 dbContext.Dispose();然后不会提交任何更改?我将不得不修改我的存储库类以接受 bool 以确定是否要保存更改,对于这些,直到最后才保存更改......对不起,大声思考。
  • 没错。 SaveChanges() 提交更改并管理自己的事务。它会保存所有内容,或者什么都不保存。

标签: c#-4.0 asp.net-mvc-4 entity-framework-5


【解决方案1】:

您不需要TransactionScope。只需在当前启动TransactionScope 的位置创建一个新上下文。为了使这项工作顺利进行,您需要删除多个退出点 (return),并在最后调用一次 SaveChanges() 并捕获异常。这也将清理您的代码并使其更易于维护(多个退出点被视为反模式)。

只有SaveChanges() 提交对数据库的更改,没有其他内容。 SaveChanges() 管理自己的事务:它保存全部,或者什么都不保存。

【讨论】:

  • @Chad Richardson :如果需要将上下文保持在业务级别并将其用于多个事务并根据用户描述回滚或提交它们并保持相同的上下文并继续使用前查询(可能将它们用作组合的datasource...)并在新上下文中使用(如修改)它们并保存它们而不会出错?谢谢。
  • @Mohsen 从不建议使用长寿命的上下文。将其用作工作单元。
【解决方案2】:

从 .Net 4 开始,如果所有 Db 更改都在一次调用 SaveChanges() 时触发,那么实体框架将决定它们实际执行的顺序。

一般的执行顺序是DELETE、INSERT,最后是UPDATE。

如果您的插入顺序无关紧要,例如没有外键类型约束,那么单个 SaveChanges() 就可以正常工作。

如果插入的顺序很重要,则无法强制 EF 更改语句的执行顺序。

我建议的第一个解决方案是多次调用 SaveChanges(),每次调用 SaveChanges() 后都会使上下文忘记它的更改,因此无法回滚。

我建议的第二个解决方案是提供回滚功能是使用 TransactionScope 并使用 SaveChanges(false) (false 使对象上下文记住它的更改,从而使回滚成为可能)或 savechanges() 与保存选项(新方法这样做)。

例如

var scope = new TransactionScope(
    TransactionScopeOption.RequiresNew,
    // we will allow volatile data to be read during transaction
    new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }
);

using (scope)
{
  // Create new contexts for each operation
  Entities myEntities = new Entities();
  Entities myEntities2 = new Entities();

  // Do stuff with the contexts

  // Insert into myEntities then call myEntities.SaveChanges(false);
  // Insert into myEntities2 then call myEntities.SaveChanges(false);

  scope.Complete();
  myEntities.Context.AcceptAllChanges();
  myEntities2.Context.AcceptAllChanges();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    相关资源
    最近更新 更多