public async Task<JobViewModel> Handle(EditJobCommand command, CancellationToken cancellationToken)
{
if (command.JobViewModel == null) throw new System.InvalidOperationException("Empty request.");
var jobViewModel = command.JobViewModel;
try
{
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
job.Id = command.Id;
OnBeforeAttach(job);
_context.Set<DataAccess.Domain.Lab.Job>().Attach(job);
//_context.MarkModified(job);
await OnBeforeSave(job);
await _context.SaveChangesAsync();
return jobViewModel;
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine(ex.Message);
throw;
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw e;
}
catch (DbUpdateException e)
{
Console.WriteLine(e.Message);
throw;
}
}
private async Task OnBeforeSave(Job job)
{
var entry = _context.Entry(job);
entry.State = EntityState.Unchanged;
entry.Property(j => j.Name).IsModified = true;
entry.Property(j => j.Description).IsModified = true;
entry.Property(j => j.SAPCode).IsModified = true;
entry.Property(j => j.InitiatedOn).IsModified = true;
entry.Property(j => j.EndDate).IsModified = true;
entry.Property(j => j.ProjectId).IsModified = true;
entry.Property(j => j.TimeStamp).IsModified = true;
if (job.IsNew)
{
entry.State = EntityState.Added;
}
else
{
entry.State = EntityState.Modified;
entry.Property(e => e.CreatedBy).IsModified = false;
entry.Property(e => e.CreatedOn).IsModified = false;
}
}
}
I want to use await for below line
var entry = _context.Entry(job); but using await gives below error DbEntityEntry does not contain definition for getawaiter
Aslo ,please not i want to use await as calling this method onBeforesave () is giving me error of optimistic concurrency.
Optimistic concurrency errors implies that your code is using something like a RowVersion Timestamp column for cuncurrency checks. These are used to help guard against "last in wins" data overwrites.
Take the scenario where Bob and Jane both running queries and happen to pick the same record to be updated. Bob makes a change to one field and Saves, then Jane makes a change to a second field and Saves. In your model you are taking all fields from a serialized copy at the time of their Read with their specific edits, attaching that as an entity, setting the fields to modified, and attempting to save the changes. Without optimistic concurrency, Jane's changes would be saved, while Bob's changes would be lost, overwritten by Jane's stale data, completely silently. With a RowVersion, the original record would have a RowVersion stamp of "AAAAAAA", when Bob saves his changes, his copy of the RowVersion would be "AAAAAA" which is checked by EF against the current database row. They match, so the update goes through and the row version is updated to "BBBBBB". Now when Jane goes to save her changes, her RowVersion value is still "AAAAA". When EF checks that against the DB row now "BBBBBB" Jane's operation fails due to the concurrency issue detected. Jane's data is stale.
Optimistic concurrency is a safeguard to ensure these types of problems don't happen silently. It is up to the developers as to what the system should do in these scenarios. Such as, Log the fact that it happened and allow the overwrite; Attempt to automatically merge the change; Reject the change and give Jane the current data state to review and apply her change again; etc.
This comes down to how you are performing your update. Whether optimistic concurrency or not, it is not a good idea to deserialize data from a client into an entity, attach it to the DbContext and call SaveChanges. The good thing is that your code is being pessimistic with the fields that it is marking as IsModified rather than setting the entire EntityState to Modified, but that is ultimately a lot of boilerplate code to write for every update. It also means your UPDATE statements will include every column nominated whether those values changed or not.
A better way to handle the Update, and concurrency:
(Add in appropriate exception handling, I trimmed out to show the relevant bits)
public async Task<JobViewModel> Handle(EditJobCommand command, CancellationToken cancellationToken)
{
if (command.JobViewModel == null) throw new System.InvalidOperationException("Empty request.");
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
var job = await _context.Jobs.SingleAsync(x => x.Id == command.JobViewModel.Id);
if(job.RowVersion != command.JobViewModel.RowVersion)
{ // Concurrency issue! Handle here.
}
_mapper.Map(command.JobViewModel, job);
await _context.SaveChangesAsync();
}
The key differences here in approach:
Fetch the job asynchronously from the DB. This ensures we are inspecting and updating the current state of the record. This also ensures that our Edit command actually has a valid Job ID. From here we can also validate that the Job is in an editable state (I.e. not Inactive) and can assert that the current user has permissions to edit that Job etc.
Check the RowVersion. This might be a different column name, but essentially our concurrency marker which needs to be passed to the View, and returned in our Edit ViewModel. If these values match, the data hasn't changed so we can continue with the edit. If they don't match you can handle that whether to refresh the client view and alert them that data has possibly changed, or allow the overwrite after logging the concurrency issue.
Leverage Automapper to copy allowed values across from the ViewModel to the Entity. In your mapping for VM -> Entity you can configure what fields can be copied across then call Mapper.Map(src, destination). Aside from being a single line of code, the benefit of using the Change Tracking on the entity is that the resulting UPDATE statement will only include fields that actually change (and only if anything actually changed).
Related
I have a webapi and I want to make my logic inside this controller thread safe.
I want user can only update payroll when the last one updated and two update at the same time should not be happend.
As you can see in the code, I added a column in Payroll entity with the name of IsLock as boolean and try to handle multiple request for update in this way but it is not thread-safe.
How can I make it thread-safe ?
[HttpPut("{year}/{month}")]
public async Task<NoContentResult> Approve([FromRoute] int year, [FromRoute] int month)
{
var payroll = _dataContext.Payrolls
.SingleOrDefaultAsync(p =>
p.Month == month && p.Year == year);
if (payroll.IsLock)
{
throw new ValidationException(
$"The payroll {payroll.Id} is locked.");
}
try
{
payroll.IsLock = true;
_dataContext.Payrolls.Update(payroll);
await _dataContext.SaveChangesAsync(cancellationToken);
payroll.Status = PayrollStatus.Approved;
_dataContext.Payrolls.Update(payroll);
await _dataContext.SaveChangesAsync(cancellationToken);
payroll.IsLock = false;
_dataContext.Payrolls.Update(payroll);
await _dataContext.SaveChangesAsync(cancellationToken);
return NoContent();
}
catch (Exception)
{
payroll.IsLock = false;
_dataContext.Payrolls.Update(payroll);
await _dataContext.SaveChangesAsync(cancellationToken);
throw;
}
}
You are looking for Concurrency Tokens. Each row in the payroll table would have one. When a user loaded the edit interface for a payroll, the concurrency token would be sent to the client. The client would include the concurrency token in the request to update the payroll. The update would only succeed of the concurrency token had not changed - meaning that the data had not changed since the user fetched it to start the edit.
Entity Framework uses the concurrency tokens internally, as well, so it won't save changes from a stale entity (where the data has changed since it was loaded).
The current IsLocked solution has some flaws. If two API requests are received at the same time, both may read the payroll data and see that it isn't locked. Both requests would then lock the row, make competing changes, and release the lock without ever realizing there were simultaneous edits.
I have some code, you can check project github, error contains in UploadContoller method GetExtensionId.
Database diagram:
Code (in this controller I sending files to upload):
[HttpPost]
public ActionResult UploadFiles(HttpPostedFileBase[] files, int? folderid, string description)
{
foreach (HttpPostedFileBase file in files)
{
if (file != null)
{
string fileName = Path.GetFileNameWithoutExtension(file.FileName);
string fileExt = Path.GetExtension(file.FileName)?.Remove(0, 1);
int? extensionid = GetExtensionId(fileExt);
if (CheckFileExist(fileName, fileExt, folderid))
{
fileName = fileName + $" ({DateTime.Now.ToString("dd-MM-yy HH:mm:ss")})";
}
File dbFile = new File();
dbFile.folderid = folderid;
dbFile.displayname = fileName;
dbFile.file_extensionid = extensionid;
dbFile.file_content = GetFileBytes(file);
dbFile.description = description;
db.Files.Add(dbFile);
}
}
db.SaveChanges();
return RedirectToAction("Partial_UnknownErrorToast", "Toast");
}
I want to create Extension in database if it not exist yet. And I do it with GetExtensionId:
private static object locker = new object();
private int? GetExtensionId(string name)
{
int? result = null;
lock (locker)
{
var extItem = db.FileExtensions.FirstOrDefault(m => m.displayname == name);
if (extItem != null) return extItem.file_extensionid;
var fileExtension = new FileExtension()
{
displayname = name
};
db.FileExtensions.Add(fileExtension);
db.SaveChanges();
result = fileExtension.file_extensionid;
}
return result;
}
In the SQL Server database I have unique constraint on displayname column of FileExtension.
Problem starts only if I uploading few files with the same extension and this extension not exist in database yet.
If I remove lock, in GetExtensionId will be Exception about unique constraint.
Maybe, for some reason, next iteration of foreach cycle calls GetExtensionId without waiting? I don't know.
But only if I set lock my code works fine.
If you know why it happens please explain.
This sounds like a simple concurrency race condition. Imagine two requests come in at once; they both check the FirstOrDefault, which correctly says "nope" for both. Then they both try and insert; one wins, one fails because the DB has changed. While EF manages transactions around SaveChanges, that transaction doesn't start from when you query the data initially
The lock appears to work, by preventing them getting into the looking code at the same time, but this is not a reliable solution for this in general, as it only works inside a single process, let alone node.
So: a few option here:
your code could detect the foreign key violation exception and recheck from the start (FirstOrDefault etc), which keeps things simple in the success case (which is going to be the majority of the time) and not horribly expensive in the failure case (just an exception and an extra DB hit) - pragmatic enough
you could move the "select if exists, insert if it doesn't" into a single operation inside the database inside a transaction (ideally serializable isolation level, and/or using the UPDLOCK hint) - this requires writing TSQL yourself, rather than relying on EF, but minimises round trips and avoids writing "detect failure and compensate" code
you could perform the selects and possible inserts inside a transaction via EF - complicated and messy, frankly: don't do this (and it would again need to be serializable isolation level, but now the serializable transaction spans multiple round trips, which can start to impact locking, if at scale)
I'm sending an email from an MVC controller.
[HttpPost]
public async Task<ActionResult> Send(SendModel model)
{
var userId = HttpContext.User.Identity.GetUserId();
// This method takes current user ID and retrieves the user from the DB
var thisUser = await GetThisApplicationUserAsync();
if (thisUser.FeedbackSendLimit <= 0) return RedirectToActionWithMessage(MessageType.Error, "You can't send emails anymore! You have exceeded your send limit!", "Send");
// Handling the case when the passed model is invalid
if (!ModelState.IsValid) return View(model);
using (var transaction = _dbContext.Database.BeginTransaction())
{
// _dbContext is a DbContext initialized in the controller's constructor
var companiesToSend = _dbContext
.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
try
{
// Each user has a limit of emails they can send monthly
thisUser.FeedbackSendLimit -= 1;
// Each company has a limit of how many emails we can address them as well
foreach (var company in companiesToSend)
{
company.FeedbackCounter -= 1;
}
var newSend = new FeedbackSend
{
Id = Guid.NewGuid(),
UserId = userId,
SentAt = DateTime.Now,
Companies = companiesToSend
};
_dbContext.FeedbackSends.Add(newSend);
await _dbContext.SaveChangesAsync();
// This generates an HTML email and sends it to specified email address
await SendFeedbackEmailAsync(model.ToEmail, thisUser, companiesToSend);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return RedirectToActionWithMessage(MessageType.Error, "An error occurred while trying to send feedback", "Send");
}
}
return RedirectToActionWithMessage(MessageType.Success, "Sent successfully", "Send");
}
Here are two questions:
1. Do I really need a transaction here? Wouldn't using _dbContext.SaveChanges() be enough in this case? I used a transaction to revert everything back in case SendFeedbackEmailAsync failed and no email sent.
transaction.Commit() doesn't seem to be updating thisUser.FeedbackSendLimit. Should I retrieve the user in the transaction using block to get it working?
Technologies:
Entity Framework 6.0
ASP.NET MVC 5
Do you need the explicit transaction: No. If your Try block completes and calls SaveChanges, the changes will be committed as one effective transaction. If the exception gets caught, no SaveChanges happens so the transaction is rolled back when the Context is disposed.
Why your User change isn't saved? This is most likely because your user was loaded by a different DbContext instance in the GetThisApplicationUserAsync() method, or was loaded AsNoTracking() or otherwise detached.
When retrieving data and performing updates, do it within the scope of a single DbContext instance.
using (var context = new MyDbContext())
{
var thisUser = context.Users.Where(x => x.Id == userId).Single();
var companiesToSend = context.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
//....
From there when the Context SaveChanges is called, that user is tracked by the Context and will be persisted. The uglier way to deal with it is to check if the thisUser is tracked by the context (no) or a user with the same PK is tracked by the context (no, if a new DbContext) and if not, Attach the user to that context, however the user instance needs to be first detached from any DbContext instance it may still be attached to. Messy.
I'm not a fan of initializng a module-level DbContext but rather ensuring instances are instantiated and disposed in the scope they are needed. Module level contexts make it harder to predict method chains where changes may be inadvertently saved when some method decides to call SaveChanges, and leads to odd placement of explicit transactions and such to try and normalize behaviour. Using blocks make that a lot easier. If you want to DI a context then I recommend considering either a Repository pattern and/or a DbContextFactory/UnitOfWork dependency to enable mocking the results (Repository) or mocking the DbContext (Factory).
My go-to pattern is Repository (non-generic) /w the DbContextScopeFactory/Locator pattern for Unit of Work by Mehdime.
I am using EF5 and Data First approach to Update entities.
I am using approach suggested by other questions to conditionally update only modified properties in the Entities.
Oki so here's the scenario My controller call Service with POCO objects and gets POCO objects from Service, The Service layer talks with Data layer which internally uses EF5 to retrieve entity from DB and Update them in DB.
The View data is loaded by controller from DTO object retrieved from Service layer.
User makes changes to View and Posts back JSON data to controller which gets mapped to DTO object in controller (courtesy MVC).
The controller makes call to Service layer with the DTO object (POCO) object.
The Service maps the POCO object to EF entity object and calls the Data layer's(i.e Repository) Update method passing in the EF entity.
In the Repository I fetch the existing entity from DB and call ApplyCurrentvaluesValues method, then I check if any properties are modified .
If properties are modified then I apply my custom logic to other entities which are not related to current entity and also Update the "UpdatedAdminId" & "UpdationDate" of current entity.
Post this I call "SaveChanges" method on Centext.
Every thing above I mentioned is working fine , except if I insert a break point in "SaveChanges" call and update some field modified by User to different value then "DbUpdateConcurrencyException" is not thrown by EF5.
i.e. I can get conditional Update & fire my custom logic when properties of my interest are modified to work perfectly.
But I am not getting error in case of the concurrency i.e the EF is not raising "DbUpdateConcurrencyException" in case a record is updated in between me fetching the record from DB , updating the record and saving it.
In real scenario there is a offline cron running which checks for newly created campaign and creates portfolio for them and marks the IsPortfolioCreated property below as true, in the mean time user can edit the campaign and the flag can be set to false even though the cron has created the portfolios.
To replicate the concurrency scenario I put a break point on SaveChanges and then Update the IsPortfolioCreated feild from MS-Sql enterprise manager for the same entity, but the "DbUpdateConcurrencyException" is not thrown even though the Data in Store has been updated.
Here's my code for reference,
Public bool EditGeneralSettings(CampaignDefinition campaignDefinition)
{
var success = false;
//campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client
var updatedAdminId = campaignDefinition.UpdatedAdminId;
var updationDate = DateTime.UtcNow;
CmsContext context = null;
GlobalMasterContext globalMasterContext = null;
try
{
context = new CmsContext(SaveTimeout);
var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();
//Always use this fields from Server, no matter what comes from client
campaignDefinition.CreationDate = contextCampaign.CreationDate;
campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;
campaignDefinition.UpdationDate = contextCampaign.UpdationDate;
campaignDefinition.AdminId = contextCampaign.AdminId;
campaignDefinition.AutoDecision = contextCampaign.AutoDecision;
campaignDefinition.CampaignCode = contextCampaign.CampaignCode;
campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;
var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName;
// Will be used in the below if condition....
var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();
var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();
//This also not firing concurreny exception....
var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition);
((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign);
var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);
var modifiedProperties = entry.GetModifiedProperties();
//Even tried this , works fine but no Concurrency exception
//var entry = context.Entry(contextCampaign);
//entry.CurrentValues.SetValues(campaignDefinition);
//var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
// If any fields modified then only set Updation fields
if (modifiedProperties.Count() > 0)
{
campaignDefinition.UpdatedAdminId = updatedAdminId;
campaignDefinition.UpdationDate = updationDate;
//entry.CurrentValues.SetValues(campaignDefinition);
updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
//Also perform some custom logic in other entities... Then call save changes
context.SaveChanges();
//If campaign name changed call a SP in different DB..
if (campaignNameChanged)
{
globalMasterContext = new GlobalMasterContext(SaveTimeout);
globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId);
globalMasterContext.SaveChanges();
}
}
success = true;
}
catch (DbUpdateConcurrencyException ex)
{
//Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry
//In short Store Wins Strategy
//Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question...
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
finally
{
if (context != null) context.Dispose();
if (globalMasterContext != null) globalMasterContext.Dispose();
}
return success;
}
Entity framework it's not doing anything special about concurrency until you (as developer) configure it to check for concurrency problems.
You are trying to catch DbUpdateConcurrencyException, the documentation for this exception says: "Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected. ", you can read it here
In a database first approach, you have to set the property 'Concurrency Mode' for column on 'Fixed' (the default is None). Look at this screenshot:
The column Version is a SQL SERVER TIMESTAMP type, a special type that is automatically updated every time the row changes, read about it here.
With this configuration, you can try with this simple test if all is working as expected:
try
{
using (var outerContext = new testEntities())
{
var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
outerCust1.Description += "modified by outer context";
using (var innerContext = new testEntities())
{
var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
innerCust1.Description += "modified by inner context";
innerContext.SaveChanges();
}
outerContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ext)
{
Console.WriteLine(ext.Message);
}
In the example above the update from the inner context will be committed, the update from the outer context will thrown a DbUpdateConcurrencyException, because EF will try to update the entity using 2 columns as a filters: the Id AND the Version column.
Hope this helps!
I'm having a strange issue. I have a database with existing items, I added a new validation rule so some of the items in the database don't comply with this new rule.
I have a loop that finds a record, changes an element then saves it back to the database, like this:
foreach(int foo in bar)
{
Model model = db.Model.Find(foo);
model.updated = true;
if(ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
}
}
I figured that for records that don't comply to the new validation rule would just not be updated because the ModelState.IsValid wouldn't pass. This is not the case though, it trows a validation failed exception. So I put it in a try catch and figured while I'm at it I'll log the error so I know what records arn't valid. So it now looks like this:
foreach(int foo in bar)
{
Model model = db.Model.Find(foo);
model.updated = true;
try
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
}
catch(Exception x)
{
// log error
if(ModelState.IsValid)
{
db.ErrorLogs.Add(errorLog);
db.SaveChanges();
}
}
}
This also trows a validation failed exception, i guess because the exception in the try is not cleared. So fine, I decide to just try, and not catch the error. So it looks like this:
foreach(int foo in bar)
{
Model model = db.Model.Find(foo);
model.updated = true;
try
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
}
catch()
{
}
}
Now what ends up happening is that say I have 100 records that are cycled through the foreach loop, if #27 fails validation every record that comes after fails and therefore is not updated!
This is very aggravating, how can I fix this? Is there a way to clear the validation error? Why is the error persisting to all the other loops? Is it because db was declared outside the loop? And why is it passing the ModelState.IsValid in the first place?
Thanks
I think you're confusing ASP.NET MVC validation with Entity Framework's validation.
ModelState.IsValid checks the validity of the models that were bound to your MVC Action's parameters.
Entity Framework does its own validation of the entities that it tries to save.
It appears that the aspects of your model which aren't valid are not those which were sent to your action method, but rather those which came out of the database. So when the model binding occurred, you had a valid object, and ModelState.IsValid is true. But when you try to save the objects, Entity Framework still detects that they are invalid and throws an exception.
It seems odd that you would create validation on your entities that causes current data in the database to be incorrect. Consider running a SQL script to correct the data in the database.
If you can't do that, you could try binding the entities after having loaded them out of the database:
var models = db.Model.Where(f => bar.Contains(f.Id)).ToList();
TryUpdateModel(models);
if(ModelState.IsValid)
{
foreach(var model in models)
{
model.updated = true;
db.Entry(model).State = EntityState.Modified;
}
db.SaveChanges();
}
Of course, the above code only works if all of the model are valid. If you want to selectively update only the correct ones, you could check the ModelState.Errors property for errors pertaining to each model entry.
If you really want to suppress entity validation in this particular case, bearing in mind that this could be dangerous, you can simply disable validation on your context before saving.
db.Configuration.ValidateOnSaveEnabled = false;