EF Core 1.1
Collection navigation properties and fields in EF Core 1.1
There has recently been some confusion about what mappings are supported for collection navigation properties in EF Core. This post is an attempt to clear things up by showing:
- What types of collection are supported
- When the backing field can be used directly
Mapping to auto-properties
Perhaps the most common way to define a collection navigation property is to use a simple ICollection
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; set; }
}
EF will create the collection for you, usually as a HashSet
EF Core 1.1 also allows a collection to be exposed as IEnumerbale
public class Blog
{
public int Id { get; set; }
public IEnumerable<Post> Posts { get; set; }
}
This means that there is no public surface for adding entities to the collection. The collection itself still needs to be a proper collection with a working Add method, and EF will still create one for you, or the application can create it itself.
There is also no need for a setter as long as the entity always has a collection created so that EF never needs to do it.
public class Blog
{
public int Id { get; set; }
public IEnumerable<Post> Posts { get; } = new List<Post>();
}
Properties with backing fields
Using an Add method
Taking this one step further, you can expose your IEnumerable
public class Blog
{
private readonly List<Post> _posts = new List<Post>();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts;
public void AddPost(Post post)
{
// Do some buisness logic here...
_posts.Add(post);
}
}
Using a defensive copy
In all of the examples above the Posts navigation property returns the actual collection. This means that application code could add entities directly by casting to an ICollection
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var navigation = modelBuilder.Entity<Blog>().Metadata.FindNavigation(nameof(Blog.Posts));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}
Adding fluent API for this is being tracked by issue 6674.
Once we tell EF to always use the field we can get something like this:
public class Blog
{
private readonly List<Post> _posts = new List<Post>();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts.ToList();
public void AddPost(Post post)
{
// Do some buisness logic here...
_posts.Add(post);
}
}
In this example:
- EF accesses the navigation property directly through the field
- Mutation of the collection is controlled by
- Creating a defensive copy
- Using an Add method with business logic
Summary
EF Core allows navigation properties to be defined in the traditional way. It also allows navigation properties to be exposed as IEnumerable