EF5 cannot handle concurrency when updating select fields

I use EF5 and Data First to update objects. I use the approach suggested by other questions to conditionally update only modified properties in Entities.

Oki, so here the scenario My controller calls a service with POCO objects and receives POCO objects from the Service. The service layer interacts with the data layer that EF5 internally uses to retrieve an object from the database and update them in the database.

View data is loaded by the controller from the DTO object obtained from the service level. The user changes the view and returns the JSON data to the controller, which maps to the DTO object in the controller (courtesy of MVC). The controller makes a service level call with a DTO (POCO) object. The service maps the POCO object to the object of the EF object and calls the data layer update method (for example, the repository) passed in the EF element. In the repository, I retrieve an existing object from the database and call the ApplyCurrentvaluesValues ​​method, then check to see if any properties are changed. If the properties are changed, I apply my custom logic to other objects that are not associated with the current entity, and also update the "Updated AdminId" and "UpdationDate" of the current object. Put this, I call the SaveChanges method in Centext.

Everything that I mentioned above works fine, except when I insert a break point in the call to "SaveChanges" and update some field changed by the user to another value, then "DbUpdateConcurrencyException" does not throw EF5. that is, I can get a conditional update and run my own logic when the properties of my interest are changed to work perfectly. But I do not get an error in case of concurrency ie EF does not raise a "DbUpdateConcurrencyException" in case the record is updated between me, taking the record from the database, updating the record and saving it.

In the real scenario, a stand-alone cron runs, which checks the campaign just created and creates a portfolio for them, and marks the IsPortfolioCreated property below as true, while the user can edit the campaign and the flag can be set to false even though cron created the portfolios.

To replicate the concurrency script, I set a breakpoint on SaveChanges and then updated the IsPortfolioCreated feild function from the MS-Sql corporate manager for the same object, but the “DbUpdateConcurrencyException” was not thrown, even though the Data in Store was updated.

Here is 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; } 
+5
source share
1 answer

The Entity framework does nothing special about concurrency until you (as a developer) configure it to check for concurrency problems.

You are trying to catch a DbUpdateConcurrencyException, the documentation for this exception says: "The exception caused by DbContext when it was expected that SaveChanges for the object will update the database , but in fact no rows in the database were affected .", You can read it here

In the first approach to the database, you must set the "Concurrency Mode" property for the "Fixed" column (the default is "No"). Take a look at this screenshot: enter image description here

The column version is the SQL SERVER TIMESTAMP type, a special type that is automatically updated every time a row changes, read about it here .

In this configuration, you can try with this simple test if everything works 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 above example, the update from the internal context will be fixed, the update from the external context will throw a DbUpdateConcurrencyException, because EF will try to update the object using 2 columns as filters: Id column and version.

Hope this helps!

+2
source

Source: https://habr.com/ru/post/1245606/


All Articles