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:

Mapping to auto-properties

Perhaps the most common way to define a collection navigation property is to use a simple ICollection property:

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 when using Include and during fixup, or the application can set it to any ICollection that allows objects to be added.

EF Core 1.1 also allows a collection to be exposed as IEnumerbale. For example:

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 navigation property and use a backing field to control other mutations. For example:

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. This can be fixed by making a defensive copy, but this requires that EF always read and write directly to the field because adding entities to the defensive copy obviously won't work. There is currently no way to do this with the fluent API, but it is still easy to do by dropping to core metadata. For example:

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:

Summary

EF Core allows navigation properties to be defined in the traditional way. It also allows navigation properties to be exposed as IEnumerable. Finally, EF Core can make use of backing fields, which allows for full encapsulation of the collection.


This page is up-to-date as of October 28th, 2016. Some things change. Some things stay the same. Use your noggin.