I've checked this question and it seems to be related to what I need, but does not answer it exactly.
I have an entity (Sql Compact using EF Code First via MVC3- if that wasn't clear from the title) for an "Issue" (generic issue tracking, just for my own education understanding of how MVC3 works). The Issue class has a CreatedBy property (Int reference to a User who Created the Issue) and a CreatedDate property (DateTime). When I use the scaffolded code to update (modified only to prevent some updated date fields from being modified by the user):
if (ModelState.IsValid)
{
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
startingIssue = null;
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
I receive the error mentioned in the linked question (conversion of a datetime2 data type to a datetime data type etc., etc.,)
When I step through the code, it appears my CreatedBy and CreatedDate properties are not contained in the instance of issue that the controller is passing around. When I try to fix that by grabbing another copy of the issue from the db, and updating those to values:
var startingIssue = db.Issues.Find(issue.IssueId);
if (ModelState.IsValid)
{
if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
startingIssue = null;
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
I get the concurrency violation: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
So, how do I get EF to see the date which is already set in the DB (so it doesn't try to update the CreatedDate to 1/1/0001) without violating concurrency?
Edit
Okay... I found it. I was, apparently, looking for #Html.HiddenFor(model => model.[property]) and adding the editor to the view anyway. That seems a little silly and round-about to me, but it does work without having to add custom code to detach one object and substitute an updated one.
The short answer is that you've already loaded the entity into the context with the Find and you cannot later attach another one.
You are left with two options:
Detach the first instance, then attach the second
Copy the fields from the second instance to the first
I'll share code for the first option. First, add a Detach method to your DbContext implementation:
public void Detach(object entity)
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.Detach(entity);
}
Then call Detach instead of setting the variable to null
var startingIssue = db.Issues.Find(issue.IssueId);
if (ModelState.IsValid)
{
if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
// startingIssue = null;
db.Detach(startingIssue);
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
If the CreateDate and CreatedBy fields are not in the edit form, the update action object will not have the db values.
The additional call to the db and resetting, as Ed's answer describes, can be avoided if you include those fields in the edit form. Then the normal model binding should pick them up and give them back to you on the update.
Related
I currently have many if statement when saving data to my database because the Column changes dynamically. My code would be far more efficient if I could use the "name" string to dynamically change the column that I need to save too.
if (String.Compare(name, "MWSAccessKeyID", StringComparison.Ordinal) == 0)
{
var DBobj = db.AccountsConfigDatas.Find(0);
DBobj.MWSAccessKeyID = NewValue;
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
}
What I have tried:
I have researched this topic for a couple of hours. I believe I'm using the wrong search terms because there is not a lot out there. I tried finding the Column using the name string in a LINQ.
var Column = DBobj
.Where(e == name)
.Select(e)
.FirstOrDefault();
DBobj.Column = NewValue;
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
Any suggestions or documentation from the community would be very helpful.
It seems that X-editable forces you to update properties by name. In my opinion your architecture shouldn't allow idiosyncrasies of a UI library to bleed into the deeper regions of your application code. An abstraction/mapping layer between controllers and DAL (or services) is recommended.
Anyway, as long as you don't have that, with Entity Framework it's easy to modify properties by name. No reflection required because EF has cached the meta data of the entity model:
var dbObj = db.AccountsConfigDatas.Find(id);
db.Entry(dbObj).CurrentValues[propertyName] = newValue;
db.SaveChanges();
This updates one property by name and marks this single property as modified, so only this property will be in the update statement. It's bad practice to set the entity's state as modified because that generates an update statement containing all of its properties.
var DBobj = db.AccountsConfigDatas.Find(0);
var propertyInfo = DBobj.GetType().GetProperty(name);
propertyInfo.SetValue(DBobj, NewValue);
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
Documentation:Type.GetProperty()
I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.
Question
Why is EF first inserting a child object (PersonnelWorkRecord) with a dependency, before the object that it is depended on (TimesheetActivity). Also what are my options on correcting this?
ERD (simplified)
This is predefined by another system out of my direct control.
EF setup and save code
I am not sure I understand why/how Entity Framework is inserting the objects I have in the order it does however here is the code I am using to insert a parent and several children.
using (var db = new DataContext(user))
{
timesheet.State = State.Added;
timesheet.SequenceNumber = newSequenceNumber;
this.PrepareAuditFields(timesheet);
//To stop EF from trying to add all child objects remove them from the timehseets object.
timesheet = RemoveChildObjects(timesheet, db);
//Add the Timesheet object to the database context, and save.
db.Timesheets.Add(timesheet);
result = db.SaveChanges() > 0;
}
SQL Trace of EF's Inserts
When I run the code I get a SQL foreign key violation on the PersonnelWorkRecord (TimesheetActivityID) because I have not yet added the Activity (see trace).
exec sp_executesql N'insert [dbo].[Timesheets]([ProjectID], [TimesheetStatusID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkdays]([TimesheetID], [PersonnelID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkRecords]([PersonnelWorkdayID],[TimesheetActivityID], ...
Data Context Summary
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Personnel).WithMany(p => p.PersonnelWorkdays).HasForeignKey(pwd => pwd.PersonnelID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Timesheet).WithMany(t => t.PersonnelWorkdays).HasForeignKey(pwd => pwd.TimesheetID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.PersonnelWorkday).WithMany(pwd => pwd.PersonnelWorkRecords).HasForeignKey(pwr => pwr.PersonnelWorkdayID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.TimesheetActivity).WithMany(ta => ta.PersonnelWorkRecords).HasForeignKey(pwr => pwr.TimesheetActivityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.ProjectActivity).WithMany(a => a.TimesheetActivities).HasForeignKey(ta => ta.ProjectActivityCodeID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasOptional(ta => ta.Facility).WithMany(f => f.TimesheetActivities).HasForeignKey(tf => tf.FacilityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.Timesheet).WithMany(t => t.TimesheetActivities).HasForeignKey(ta => ta.TimesheetID).WillCascadeOnDelete(false);
Remove Child Objects
Here is the code for the child objects method. I added this method to remove the objects from the timesheets' child objects related objects that are not foreign keys. For example I have a Crew object but I also have a CrewID foreign key, so I have set Crew = null so that EF does not try to insert it since it already exists.
private Timesheet RemoveChildObjects(Timesheet timesheet, DataContext db)
{
timesheet.Crew = null;
timesheet.Foreman = null;
timesheet.Location = null;
timesheet.Project = null;
timesheet.SigningProjectManager = null;
timesheet.TimesheetStatus = null;
timesheet.Creator = null;
timesheet.Modifier = null;
if (timesheet.TimesheetActivities != null)
{
foreach (TimesheetActivity tsa in timesheet.TimesheetActivities)
{
tsa.Creator = null;
if (tsa.EquipmentWorkRecords != null)
{
tsa.EquipmentWorkRecords = RemoveChildObjects(tsa.EquipmentWorkRecords, db);
}
tsa.Facility = null;
tsa.Modifier = null;
if (tsa.PersonnelWorkRecords != null)
{
tsa.PersonnelWorkRecords = RemoveChildObjects(tsa.PersonnelWorkRecords, db);
}
tsa.ProjectActivity = null;
tsa.Structures = null;
tsa.Timesheet = null;
}
}
if (timesheet.TimesheetEquipment != null)
{
foreach (TimesheetEquipment te in timesheet.TimesheetEquipment)
{
te.Equipment = null;
te.Timesheet = null;
}
}
if (timesheet.EquipmentWorkdays != null)
{
timesheet.EquipmentWorkdays = RemoveChildObjects(timesheet.EquipmentWorkdays, true, db);
}
if (timesheet.TimesheetPersonnel != null)
{
foreach (TimesheetPersonnel tp in timesheet.TimesheetPersonnel)
{
tp.Personnel = null;
tp.PersonnelWorkday = null;
if (tp.PersonnelWorkday != null)
{
tp.PersonnelWorkday = RemoveChildObjects(tp.PersonnelWorkday, db);
}
tp.Timesheet = null;
}
}
if (timesheet.PersonnelWorkdays != null)
{
timesheet.PersonnelWorkdays = RemoveChildObjects(timesheet.PersonnelWorkdays, true, db);
}
return timesheet;
}
Debug of values before EF save
From my understanding anything an dbContex.ObjectNameHere.Local will be added/modified/deleted when a dbContext.Save() is called. (Depending on what the entity State is set too.) Here is what EF is trying to save before I call the save() and get an SQL FK exception.
Then I get the FK exception.
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_PersonnelWorkRecords_TimesheetActivities". The conflict occurred
in database "VPMTEST_GC", table "dbo.TimesheetActivities", column
'TimesheetActivityID'. The statement has been terminated.
Notes
Please let me know if there is anything I can post to help describe my question. I have looked around google / SO for answers but so far no solid answers, it looks like EF can not determine the order of inserting objects unless the Domain model is setup differently? I am not able to change the structure of most objects as they are used by another system. I can attempt to change my EF call, I would prefer not to use Raw SQL as the objects are quite a bit more extensive then the simplified versions I have posted here.
Similar questions: Self referencing entity and insert order
In your RemoveChildObjects method I see the line...
tsa.Timesheet = null;
So, apparently your are setting the inverse navigation property of Timesheet.TimesheetActivities to null. Are you doing the same with PersonnelWorkRecord.TimesheetActivity and PersonnelWorkRecord.PersonnelWorkday, i.e. do you set those properties to null as well in the nested RemoveChildObjects methods?
This could be a problem because you have two different paths from Timesheet to PersonnelWorkRecord, namely:
Timesheet -> TimesheetActivities -> PersonnelWorkRecords
Timesheet -> PersonnelWorkdays -> PersonnelWorkRecords
When you call db.Timesheets.Add(timesheet) I believe EF will traverse each branch in the object graph one by one and determine on the path which related objects ("nodes") are dependent and which are principal in a relationship to determine the order of insertion. timesheet itself is principal for all its relationships, therefore it is clear that it must be inserted first. Then EF starts to iterate through one of the collections Timesheet.TimesheetActivities or Timesheet.PersonnelWorkdays. Which one comes first doesn't matter. Apparently EF starts with Timesheet.PersonnelWorkdays. (It would not solve the problem if it would start with Timesheet.TimesheetActivities, you would get the same exception, but with PersonnelWorkRecord.PersonnelWorkday instead of PersonnelWorkRecord.TimesheetActivity.) PersonnelWorkday is only dependent on Timesheet which is already inserted. So, PersonnelWorkday can be inserted as well.
Then EF continues traversing with PersonnelWorkday.PersonnelWorkRecords. With respect to the PersonnelWorkday dependency of PersonnelWorkRecord there is again no problem because the PersonnelWorkday has already been inserted before. But when EF encounters the TimesheetActivity dependency of PersonnelWorkRecord it will see that this TimesheetActivity is null (because you've set it to null). It assumes now that the dependency is described by the foreign key property TimesheetActivityID alone which must refer to an existing record. It inserts the PersonnelWorkRecord and this violates a foreign key constraint.
If PersonnelWorkRecord.TimesheetActivity is not null EF would detect that this object hasn't been inserted yet but it is the principal for PersonnelWorkRecord. So, it can determine that this TimesheetActivity must be inserted before the PersonnelWorkRecord.
I would hope that your code works if you don't set the inverse navigation properties to null - or at least not the two navigation properties in PersonnelWorkRecord. (Setting the other navigation properties like tsa.Creator, tsa.Facility, etc. to null should not be a problem because those related objects really already exist in the database and you have set the correct FK property values for those.)
This may no longer be valid, however is it an option to use a transaction and adding each child object individually?
Note:
I think Slauma's solution is more complete, however a transaction call may still be an option for others with similar issues.
I'm updating an existing entity by attaching it to my data context like this:
var updatedDocumentState = new AccDocumentState()
{
Id = accDocumentState.Id,
IsDocumentary = accDocumentState.IsDocumentary,
IsEditable = accDocumentState.IsEditable,
IsRecursive = accDocumentState.IsRecursive,
Title = accDocumentState.Title,
Reportable = accDocumentState.Reportable,
};
context.AccDocumentStates.Attach(updatedDocumentState);
context.ObjectStateManager.ChangeObjectState(updatedDocumentState, System.Data.EntityState.Modified);
flag = context.SaveChanges() > 0;
And this works, however after saving the attached entity, the properties of the existing entity which i didn't update, but i want to keep as they were, are overwritten and given null values. How can I attach my entity and keep the properties of the existing entity which i have not updated?
EF has an Object Data change tracker. Is enabled via proxies
Tracking changes in Poco entries
Essentially You/find Read the Object/Poco entity first.
Change only those properties you want. And save.
Only the changed properties are updated.
If you are not using autoDetectChnages
this.Configuration.AutoDetectChangesEnabled = false; ////<<<<<<<<< Default true
Then you would Call Detect Changes before Saving.
But either way the concept is based around a Read first to get entity.
Make the necessary changes and save.
Only the actually changes are sent back to Db.
eg:
var mypoco = Context.Set<TPoco>.Find(1);
myPoco.propertyXyz = "changed";
// normally not required by default, But incase your are not using tracking proxies , tell ef heads Up
// Context.Context.ChangeTracker.DetectChanges(); // uncomment when needed
Context.SaveChanged();
Only the actually changes are sent to DB.
Whilst the POST from Rameez is correct, it does not indicate why setting the whole entry as changed is desirable nor why do that ? Why link the State entry post from documentation ?
Context.Entry(poco).State = state; // why do this ? or the objectContext equivalent
This will result in an UPdate Set for all values going to Database on SaveChanges
Since ALL fields will be treated as changed. This is NOT a good way to use EF.
It is important to know about the auto detect changes in EF.
See Automatic detect changes
and Entity states and SaveChanges
As per msdn When you change the EntityState of an entity object entry to Modified, all the properties of the object are marked as modified, regardless of the current or original values. http://msdn.microsoft.com/en-us/library/system.data.objects.objectstatemanager.changeobjectstate.aspx
Hence I think all other properties get set to null as the object that you create will have other properties as null or their default values.
Below is the modified code.
var updatedDocumentState = context.AccDocumentStates.First(a => a.Id== accDocumentState.Id);
updatedDocumentState.IsDocumentary = accDocumentState.IsDocumentary,
updatedDocumentState.IsEditable = accDocumentState.IsEditable,
updatedDocumentState.IsRecursive = accDocumentState.IsRecursive,
updatedDocumentState.Title = accDocumentState.Title,
updatedDocumentState.Reportable = accDocumentState.Reportable,
flag = context.SaveChanges() > 0;
As a workaround to your problem, create a model for just the fields you are updating. Assuming this is a common scenario and warrants the extra model to avoid an extra call to the db.
With the new, minimized model, pointing to the same table, but with only the required properties, it will work as you want. Of course, nothing changed on the EF side, but it will only update the properties it knows about.
While I agree this is not how EF was designed, I too feel frustrated with the extra DB calls to do an update or delete. This solution helps with that.
Try this. Maybe works as you need:
var updatedDocumentState = context.AccDocumentStates.Find(accDocumentState.Id)
{
IsDocumentary = accDocumentState.IsDocumentary,
IsEditable = accDocumentState.IsEditable,
IsRecursive = accDocumentState.IsRecursive,
Title = accDocumentState.Title,
Reportable = accDocumentState.Reportable,
};
flag = context.SaveChanges() > 0;
I've had luck with the following. First I created an extension method to unset the IsModified flag for any property that is not in the set of properties that I want to restrict updates to:
public static void RestrictModifiedProps<ENT>(this DbContext context, ENT entity, IEnumerable<string> restrictedPropNames)
where ENT : class
{
//Grab the meta entry that knows whether the entity/properties have been updated
var entry = context.Entry(entity);
if (entry == null) return;
//loop over properties, only allow properties in the
// restrictedPropNames list to be modified
foreach (var propName in entry.CurrentValues.PropertyNames)
{
var prop = entry.Property(propName);
if (!prop.IsModified) continue;
prop.IsModified = restrictedPropNames.Any(O => O == propName);
}
}
In my case, I am accepting the entity's property values from a json post to an MVC action. So, I want to find out what properties were posted and created a (couple) extension methods for the controller:
public static JObject JsonPostData(this Controller cntrlr)
{
//ensure we're at the start of the input stream
Stream req = cntrlr.Request.InputStream;
req.Seek(0, SeekOrigin.Begin);
//read in any potential json
string json = d2s.SafeTrim(new StreamReader(req).ReadToEnd());
if (string.IsNullOrWhiteSpace(json)
|| !json.StartsWith("{")
|| !json.EndsWith("}"))
return null;
//try to deserialize it
return JsonConvert.DeserializeObject(json) as JObject;
}
public static IEnumerable<JProperty> JsonPostProperties(this Controller cntrlr)
{
JObject jObj = cntrlr.JsonPostData();
if (jObj == null) return null;
return jObj.Properties();
}
public static IEnumerable<string> JsonPostPropNames(this Controller cntrlr)
{
IEnumerable<JProperty> jProps = cntrlr.JsonPostProperties();
if (jProps == null) return null;
return jProps.Select(O => O.Name);
}
In the action, we get:
[HttpPost, ActionName("Edit")]
public virtual ActionResult Edit_Post(ENT obj)
{
...code...
Ctxt.Set<ENT>().Attach(obj);
Ctxt.Entry(obj).State = EntityState.Modified;
Ctxt.RestrictModifiedProps(obj, this.JsonPostPropNames());
...code...
}
If you are just excluding one or two properties, like say you never wanted to allow updates to the Title property (in your example), just unset IsModified on target properties after you set the object state to modified:
context.AccDocumentStates.Attach(updatedDocumentState);
context.ObjectStateManager.ChangeObjectState(updatedDocumentState, System.Data.EntityState.Modified);
context.Entry(updatedDocumentState).Property("Title").IsModified = false;
flag = context.SaveChanges() > 0;
Also FYI - Default MVC5 projects in VS use this line to set the object's modified property:
context.Entry(updatedDocumentState).State = System.Data.EntityState.Modified;
UPDATE (2010-12-21): Completely rewrote this question based on tests that I've been doing. Also, this used to be a POCO specific question, but it turns out that my question isn't necessarily POCO specific.
I'm using Entity Framework and I've got a timestamp column in my database table that should be used to track changes for optimistic concurrency. I've set the concurrency mode for this property in the Entity Designer to "Fixed" and I'm getting inconsistent results. Here are a couple of simplified scenarios that demonstrate that concurrency checking works in one scenario but not in another.
Successfully throws OptimisticConcurrencyException:
If I attach a disconnected entity, then SaveChanges will throw an OptimisticConcurrencyException if there is a timestamp conflict:
[HttpPost]
public ActionResult Index(Person person) {
_context.People.Attach(person);
var state = _context.ObjectStateManager.GetObjectStateEntry(person);
state.ChangeState(System.Data.EntityState.Modified);
_context.SaveChanges();
return RedirectToAction("Index");
}
Does not throw OptimisticConcurrencyException:
On the other hand, if I retrieve a new copy of my entity from the database and I do a partial update on some fields, and then call SaveChanges(), then even though there is a timestamp conflict, I don't get an OptimisticConcurrencyException:
[HttpPost]
public ActionResult Index(Person person) {
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.Name = person.Name;
// currentPerson.VerColm == [0,0,0,0,0,0,15,167]
// person.VerColm == [0,0,0,0,0,0,15,166]
currentPerson.VerColm = person.VerColm;
// in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
// in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
_context.SaveChanges();
return RedirectToAction("Index");
}
Based on SQL Profiler, it looks like Entity Framework is ignoring the new VerColm (which is the timestamp property) and instead using the originally loaded VerColm. Because of this, it will never throw an OptimisticConcurrencyException.
UPDATE: Adding additional info per Jan's request:
Note that I also added comments to the above code to coincide with what I see in my controller action while working through this example.
This is the value of the VerColm in my DataBase prior to the update: 0x0000000000000FA7
Here is what SQL Profiler shows when doing the update:
exec sp_executesql N'update [dbo].[People]
set [Name] = #0
where (([Id] = #1) and ([VerColm] = #2))
select [VerColm]
from [dbo].[People]
where ##ROWCOUNT > 0 and [Id] = #1',N'#0 nvarchar(50),#1 int,#2 binary(8)',#0=N'hello',#1=1,#2=0x0000000000000FA7
Note that #2 should have been 0x0000000000000FA6, but it's 0x0000000000000FA7
Here is the VerColm in my DataBase after the update: 0x0000000000000FA8
Does anyone know how I can work around this problem? I'd like Entity Framework to throw an exception when I update an existing entity and there's a timestamp conflict.
Thanks
Explanation
The reason why you aren't getting the expected OptimisticConcurrencyException on your second code example is due to the manner EF checks concurrency:
When you retrieve entities by querying your db, EF remembers the value of all with ConcurrencyMode.Fixed marked properties by the time of querying as the original, unmodified values.
Then you change some properties (including the Fixed marked ones) and call SaveChanges() on your DataContext.
EF checks for concurrent updates by comparing the current values of all Fixed marked db columns with the original, unmodified values of the Fixed marked properties.
The key point here is that EF treats the update of you timestamp property as a normal data property update. The behavior you see is by design.
Solution/Workaround
To workaround you have the following options:
Use your first approach: Don't requery the db for your entity but Attach the recreated entity to your context.
Fake your timestamp value to be the current db value, so that the EF concurrency check uses your supplied value like shown below (see also this answer on a similar question):
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.VerColm = person.VerColm; // set timestamp value
var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson);
ose.AcceptChanges(); // pretend object is unchanged
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
You can check for concurrency yourself by comparing your timestamp value to the requeried timestamp value:
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
if (currentPerson.VerColm != person.VerColm)
{
throw new OptimisticConcurrencyException();
}
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
Here is another approach that is a bit more generic and fits in the data layer:
// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
.Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();
It just checks to see if the TimeStamp has changed and throws concurrency exception.
If it's EF Code first, then use code similar to below code. This will change the original TimeStamp loaded from db to the one from UI and will ensure OptimisticConcurrencyEception occurs.
db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;
I have modified #JarrettV solution to work with Entity Framework Core. Right now it is iterating through all modified entries in context and looking for any mismatch in property marked as concurrency token. Works for TimeStamp (RowVersion) as well:
private void ThrowIfInvalidConcurrencyToken()
{
foreach (var entry in _context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Unchanged) continue;
foreach (var entryProperty in entry.Properties)
{
if (!entryProperty.IsModified || !entryProperty.Metadata.IsConcurrencyToken) continue;
if (entryProperty.OriginalValue != entryProperty.CurrentValue)
{
throw new DbUpdateConcurrencyException(
$"Entity {entry.Metadata.Name} has been modified by another process",
new List<IUpdateEntry>()
{
entry.GetInfrastructure()
});
}
}
}
}
And we need only to invoke this method before we save changes in EF context:
public async Task SaveChangesAsync(CancellationToken cancellationToken)
{
ThrowIfInvalidConcurrencyToken();
await _context.SaveChangesAsync(cancellationToken);
}