EF Core allows us to omit foreign key properties since the presence of navigation properties is sufficient to establish a relationship between two entities. EF Core will then create what's called a foreign key shadow property on its own under the hood.
I'll use an example similar to the one in the docs:
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public Blog Blog { get; set; }
}
Now, given a structure like this, where there are no explicit foreign key properties, how would you create a new Post for a Blog with a given ID?
I've tried this:
db.Posts.Add(new Post() {
Title = "Some title",
Course = new Blog { Id = 1234 },
});
db.SaveChanges();
But it doesn't seem to work. This seems like a simple problem, but to my surprise, there's no information about it neither in the documentation nor anywhere else I visited.
If you are using Shadow FKs then there are a number of approaches you can use. The first is to fetch the related entity. This means going to the DB, but the benefit is that this asserts that the Blog ID you assume should be valid is valid:
var blog = db.Blogs.Single(x => x.Id == blogId);
var post = new Post
{
Title = title,
Blog = blog
};
db.Posts.Add(post);
db.SaveChanges();
As a general rule you shouldn't trust anything coming from a client. If you do want to assume the ID is valid and don't want the round trip then the safe approach is:
var blog = db.Blogs.Local.SingleOrDefault(x => x.Id == blogId);
if (blog == null)
{
blog = new Blog { Id = blogId };
db.Attach(blog);
}
var post = new Post
{
Title = title,
Blog = blog
};
db.Posts.Add(post);
db.SaveChanges();
This checks the DbContext for a locally cached instance of the Blog (without hitting the DB) and using that if found. If not found, we create one and attach it, then associate that with the Post. We should check the local cache before attaching an instance because if you skip that step and for any reason the DbContext was already tracking a Blog with that ID, the Attach call will fail.
The one caveat of using this approach is that unlike fetching the actual Blog entry, the created Blog is not a complete blog so sending that Post or such anywhere that might expect post.Blog to be a complete blog (such as to get the Name, author, etc.) will not be able to see any details about the blog. This should only be used if you trust the inputs and the entities created don't get referenced beyond creating the required row in the DB.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With