OptimisticConcurrencyException Does Not Work in Entity Framework In Certain Situations - c#

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);
}

Related

ASP.NET API throws Entity Framework error

Below is the code of my API, but it always returns a 500 Internal server error. The exception is thrown when executing SaveChangesAsync().
The instance of entity type 'NewsReport' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values
[HttpPut("{id}")]
public async Task<IActionResult> EditDevblog([FromBody] DevblogModel devblog, int id)
{
if (!ModelState.IsValid)
return BadRequest();
var devblogInDb = _context.DevblogModels
.Include(d => d.Fixs)
.Include(d => d.News)
.Include(d => d.Removes)
.Include(d => d.Updates)
.SingleOrDefault(d => d.Id == id);
if (devblogInDb == null)
return NotFound();
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
await _context.SaveChangesAsync();
var h = _context.DevblogModels.SingleOrDefault(d => d.Id == id);
return Ok();
}
Please see my answer here.
EF Core throws this exception when you replace a child entity instance with a new one like you are doing here:
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
Unfortunately the only workable alternative has been to update the current child entity properties directly. Per my original answer:
// In your DevblogModels and assuming a 1:1 relationship
public void SetNewsReport(NewsReport newsReport)
{
this.News.UpdateFrom(newsReport);
}
// Then, on your NewsReport entity
internal void UpdateFrom(NewsReport other)
{
this.Title = other.Title;
this.Article = other.Article;
// other properties...
}
The second approach in my original answer suggests to detach the entity, replace the child instance, then reattach and try to save. In my attempts with this approach the exception went away, but the data did not persist to the database. Maybe you'll have better luck with it than I did...
context.Entry(devblogInDb).State = EntityState.Detached;
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
context.Entry(devblogInDb).State = EntityState.Modified;
A Bit Further
As my original answer suggests this has been a longstanding issue with EF Core. This issue was supposed to be fixed in EF Core 2.1, however, the OP of my original answer was using the release candidate for EF Core 2.1. Apparently this issue either didn't make the cut or the fixes put in place still have a few bugs in them (it is just a release candidate after all...)
For a great read on this topic Julie Lerman posted an article in the MSDN magazine that you can find here. She also has a few alternative solutions that you could use as well.

Failure to attach a detached entity (entity with the same key is already in the context)

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.

Entity Framework inserting child objects in the wrong order

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.

ASP.NET MVC3 Code-First Error- attempting to update entity

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.

Entity Framework: Updating a field to a null value?

I'm using stubs to update my entities and when the updated entity consists of columns that have values changed from non-nulls to nulls, the nulls are not persisted to the database i.e. the record continues to hold the previous non-null values.
What am I doing wrong?
public void UpdateEntity(Entity e)
{
_context.Works.Attach(new Entity{ Id = e.Id });
_context.ApplyCurrentValues("Entities", e);
_context.SaveChanges();
}
The problem is that you need to assign null to these properties after you Attach(), not before. Perhaps ApplyCurrentValues() only copies non-already-identical properties? (I've never tested, but it would be reasonable if it did.)
Try to avoid loading entities using Attach. If you just get entity from DB, you don't have to bother with settings flags on nulled items:
var stage = Db.Stages.First(s => s.ID == someId);
stage.Notation = vm.Notation; // can be also null
Db.SaveChanges();

Categories

Resources