实体框架Transactionscope - 不回滚第一个事务

时间:2013-01-24 18:51:26

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

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

我“大部分”都在工作。我的意思是,如果我在这6个db交互中的任何一个中得到错误,则先前的回滚发生...除了第一个。第一个是对Header表的插入...后续插入到详细信息表甚至是另一个头表等等...所有回滚...但是如果在回滚后检查表,则所有表都没有记录,除了第一个。

//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);
            }
        }

2 个答案:

答案 0 :(得分:1)

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

只有SaveChanges(),而不提供其他内容,提交对数据库的更改。 SaveChanges()管理自己的交易:它会保存全部或全部保存。

答案 1 :(得分:1)

自.Net 4以来,如果在对SaveChanges()的单次调用中触发了所有Db更改,则实体框架将决定它们实际执行的顺序。

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

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

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

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

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

e.g。

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();
}