I have an app where I create entities in one database context, and update them in the database using a different database context. The changes to the entity are made outside of a context. This works for regular properties like the Url but setting the Post navigation property to null doesn't update the PostId in the database. Blog.PostId is a shadow foreign key property created by EF.
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public Post Post { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
This is the code for a simple test (using XUnit)
Post p = new Post();
Blog b = new Blog() { Post = p };
//Add to the database in one context
using (var db = new DatabaseContext())
{
db.Add(b);
db.SaveChanges();
}
//Make changes outside of a context
b.Post = null;
b.Url = "the url";
//Update blog in a different context
using (var db = new DatabaseContext())
{
var e = db.Update(b);
output.WriteLine(e.Property("PostId").CurrentValue + ""); //prints empty string
output.WriteLine(e.Property("PostId").OriginalValue + ""); //prints empty string
output.WriteLine(e.Property("PostId").IsModified.ToString()); //prints "False"
output.WriteLine(e.Property("Url").CurrentValue + ""); //prints "the url"
output.WriteLine(e.Property("Url").OriginalValue + ""); //prints "the url"
output.WriteLine(e.Property("Url").IsModified.ToString()); //prints "True"
db.SaveChanges();
var blog = db.Blogs.Include(x => x.Post).Single();
Assert.NotNull(blog.Post); //this test incorrectly passes as this should be null
Assert.Equal("the url", blog.Url);
}
When setting a navigation property to null, the change tracker doesn't consider the property as modified. From the output of the test case, it appears that the problem isn't that the CurrentValue and OriginalValue are the same, as it works for urls. AND, if you change the above code so that b.Post is initialized as null and later updated to be b.Post = p the PostId is correctly updated in the database.
The workaround I found was to explicitly define the foreign key
ie by adding public int? PostId {get; set;} to the Blog class and setting both blog.Post and blog.PostId to be null
I don't think you should have to use a workaround like this since EF gives you the option not to explicitly create foreign key properties, however I imagine this behavior might exist since you can load a Blog item without including the Posts, and wouldn't want to accidentally remove the Post from the blog.
EF Core version: 1.0.0 (Sqlite with 1.0.0-preview2-final Tools package)
Operating system: Win 10 1607
Visual Studio version: VS 2015
@smnbackwards When you attach the blog to the second context, EF has no knowledge of what went on before in the previous context. EF does not, as you said, know that the Post navigation property has been set to null after loading, as opposed to just being null because it is not loaded. Therefore it would be wrong to send an update to the database setting the FK to null.
The easiest way to deal with this kind of change to disconnected entities is to do what you suggest and map your FKs to properties, and then make the changes through the FK since this is unambiguous. Another thing you can do is explicitly set the shadow FK to null after attaching to the context:
``` C#
db.Entry(blog).Property("PostId").CurrentValue = null;
This tells EF that you know the value of PostId and that it is null--previously even though the value was null EF had a flag set indicating that the value was unknown.
I think you should also be able to do this:
``` C#
db.Entry(blog).Property("PostId").IsModified = true;
but I need to check that that actually works.
In the 1.1 codebase you should be able to do this:
C#
db.Entry(blog).Property(e => e.Post).IsModified = true;
This is because we made a change such that setting a navigation property as modified will set the underlying FK as modified. This is nice because you don't have to have any knowledge of the FK name or use a string to address it.
.IsModified() is not an option in .NET Core 1.1.1
Most helpful comment
@smnbackwards When you attach the blog to the second context, EF has no knowledge of what went on before in the previous context. EF does not, as you said, know that the Post navigation property has been set to null after loading, as opposed to just being null because it is not loaded. Therefore it would be wrong to send an update to the database setting the FK to null.
The easiest way to deal with this kind of change to disconnected entities is to do what you suggest and map your FKs to properties, and then make the changes through the FK since this is unambiguous. Another thing you can do is explicitly set the shadow FK to null after attaching to the context:
``` C#
db.Entry(blog).Property("PostId").CurrentValue = null;
but I need to check that that actually works.
In the 1.1 codebase you should be able to do this:
C# db.Entry(blog).Property(e => e.Post).IsModified = true;This is because we made a change such that setting a navigation property as modified will set the underlying FK as modified. This is nice because you don't have to have any knowledge of the FK name or use a string to address it.