1

在我的 EF4 程序中,我有一个申请人和申请表。该程序的多个实例会定期运行,以根据某些业务逻辑为申请人创建应用程序。在申请表中,我不能为申请人拥有多个已提交/正在提交的记录。

所以这是一段代码,它检查是否有一个已提交/正在提交的应用程序并插入它。它在申请者列表的 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;
    }

谢谢。

4

1 回答 1

0

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

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

于 2012-07-12T10:31:03.317 回答