parallel.invoke事务范围实体框架

时间:2012-07-12 08:56:06

标签: c# .net entity-framework transactionscope

在我的EF4程序中,我有一个申请人和申请表。该程序的多个实例定期运行,以基于某些业务逻辑为申请人创建应用程序。在Application表中,我不能为申请人提供多个Submitted / BeingSubmitted记录。

所以这里是检查是否有Submitted / BeingSubmitted应用程序并插入它的代码片段。它在foreach循环中运行,以获取申请人列表。

public Application SaveApplication(Int32 applicantId)
    {
        using (TransactionScope txScope = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            if (ApplicantHasPendingApplication(applicantId))
                return null;

            Application app = null;
            try
            {
                app = new Application()
                {
                    // Create the object...
                };

                _unitOfWork.DisclosureApplications.Add(app);
                _unitOfWork.Commit();
                _unitOfWork.Refresh(app); // We save and refresh it to get the Id.

                txScope.Complete();
            }
            catch (UpdateException ex)
            {
                // We get an Update exception here when multiple instances tries to insert Application.
            }

            return app;
        }
    }

上面的代码阻止了重复记录的插入,除了它在运行多个程序实例时抛出UpdateException。如果我吞下那个例外并继续下去那么一切都很好。

但是,我尝试并行测试/运行上面的代码,但它在数据库中插入重复的记录。

Parallel.Invoke(
            () => CreateApplications("Parallel Instance 1"),
            () => CreateApplications("Parallel Instance 2"));

private void CreateApplications(String dummyInstanceName)
{
   var unitOfWork = new SqlUnitOfWork();
   var applicants = unitOfWork.Applicants.FindAll().Take(100).ToList();

   var facade = new ProviderFacade(unitOfWork, new Log4NetLogger(dummyInstanceName));

   foreach (Applicant applicant in applicants)
            {
                facade.ApplicationProvider.SaveApplication(applicant.applicantID);
            }
}

在上面的代码中,它抛出UpdateException并为申请人插入多个Application行。

请注意,该表只有一个代理主键,没有其他唯一约束。

我的问题是:为什么TransactionScope通过在Parallel.Invoke中运行它来插入重复的行,但是当我触发多个程序实例时却没有?实现它的合理方法是什么?

更新:SqlUnitOfWork的ctor是

public SqlUnitOfWork()
    {
        _context = new MyEntities();
    }

MyEntities的ctor由EF生成 -

    public const string ConnectionString = "name=Entities";
    public const string ContainerName = "Entities";

    public TPIEntities() : base(ConnectionString, ContainerName)
    {
        this.ContextOptions.LazyLoadingEnabled = true;
    }

感谢。

1 个答案:

答案 0 :(得分:0)

这是一个经典的竞争条件。两个事务都检查是否存在预先存在的Application。两者都发现没有。然后,两者都试图插入。

您需要通过锁定应用程序或在检查UPDLOCK, HOLDLOCK是否存在时使用SQL Server Application来同步您的检查。