Implementing Missing Features in Entity Framework Core – Part 2

This is the second in a series of posts about missing functionality in EF Core. You can find the first here.

Entity Framework used to support three ways to load related entities (one to one, one to many, many to one, many to many):

Entity Framework Core doesn’t include (yet) the explicit loading and lazy loading mechanisms. Lazy loading is not included because EF Core does not generate proxies for the entities it loads, and I guess nobody bothered to implement explicit loading. Well, that’s exactly what we’ll do here!

The idea is, even if we didn’t require a collection to be loaded when we issued the query, we can still load it afterwards. We should keep a syntax similar to the previous version, when this feature was available:

//load a blog

var blog = ctx.Blogs.Single(b => b.BlogId == 1);

 

//Posts collection is empty

blog.Posts.Count(); //0

 

//eager load all Posts

ctx.Entry(blog).Load(b => b.Posts);

 

//Posts collection is no longer empty

blog.Posts.Count(); //!= 0

 

We will create an extension method over EntityEntry<T>:

public static void Load<TSource, TDestination>(this EntityEntry<TSource> entry, Expression<Func<TSource, IEnumerable<TDestination>>> path, Expression<Func<TDestination, TSource>> pathBack = null) where TSource : class where TDestination : class

{

    var entity = entry.Entity;

    var context = entry.Context;

    var entityType = context.Model.FindEntityType(typeof(TSource));

    var keys = entityType.GetKeys();

    var keyValues = context.GetEntityKey(entity);

    var query = context.Set<TDestination>() as IQueryable<TDestination>;

    var parameter = Expression.Parameter(typeof(TDestination), "x");

    PropertyInfo foreignKeyProperty = null;

 

    if (pathBack == null)

    {

        foreignKeyProperty = typeof(TDestination).GetProperties().Single(p => p.PropertyType == typeof(TSource));

    }

    else

    {

        foreignKeyProperty = (pathBack.Body as MemberExpression).Member as PropertyInfo;

    }

 

    var i = 0;

 

    foreach (var property in keys.SelectMany(x => x.Properties))

    {

        var keyValue = keyValues[i];

 

        var expression = Expression.Lambda(

                Expression.Equal(

                    Expression.Property(Expression.Property(parameter, foreignKeyProperty.Name), property.Name),

                    Expression.Constant(keyValue)),

                parameter) as Expression<Func<TDestination, bool>>;

 

        query = query.Where(expression);

 

        i++;

    }

 

    var list = query.ToList();

 

    var prop = (path.Body as MemberExpression).Member as PropertyInfo;

    prop.SetValue(entity, list);

}

The GetEntityKey method was introduced in the previous post, but I will add it here, for your convenience:

public static object[] GetEntityKey<T>(this DbContext context, T entity) where T : class

{

    var state = context.Entry(entity);

    var metadata = state.Metadata;

    var key = metadata.FindPrimaryKey();

    var props = key.Properties.ToArray();

 

    return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();

}

You can see that the Load method takes two parameters: the first is required, it represents the collection that we with to load, the second is optional, and it represents the go-back property, in case there is more than one property of the root entity’s type. For example:

ctx.Entry(blog).Load(b => b.Posts, p => p.Blog);

In this case, the path p => p.Blog is needless, because a Post only belongs to one Blog.

A similar approach can be used to load explicitly other kinds of relations (one to one and many to one).

Hope you find this useful! Winking smile

Published by

Ricardo Peres

Tech Lead at RedLight Software.

2 thoughts on “Implementing Missing Features in Entity Framework Core – Part 2”

  1. Hi,

    Great article. However, I was wondering if you have considered the case for 1-to-zero-or-1 relationships, in which the nagivational property is NOT a collection? Your code seems to break in that case because it cannot find the foreign key.

    Do you have a workaround for that?

Leave a Reply

Your email address will not be published. Required fields are marked *