In my EF4 program, I have an Applicant and Application table. Multiple instances of the program runs periodically to create application for applicants based on some business logic. In Application table I cannot have more than one Submitted/BeingSubmitted records for an Applicant.
So here’s the piece of code which checks if there’s an Submitted/BeingSubmitted application and inserts it. It is run inside a foreach loop for a list of Applicants.
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;
}
}
Above piece of code prevents insertion of duplicate records apart from the fact that it throws UpdateException while running multiple instances of the program. If I swallow that exception and carry on then everything is fine.
However, I tried to test/run above code in parallel but it inserts duplicate records in database.
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);
}
}
In above piece of code, it throws UpdateException and inserts multiple Application row for an Applicant.
Note that, the table only has a surrogate primary key and no other unique constraints.
My question is: Why the TransactionScope inserts duplicate rows by running it in Parallel.Invoke but not when I fire off multiple instances of the program? What would be a sound approach achieving it?
Update: The ctor of SqlUnitOfWork is
public SqlUnitOfWork()
{
_context = new MyEntities();
}
The ctor of MyEntities is generated by EF –
public const string ConnectionString = "name=Entities";
public const string ContainerName = "Entities";
public TPIEntities() : base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
Thanks.
This is a classic race condition. Both transactions check to see if a pre-existing
Applicationexists. Both find that there is none. Then, both try to insert.You need to synchronize your checking, either by locking in the application or by taking a SQL Server
UPDLOCK, HOLDLOCKwhen checking whether theApplicationexists.