Reputation: 3523
I am using mehdime/DbContextScope as documented here to implemeent a bulk user refresh/import solution for our product.
Under the heading "Changes are only persisted when SaveChanges() is called", Mehdi states in code sample:
Do not call SaveChanges() until the business transaction is complete - i.e. no partial or intermediate saves. SaveChanges() must be called exactly once per business transaction.
If you find yourself needing to call SaveChanges() multiple times within a business transaction, it means that you are in fact implementing multiple business transactions within a single service method.
However, I want to add a new row, obtain its generated ID (IDENTITY column) and then add an audit log entry to the database referencing the generated ID. To do this, I need to perform an intermediate SaveChanges()
in order for EF to perform the INSERT and get an ID, however I want to do this in a single atomic transaction.
Am I missing something or is this actually possible without breaking the Medhi's rule?
Here is my code sample (which current breaks the rule)
/// <summary>
/// Creates a new Location
/// </summary>
/// <param name="orgID">ID of Organisation</param>
/// <param name="name">Location Name</param>
/// <returns></returns>
public IActionResult Create(int orgID, string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
// New Location
var location = new Location {
OrganisationID = orgID,
Name = name,
Enabled = true
};
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var ctx = dbContextScope.DbContexts.Get<PlatformEntities2014>();
// Duplicate name?
var existing = ctx.Locations.Where(l => l.OrganisationID == orgID && l.Name == name).FirstOrDefault();
if (existing != null)
return new ActionResult(ActionResultCode.ErrorAlreadyExists) { ReferencedObject = existing };
ctx.Locations.Add(location);
// ---POSITION A---
dbContextScope.SaveChanges(); // (Assigns location.ID)
// Log
GeneralLog log = new GeneralLog() {
DateTime = DateTime.Now,
Code = "LOC",
SubCode = "NEW",
OrganisationID = orgID,
Information = $"Location {location.ID} \"{location.Name}\" created during Bulk Refresh."
};
ctx.GeneralLogs.Add(log);
// ---POSITION B---
dbContextScope.SaveChanges();
return new ActionResult(ActionResultCode.Success) { ReferencedObject = location };
}
}
Would it be acceptable to replace the first dbContextScope.SaveChanges();
at POSITION A with ctx.SaveChanges();
to obtain the ID and call dbContextScope.SaveChanges();
at the POSITION B?
Thanks.
Upvotes: 1
Views: 855
Reputation: 1861
Further on from your explanation of what the relationships are in comments. these are 2 separate business transactions. Look at it this way your transaction is not considered as failed if it fails to create a 'GeneralLog'. Hence I would say do it as 2 separate transactions. Logging should not fail your business transaction. What you can do is create the objects first in one transaction. The get the IDs and create logs in a separate transaction.
Upvotes: 2