Entity Framework won't detect changes of navigation properties - c#

I'm having trouble with detecting changes of a navigation property:
My testing model looks like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
}
I've created and saved an object of type Person with both Name and Address properties assigned. My problem is that if I fetch the Person object back from the database and I change the Address property (ex. to null) then EF doesn't detect the change!
My code is this:
using (var ctx = new EFContext())
{
Person p = ctx.People.First();
// p.Address IS NOT NULL!
p.Address = null;
var entry = ctx.Entry(p);
}
Why is entry.State Unchanged ?
Edit: If I call SaveChanges, the record is saved correctly (the Address become null)!
Edit 2: I've created the foreign key property as billy suggested. If I inspect the Person object in Visual Studio, the State is Modified. If I don't stop with the debugger inspecting the object's values, the state is Unchanged!
Edit 3: Loading the Person object using ctx.People.Include(x => x.Address).First(); solves the problem. Is there a way to avoid calling Include and continue to modify the Address property instead of the AddressId one?

First of all: You MUST follow #billy's advice to use Include. Your remark "p.Address IS NOT NULL!" is only true because you are watching p.Address in the debugger and thereby triggering lazy loading in the debugger, so the change of setting the address to null is detected. In release mode or when you don't inspect the properties in the debugger your code wouldn't work and no changes would be saved.
So, the answer to your Edit 3 is: No.
Second: var entry = ctx.Entry(p) only returns entity states and you didn't change an entity state but instead a relationship state, or more precisely you deleted a relationship. You can't inspect relationship states with the DbContext API but only with the ObjectContext API:
Person p = ctx.People.Include(x => x.Address).First();
p.Address = null;
var objCtx = ((IObjectContextAdapter)ctx).ObjectContext;
var objentr = objCtx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);
objentr will have an entry of type RelationshipEntry now:
EF will consider this relationship entry together with entity state entries when you call SaveChanges() and delete the relationship, i.e. set the Address foreign key column of the Person in the database to NULL.
About Edit 2: Changing a foreign key property (which is a scalar property in your model) is a change of the entity itself, so the entity state will be Modified in this case.

You need to include the Address nav. prop. in your query, otherwise EF won't consider changes to it when you save :
using (var ctx = new EFContext())
{
Person p = ctx.People.Include(x => x.Address).First();
//p.Address IS NOT NULL!
p.Address = null;
var entry = ctx.Entry(p);
}
You could also use foreign keys in your model, which I like very much :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
public int? AddressId {get; set;}
}
...
using (var ctx = new EFContext())
{
Person p = ctx.People.First();
p.AddressId = null;
var entry = ctx.Entry(p);
}

In my application, before a reload is requested or the user leaves the item/view, I perform some checks to make sure there are no unsaved changes.
This is basically running off the currently accepted answer, but I wanted to provide an implementation and bring to the attention that you must call Context.ChangeTracker.DetectChanges() before the ObjectContext.ObjectStateManager can pick up relationship changes! I spent quite a bit of time debugging this, silly!
_EagleContext.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)_EagleContext).ObjectContext;
var changedEntities = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);
if (_EagleContext.ChangeTracker.Entries().Any(e => e.State == EntityState.Modified)
|| changedEntities.Count() != 0)
{
var dialogResult = MessageBox.Show("There are changes to save, are you sure you want to reload?", "Warning", MessageBoxButton.YesNo);
if (dialogResult == MessageBoxResult.No)
{
return;
}
}
// Continue with reloading...

Related

Deleting parent-child relation with Entity Framework or Entity Framework Core

I'm learning how to use Entity Framework, having a NHibernate background.
There are plenty of tutorials on the net, but I didn't find something for my specific case.
I don't want to delete the Category record, I only want to delete the relation!
I have following poco's:
public class TrainingCourse
{
[Key]
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<TrainingContent> Content { get; set; } = new List<TrainingContent>();
}
public class TrainingContent
{
[Key]
public int ContentId { get; set; }
public string ContentName { get; set; }
public int? CategoryId { get; set; }
[ForeignKey("CategoryId")]
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
In the database I have following 'data' inserted
var category = new Category { CategoryName = "Category 1" };
var course = new TrainingCourse { CourseName = "Course 1" };
context.TrainingCourses.Add(course);
var content = new TrainingContent { ContentName = "Content 1", Category = category };
context.TrainingContents.Add(content);
course.Content.Add(content);
I now want to delete the relation from TrainingContent to Category: in database terms speaking, I want to set my foreignkey CategoryId in the table TrainingContent to null.
When doing this in one context, it is working, my foreign key is NULL after the save:
using (var context = new ClientContext())
{
_course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");
_course.Content.ToList()[0].ContentName = "Content 1 changed";
_course.Content.ToList()[0].Category = null;
context.SaveChanges();
}
But in the real world, we are working with disconnected entities.
I simulate this with following code
using (var context = new ClientContext())
{
_course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");
}
_course.Content.ToList()[0].ContentName = "Content 1 changed";
_course.Content.ToList()[0].Category = null;
using (var context = new ClientContext())
{
context.Entry(_course.Content.ToList()[0]).State = EntityState.Modified;
context.SaveChanges();
}
This is not working, the content name is changed, but my foreign key is still defined.
If I set the navigation property to NULL and the FK to NULL, it is working:
using (var context = new ClientContext())
{
_course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");
}
_course.Content.ToList()[0].ContentName = "Course 1 changed";
_course.Content.ToList()[0].Category = null;
_course.Content.ToList()[0].CategoryId = null;
using (var context = new ClientContext())
{
context.Entry(_course.Content.ToList()[0]).State = EntityState.Modified;
context.SaveChanges();
}
Why this behaviour? I'm new to EF and EF Core, so maybe there is a simple explication?
I tested this originally with with Entity Framework Core, but same behaviour.
Your problem is that with a navigation property of null, EF will not check referential integrity (this will happen on SQL side).
However, this is necessary most of the time. Consider you want to attach a content to another category, you might want to do:
var content=context.Contents.FirstOrDefault();
content.CategoryId=2;
context.Entry(content).State=EntityState.Modified;
context.SaveChanges();
this looks fine, doesn't it? However, notice you never actually loaded (lazily or explicitly) the corresponding Category object - it is still null.
So, in this example and in yours the code itself looks fine, but the FK and the navigation property do not match. However, since the navigation property does not have to be correct (unless tracked by the context, since then it is loaded and EF knows its correct), EF will expect the FK to be correct and therefore save this value into the database.
Notice that this issue will not happen in a few cases: The first obviously is when EF was able to track the category in its ChangeTracker - then it will know you actually want to consider the category navigation property, not necessarily the FK.
The second is when your FK is not part of your object. Since then EF has to create a FK, however you cannot set it yourself. Then EF has to set the FK corresponding to the navigation property. (notice that this will not work with null. null means either the value is not loaded or the value is nothing (along with some other meanings) and EF cannot determine which one is correct).
Also, keep in mind that in disconnected scenario EF will check the referential integrity of the whole object tree upon Attaching the root of that tree - if any FK's do not match the navigation property, EF will throw an Exception (with the exception of null because of reasons above).
And: when you work in disconnected scenario, be aware that when any parent entries are in Added state, all child objects have to be attached (atm I do not know if they also have to be Modified/New), since otherwise EF cannot fix the child object's FK's and throw an exception.

Partially updating an entity in EF6

I'm trying to figure out how to smoothly do a partial update (basically a HTTP PATCH) of an entity, using Entity Framework 6.0, but I'm stumped at the number of examples out there that don't seem to work for me (even those that aren't obviously for another version of EF).
What I'd like to accomplish:
The entity is updated without having to load it first; i.e. there's only one trip to the database
Only the properties that I touch are updated - others are left as is
The closest I've gotten is neatly described by this answer to a very similar question, and illustrated by the following code:
public async Task UpdateMyEntity(int id, int? updatedProperty, string otherProperty)
{
using (var context = new MyDbContext())
{
var entity = new MyEntity { Id = id };
context.MyEntities.Attach(entity);
if (updatedProperty != null) { entity.Property = updatedProperty.Value; }
if (!string.IsNullOrEmpty(otherProperty) { entity.OtherProperty = otherProperty; }
await context.SaveChangesAsync();
}
}
Now, this works for simple entities, but I'm getting entity validation errors because I have a couple of required properties and relations that are not updated and therefore not present in the attached entity. As noted, I'd just like to ignore those.
I've debugged and verified that context.Entry(entity).Property(e => e.Property).IsModified changes to true when that line is run, and that all the properties I never touch still return false for similar checks, so I thought EF would be able to handle this.
Is it possible to resolve this under the two constraints above? How?
Update:
With LSU.Net's answer I understand somewhat what I have to do, but it doesn't work fully. The logic fails for referential properties.
Consider the following domain model:
public class MyEntity
{
public int Id { get; set; }
public int Property { get; set; }
[Required]
public string OtherProperty { get; set; }
[Required]
public OtherEntity Related { get; set; }
}
public class OtherEntity
{
public int Id { get; set; }
public string SomeProperty { get; set; }
}
Now, if I try to update a MyEntity, I do the following:
var entity = new MyEntity { Id = 123 }; // an entity with this id exists in db
context.MyEntities.Attach(entity);
if (updatedProperty != null) { entity.Property = updatedProperty.Value; }
await context.SaveChangesAsync();
In my custom validation method, overridden as in the answer below, the validation error on the required property OtherProperty is correctly removed, since it is not modified. However, I still get a validation error on the Related property, because entityEntry.Member("Related") is DbReferenceEntry, not DbPropertyEntry, and thus the validation error is not marked as a false error.
I tried adding a separate, analogous clause for handling reference properties, but the entityEntry doesn't seem to mark those as changed; with relation = member as DbReferenceEntry, relation doesn't have anything to indicate that the relationship is changed.
What can I check against for false errors in this case? Are there any other cases I need to handle specially (one-to-many relationships, for example)?
Entity Framework validation with partial updates
#Shimmy has written some code here to omit the validation logic for unmodified properties. That may work for you.
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result.ValidationErrors
.Where(error =>
{
var member = entityEntry.Member(error.PropertyName);
var property = member as DbPropertyEntry;
if (property != null)
return !property.IsModified;
else
return false;//not false err;
});
foreach (var error in falseErrors.ToArray())
result.ValidationErrors.Remove(error);
return result;
}

Relationship not set to null

I have a very simple EF operation that fails: break the relationship between two entities as shown in the code below:
public async Task RemoveCurrentTeacherOfGroup(int groupId)
{
var group = await _dataContext.Groups.SingleAsync(g => g.Id == groupId);
group.Teacher = null;
await _dataContext.SaveChangesAsync();
}
The database was generated code-first. The entities are defined like this:
public class Teacher
{
public int Id { get; set; }
..
public virtual List<Group> Groups { get; set; }
}
public class Group
{
public int Id { get; set; }
..
public virtual Teacher Teacher { get; set; }
}
However, breaking the relationship doesn't work, Teacher keeps pointing to the same entity. When stepping with the debugger, I see that the Teacher property doesn't become null after .Teacher = null. I tried it with the synchronous alternative, which had the same effect:
public void RemoveCurrentTeacherOfGroup(int groupId)
{
var group = _dataContext.Groups.Single(g => g.Id == groupId);
group.Teacher = null;
_dataContext.SaveChanges();
}
If Teacher is not loaded you can't break the relationship. Either include it (eager-load) on the query:
_dataContext.Groups.Include(g => g.Teacher).Single(g => g.Id == groupId);
Or if lazy loading is enabled, access the property for reading before setting it to null:
var teacher = group.Teacher;
group.Teacher = null;
You see that "Teacher is not null after setting it to null" because the debugger is accessing the property for reading (lazy-loading it) after you have set it to null.
The value was already null before you hit the group.Teacher = null line since you hadn't previously loaded it (you can't however debug this, since accessing the property for reading would cause EF to actually load it if lazy loading is enabled). If you see the property value with the debugger before setting it to null, it'll work as expected and break the relationship, since Teacher would be loaded

Reference remains null

I think i ran into a bug, it seems that EF is not handling references well after deleting and reinserting an entity. I've managed to reproduce it with the code below (assume all asserts pass except the one i talk about in the comments):
var database = new TestEntities();
// select and delete the info record
var info = database.Info.First(i => i.ID == 1);
Assert.AreEqual(1, info.MemberID);
// when i uncomment the line below the last Assert fails
// Assert.IsNotNull(info.Member);
database.Info.Remove(info);
// add it again and persist it to the database
database.Info.Add(new Info {
ID = 1,
MemberID = 1
});
database.SaveChanges();
// should not be null ? EDIT: i guess i understand this becoming null
Assert.IsNull(info.Member);
// and even here its still null
info = database.Info.First(i => i.ID == 1);
Assert.IsNull(info.Member);
Can anyone tell me whats going on here?
EDIT:
My entities are generated using database first and im using the DbContext/POCO generator.
public partial class Member
{
public Member()
{
this.Info = new HashSet<Info>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Info> Info { get; set; }
}
public partial class Info
{
public int ID { get; set; }
public int MemberID { get; set; }
public virtual Member Member { get; set; }
}
It turns out that it had nothing to do with deleting and reinserting, not really anyway. It was was so obvious ...
I was inserting using a POCO which is not eagerly loaded and does not have any lazy loading capabilities ...
The second time i queried for the same record i was expecting a proxy, but it seems that the POCO was cached by EF and that is what it returned meaning still no eager or lazy loading.
I can fix it by making sure EF doesn't retrieve the second query from cache, inserting using a proxy (var info = database.Info.Create()) or including member in the query (database.Info.Include(i => i.Member).First(i => i == 1)).
Given this
var info = database.Info.First(i => i.ID == 1);
Assert.AreEqual(1, info.MemberID);
Aren't you comparing info.ID to info.MemberID here? Isn't it possible that ID and MemberID actually are different?
Also, shouldn't you be using .SaveChanges() after
database.Info.Remove(info);
?
Also, info does not have .member available if none have been instantiated. Is there a correlating Member with MemberID equal to info.MemberId?

entity framework 4.1 code first and auto mapper issue

Consider this simple Model and ViewModel scenario:
public class SomeModel
{
public virtual Company company {get; set;}
public string name {get; set;}
public string address {get; set;}
//some other few tens of properties
}
public class SomeViewModel
{
public Company company {get; set;}
public string name {get; set;}
public string address {get; set;}
//some other few tens of properties
}
Problem that occurs is:
I have a edit page where company is not needed so I do not fetch it from database. Now when the form is submitted I do:
SomeModel destinationModel = someContext.SomeModel.Include("Company").Where( i => i.Id == id) // assume id is available from somewhere.
Then I do a
Company oldCompany = destinationModel.company; // save it before mapper assigns it null
Mapper.Map(sourceViewModel,destinationModel);
//After this piece of line my company in destinationModel will be null because sourceViewModel's company is null. Great!!
//so I assign old company to it
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
And problem is even when I assign oldCompany to my company it is still null in database after savechanges.
Note:
If I change these lines:
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
to these:
context.Entry(destinationModel).State = EntityState.Modified;
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
Notice I change the state 2 times, it works fine. What can be the issue? Is this a ef 4.1 bug?
This is a sample console application to address the issue:
using System;
using System.Linq;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using AutoMapper;
namespace Slauma
{
public class SlaumaContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<MyModel> MyModels { get; set; }
public SlaumaContext()
{
this.Configuration.AutoDetectChangesEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
}
}
public class MyModel
{
public int Id { get; set; }
public string Foo { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int? CompanyId { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyViewModel
{
public string Foo { get; set; }
public Company Company { get; set; }
public int? CompanyId { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<SlaumaContext>(new DropCreateDatabaseIfModelChanges<SlaumaContext>());
SlaumaContext slaumaContext = new SlaumaContext();
Company company = new Company { Name = "Microsoft" };
MyModel myModel = new MyModel { Company = company, Foo = "Foo"};
slaumaContext.Companies.Add(company);
slaumaContext.MyModels.Add(myModel);
slaumaContext.SaveChanges();
Mapper.CreateMap<MyModel, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyModel>();
//fetch the company
MyModel dest = slaumaContext.MyModels.Include("Company").Where( c => c.Id == 1).First(); //hardcoded for demo
Company oldCompany = dest.Company;
//creating a viewmodel
MyViewModel source = new MyViewModel();
source.Company = null;
source.CompanyId = null;
source.Foo = "foo hoo";
Mapper.Map(source, dest); // company null in dest
//uncomment this line then only it will work else it won't is this bug?
//slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
dest.Company = oldCompany;
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
slaumaContext.SaveChanges();
Console.ReadKey();
}
}
}
Automapper always updates every property from the source instance to the destination instance by default. So if you don't want your Company property overwritten then you have to explicitly configure this for your mapper:
Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.Company, c => c.UseDestinationValue());
So far nothing EF related. But if you are using this with EF you have to use your navigation property Company and the CompanyId consistently: you also need to use the destination value for CompanyId during mapping:
Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.CompanyId, c => c.UseDestinationValue());
EDIT: But the problem is not that your Company is null but after resetting it is still null in the DB. And this caused by the fact that if you are having an explicit Id property like 'CompanyId' you have to mantain it. So it is not enough to call destinationModel.company = oldCompany; you also need to call destinationModel.companyId = oldCompany.Id;
And because you retrieved your dest entity from the context it's already doing the change tracking for you therefore there is no need set EntityState.Modified.
EDIT: Your modified sample:
Mapper.CreateMap<MyModel, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyModel>();
//fetch the company
MyModel dest = slaumaContext.MyModels.Include("Company").Where(c => c.Id == 18).First(); //hardcoded for demo
var oldCompany = dest.Company;
//creating a viewmodel
MyViewModel source = new MyViewModel();
source.Company = null;
source.CompanyId = null;
source.Foo = "fdsfdf";
Mapper.Map(source, dest); // company null in dest
dest.Company = oldCompany;
dest.CompanyId = oldCompany.Id;
slaumaContext.SaveChanges();
The second EDIT in #nemesv's answer or the finetuning of AutoMapper is the way to go in my opinion. You should accept his answer. I only add an explanation why your code doesn't work (but your code with setting the state twice does). First of all, the problem has nothing to do with AutoMapper, you will get the same behaviour when you set the properties manually.
The important thing to know is that setting the state ( Entry(dest).State = EntityState.Modified ) does not only set some internal flag in the context but the property setter for State calls actually some complex methods, especially it calls DbContext.ChangeTracker.DetectChanges() (if you don't disable AutoDetectChangesEnabled).
So, what happens in the first case:
// ...
Mapper.Map(source, dest);
dest.Company = oldCompany;
// at this point the state of dest EF knows about is still the state
// when you loaded the entity from the context because you are not working
// with change tracking proxies, so the values are at this point:
// dest.CompanyId = null <- this changed compared to original value
// dest.Company = company <- this did NOT change compared to original value
// The next line will call DetectChanges() internally: EF will compare the
// current property values of dest with the snapshot of the values it had
// when you loaded the entity
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
// So what did EF detect:
// dest.Company didn't change, but dest.CompanyId did!
// So, it assumes that you have set the FK property to null and want
// to null out the relationship. As a consequence, EF also sets dest.Company
// to null at this point and later saves null to the DB
What happens in the second case:
// ...
Mapper.Map(source, dest);
// Again in the next line DetectChanges() is called, but now
// dest.Company is null. So EF will detect a change of the navigation property
// compared to the original state
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
dest.Company = oldCompany;
// Now DetectChanges() will find that dest.Company has changed again
// compared to the last call of DetectChanges. As a consequence it will
// set dest.CompanyId to the correct value of dest.Company
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
// dest.Company and dest.CompanyId will have the old values now
// and SaveChanges() doesn't null out the relationship
So, this is actually normal change tracking behaviour and not a bug in EF.
One thing I find disturbing is that you have a ViewModel which apparently has properties you are not using in the view. If your ViewModel wouldn't have Company and CompanyId all the trouble would disappear. (Or configure at least AutoMapper to not map these properties, as shown by #nemesv.)

Categories

Resources