2

我正在尝试创建一个封装 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);
            }
        }
4

2 回答 2

1

You don't need the TransactionScope. Just create a new context where you currently start the TransactionScope. To make this work well, you need to remove the multiple exit points (return) and just call SaveChanges() at the end once and catch exceptions. That will also clean up your code and make it better maintainable (multiple exit point are considered an anti pattern).

Only SaveChanges(), and nothing else, commits changes to the database. SaveChanges() manages its own transaction: it saves all, or nothing.

于 2013-01-25T19:40:59.537 回答
1

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

它的一般执行顺序是删除、插入和最后更新。

如果您的插入顺序无关紧要,例如没有外键类型约束,那么单个 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();
}
于 2013-02-12T16:32:56.973 回答