Entity Framework won't detect changes of navigation properties

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 the e.f. 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 the SaveChanges, the record is saved correctly (the Address become null)!

Edit 2: I've created the foreign key property as billy suggested and 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?

Answers


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...

Need Your Help

Validation Failed: "EntityRef: expecting ';'"

xml validation xml-validation character-reference

Hi I've got some XML that won't validate. I've narrowed down the problem to this bit:

What happens if you kill a long-running alter query?

mysql kill alter

What happens if you kill a long-running alter query? Will the alter query simply revert? How long could that take (as a proportion of the time it has already been running)?