Solving Optimistic Concurrency Update problem with Entity Framework - c#

How should I solve simulation update, when one user updates already updated entery by another user?
First user request 'Category' entityobject, second user does the same.
Second user updates this object and first user updates.
I have field timestamp field in database that wa set to Concurrency Mode - Fixed.
This is how I update:
public class CategoriesRepository : BaseCategoryRepository
{
public void Update(....)
{
try
{
Category catToUpdate = (from c in Contentctx.Categories where c.CategoryID == categoryId select c).FirstOrDefault();
catToUpdate.SectionReference.EntityKey = new System.Data.EntityKey("ContentModel.Sections", "SectionID", sectionId);
if (catToUpdate != null)
{
//Set fields here....
Contentctx.SaveChanges();
}
base.PurgeCacheItems(CacheKey);
}
catch (OptimisticConcurrencyException ex)
{
}
}
}
//Contentctx comes from base class: Contentctx:
private ContentModel _contenttx;
public ContentModel Contentctx
{
get
{
if ((_contenttx == null))
{
_contenttx = new ContentModel();
}
return _contenttx;
}
set { _contenttx = value; }
}
//on Business layer:
using (CategoriesRepository categoriesRepository = new CategoriesRepository())
{
categoriesRepository.UpdateCategory(.....);
}
Exception never jumps...
How should I handle this?

Are you sure that sequence of calls is performed as you described? Anyway the main problem here is that you should not use the timestamp returned by the query in Upate method. You should use timestamp received when user get initial data for update. It should be:
User requests data from update - all fields and timestamp are received from DB
Timestamp is stored on client (hidden field in web app)
User modifies data and pushes Save button - all data including old timestamp are send to processin
Update method loads current entity
All updated fields are merged with old entity. Timestamp of entity is set to timestamp received in the first step.
SaveChanges is called
The reason for this is that it can pass a lot of time between first call and update method call so there can be several changes already processed. You have to use initial timestamp to avoid silent overwritting other changes.
Edit:
// You are updating category - update probably means that you had to load category
// first from database to show actual values. When you loaded the category it had some
// timestamp value. If you don't use that value, any change between that load and c
// calling this method will be silently overwritten.
public void Update(Category category)
{
try
{
Category catToUpdate = (from c in Contentctx.Categories where c.CategoryID == categoryId select c).FirstOrDefault();
...
// Now categoryToUpdate contains actual timestamp. But it is timestamp of
// actual values loaded now. So if you use this timestamp to deal with
// concurrency, it will only fire exception if somebody modifies data
// before you call SaveChanges few lines of code bellow.
if (catToUpdate != null)
{
//Set fields here....
// To ensure concurrency check from your initial load, you must
// use the initial timestamp.
catToUpdate.Timestamp = category.Timestamp;
Contentctx.SaveChanges();
}
...
}
catch (OptimisticConcurrencyException ex)
{
...
}
}

Related

ASP.NET MVC Updating Master-Detail Records in Single HTTP Post Request

I'm teaching myself C# and MVC but have a background in SQL. When updating an existing master-detail set of records in a single action (let's say for instance a customer order and order details), updating the master record is no problem. Regarding the detail records, I'm seeing examples that simply delete all existing details and then add them back in rather than add, delete or update only what's changed. That seems easy and effective but involves unnecessary changes to database records and might be an issue in complex relationships.
I've tried writing code that checks the existing values against posted values to determine the right EntityState (Added, Deleted, Modified, Unchanged) for each detail. Accomplishing this using LINQ Except and Intersect works but seems to cause an unexpected performance hit.
(Instead, I could load the original values in an "oldValue" hidden field in the original GET request to compare to the POST values except that would be unreliable in a multi-user environment and seems like a bad idea.)
I'll be happy to provide code examples, but my question is more about best practices. Is there a preferred method for updating existing master-detail sets of records?
EDIT: I've added the code below in response to questions. In this example, our application allows additional attributes to be attached to a product, kept in a separate table ProductAttributes. The view allows the user to edit both the product and the attributes on the same webpage and save at the same time. The code works fine but seems slow and lags at SaveChanges.
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
// Establish entity states for product attributes.
List<ProductAttribute> existingAttributes = new List<ProductAttribute>();
existingAttributes = db.ProductAttributes.AsNoTracking()
.Where(x => x.Sku == product.Sku).ToList();
// Review each attribute that DID NOT previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Except(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value didn't exist and still doesn't.
db.Entry(pa).State = EntityState.Unchanged;
}
else
{
// New value exists that didn't before.
db.Entry(pa).State = EntityState.Added;
}
}
// Review each attribute that DID previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Intersect(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value existed and has been cleared.
db.Entry(pa).State = EntityState.Deleted;
}
else
{
if (pa.Value != existingAttributes
.FirstOrDefault(x => x.Attribute == pa.Attribute).Value)
{
// Value has been altered.
db.Entry(pa).State = EntityState.Modified;
}
else
{
// Value has not been altered.
db.Entry(pa).State = EntityState.Unchanged;
}
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = product.ProductId });
}
return View(product);
}
internal class ProductAttributeComparer : IEqualityComparer<ProductAttribute>
{
public bool Equals(ProductAttribute x, ProductAttribute y)
{
if (string.Equals(x.Attribute, y.Attribute,
StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
public int GetHashCode(ProductAttribute obj)
{
return obj.Attribute.GetHashCode();
}
}
}

Best way to update data after an API call gets a record

I am currently working on an API where a record should only be allowed to be pulled once. It's basically a queue where once a client pulls the record, the Retrieved field on the record is marked true. The Get calls only pull records where the Retrieved field is false.
Controller:
[HttpGet]
public virtual IActionResult GetAll([FromQuery] int? limit)
{
try
{
return Ok(_repository.Get(limit));
}
catch
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
Repository:
public IQueryable<Report> Get(int? limit)
{
IQueryable<Report> reports;
if (limit == null)
{
reports = _context.Reports.Where(r => r.Retrieved == false);
}
else
{
reports = _context.Reports.Where(r => r.Retrieved == false).Take((int)limit);
}
return reports;
}
What would be the best way to modify the records that have been pulled by the Get call? If I do the modification before returning results from the repository code, then when the controller actually converts the IQueryable to real data, the field has changed and it won't pull any results, but the Controller seems like the wrong place to be doing this sort of modification to the database.
I would split this functionality away from the retrieval. Let the caller/client indicate that the report has been successfully retrieved and read with a second call. It is a little more overhead but it adds resilience. Example: if there is a failure in the retrieval after the server call (maybe in the network on browser or client app) then the client has another opportunity to retrieve the data.
Controller:
[HttpPut]
public virtual async Task<IActionResult> MarkAsRetrieved(IEnumerable<int> reportIds, CancellationToken token)
{
await _repository.MarkRetrievedAsync(reportIds, token).ConfigureAwait(true);
return Ok();
}
Repository:
public Task MarkRetrievedAsync([FromBody]IEnumerable<int> reportIds, CancellationToken token)
{
foreach (Report report in reportIds.Select(x => new Report{ReportId = x, Retrieved = false}))
{
_context.Reports.Attach(report);
report.Retrieved = true;
}
return _context.SaveChangesAsync(token);
}
Notes
It is only necessary to send over the identifier for a Report instance. You can then attach an empty instance with that same identifier and update the Retrieved property to true, just that will be sent in the corresponding store update statement.
I would changed the Retrieved bit in the database to a handle of some kind-- Guid or record id to another table that records the fetch, or some other unique value. Then I would determine the handle, update the records I am about to fetch with that that handle, then retrieve the records that match that handle. At any point if you fail, you can set the retrieved handle back to NULL for the handle value you started.

EF5 can not handle Concurrency when Updating selective fields

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!

Update database via Linq to SQL not working

I'm developing a C# ASP.NET application, in which i'm retrieving some data from the database, throwing in a form, and when i click on Save, i want it to save my changes in the database.
I'm using Linq to SQL. The code below, at the end, call the method ClienteBusiness.SalvarAlteracoes(cliente), which by the way, only calls the ClienteData.SalvarAlteracoes(cliente) method.
protected void Salvar()
{
TB_CLIENTE_CLI cliente = new TB_CLIENTE_CLI();
int idEstado = 0;
int idCidade = 0;
if (!Int32.TryParse(ddlEstado.SelectedValue, out idEstado))
{
return;
}
if (!Int32.TryParse(Request.Form[ddlCidade.UniqueID], out idCidade))
{
return;
}
cliente.TXT_RAZAOSOCIAL_CLI = txtRazaoSocial.Text;
cliente.TXT_NOMEFANTASIA_CLI = txtNomeFantasia.Text;
cliente.TXT_CNPJ_CLI = txtCNPJ.Text;
cliente.TXT_CEP_CLI = txtCEP.Text;
/*e os demais campos*/
//Se a tela for de edição, altera o valor do ID para o cliente correspondente.
cliente.ID_CLIENTE_CLI = this.IdCliente;
ClienteBusiness.SalvarAlteracoes(cliente);
HTMLHelper.jsAlertAndRedirect(this, "Salvo com sucesso!", ResolveUrl("~/Pages/ClientePage.aspx"));
}
The method which save the changes is described below:
public static Int32 SalvarAlteracoes(TB_CLIENTE_CLI cliente)
{
using (PlanoTesteDataContext context = DataContext.ObterConexao())
{
if (cliente.ID_CLIENTE_CLI == 0)
{
context.TB_CLIENTE_CLIs.InsertOnSubmit(cliente);
}
else
{
context.TB_CLIENTE_CLIs.Attach(cliente, true);
}
context.SubmitChanges();
} return cliente.ID_CLIENTE_CLI;
}
On the line context.TB_CLIENTE_CLIs.Attach(cliente, true); i'm receiving a System.InvalidOperationException: An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
I've already checked the UpdateChecks and they are set to Never.
What can I do? Thanks and sorry for the bad english.
This should work:
else
{
context.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, cliente);
context.TB_CLIENTE_CLIs.Attach(cliente);
}
This Refresh overload will keep the changes made by the user,it compares the modified entity with the original values from the database, detects the difference and marks the entity as modified and the call to SubmitChanges applies the update to the database.
You may very well run into trouble using Linq2SQL with disconnected entities. EF is a more suited solution to handle this.
However, please ensure you have set all properties on the entity on UpdateCheck to NEVER. I have tested this myself and it works. If this does work it will run an UPDATE statement on every column regardless of whether it has been updated or not. Could cause a problem if you use triggers on your tables. It might be a better idea to use a Timestamp instead to track the entities so concurrency issues between multiple users can be raised.
If you try to Attach an entity from a context where the ObjectTrackingEnabled is not set to False then you will have the following exception thrown:
An unhandled exception of type 'System.NotSupportedException' occurred in System.Data.Linq.dll
Additional information: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
As an example please use the following for retrieving and reattaching an entity:
public TB_CLIENTE_CLI Get(int id)
{
using (PlanoTesteDataContext ctx = new PlanoTesteDataContext())
{
ctx.ObjectTrackingEnabled = false;
return ctx.TB_CLIENTE_CLI.SingleOrDefault(n => n.ID == id);
}
}
public void Save(TB_CLIENTE_CLI cliente)
{
using (PlanoTesteDataContext ctx = new PlanoTesteDataContext())
{
ctx.DeferredLoadingEnabled = false;
ctx.TB_CLIENTE_CLI.Attach(cliente, true);
ctx.SubmitChanges();
}
}
You will also need to set DeferredLoadingEnabled loading to False in the Save method so that you can save down changes on the entity subsequent times after the first initial save on modification.

Entity Framework Update Problem

I am using Entity Framework & LINQ to retrieve data. I am having a problem with the following:
var customer= db.customers.where(c=>c.id==1);
customer.name=santhosh;
customer.city=hyd;
The fields are changing in the database before I call:
db.SaveChanges();
How do I avoid this?
As others have said, I believe that you are using your context in another place as well and that other location is calling savechanges and updating everything. Try doing what #Evan suggested with a using statment to make sure you have a fresh context.
AsNoTracking will not ensure that you get a entity that is not cached in the database, its purpose is to not put the objects inside the context. If you use AsNoTracking and then change the entities returned in the query you will need to attach them as modified to the context before calling savechanges or else they won't be updated.
var customer= db.customers.AsNoTracking().Single(c=>c.id==1);
customer.name=santhosh;
customer.city=hyd;
ctx.customers.Attach(customer);
ctx.ObjectStateManager.ChangeObjectState(customer, System.Data.EntityState.Modified);
I would have just commented on the other posts but don't have enough rep yet.
Check whether you are passing the db object to some other method, and SaveChanges() is called there?
Or check whether you have a catch block of an exception and you might be using SaveChanges() in the catch block to log error message?
(These are common programming mistakes)
The fields are changing in the database before I call
If you mean changing as in changing outside of application, changes in SQL Management Studio for example. Entity Framework cannot detect those changes, so as a result you might get stale objects that was cached by Entity Framework. To prevent receiving cached object and get the up-to-date values from database, use AsNoTracking.
Try putting AsNoTracking():
var customer= db.customers.AsNoTracking().where(c=>c.id==1);
customer.name=santhosh;
customer.city=hyd;
db.SaveChanges();
Or if your problem is to detect concurrent updates(unfortunate terminology, it doesn't apply to UPDATE only) to same row, use rowversion(aka timestamp) field type; then on your .NET code add Timestamp attribute on the property. Example: http://www.ienablemuch.com/2011/07/entity-framework-concurrency-checking.html
public class Song
{
[Key]
public int SongId { get; set; }
public string SongName { get; set; }
public string AlbumName { get; set; }
[Timestamp]
public virtual byte[] Version { get; set; }
}
UPDATE (after your comment):
If you really has no intent to persist your object changes to database. Try detaching the object.
Try this:
var customer= db.customers.where(c=>c.id==1);
db.Entry(customer).State = System.Data.EntityState.Detached; // add this
customer.name=santhosh;
customer.city=hyd;
db.SaveChanges();
That won't save your changes on name and city to database.
If you want something more robust(the above will fail an exception if the object was not yet attached), create a helper:
private static void Evict(DbContext ctx, Type t,
    string primaryKeyName, object id)
{           
    var cachedEnt =
        ctx.ChangeTracker.Entries().Where(x =>  
            ObjectContext.GetObjectType(x.Entity.GetType()) == t)
            .SingleOrDefault(x =>
        {
            Type entType = x.Entity.GetType();
            object value = entType.InvokeMember(primaryKeyName,
                                System.Reflection.BindingFlags.GetProperty,
null, x.Entity, new object[] { });
 
            return value.Equals(id);
        });
 
    if (cachedEnt != null)
        ctx.Entry(cachedEnt.Entity).State = EntityState.Detached;
}
To use: Evict(yourDbContextHere, typeof(Product), "ProductId", 1);
http://www.ienablemuch.com/2011/08/entity-frameworks-nhibernate.html
Can you give a little more of the surrounding code? Might be a little difficult without seeing how you are constructing your context.
This is how I typically handle updates (I hope it might give some insight):
using (var ctx = new myModel.myEntities())
{
int pollID = 1;
var poll = (from p in ctx.Polls
where p.PollID == pollID
select p).FirstOrDefault();
poll.Question = txtPoll.Text.Trim();
ctx.SaveChanges();
}
Jack Woodward, yours did not work for me.
I had to change it up a little for SQL Compact.
var customer= db.customers.AsNoTracking().Single(c=>c.id==1);
db.customers.Attach(customer);
customer.name=santhosh;
customer.city=hyd;
db.ObjectStateManager.ChangeObjectState(customer, System.Data.EntityState.Modified);
db.SaveChanges();
db.Dispose();
This worked alot better.

Categories