Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
139 views
in Technique[技术] by (71.8m points)

c# - Multiple Includes() in EF Core

I have an extension method that lets you generically include data in EF:

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
    where T : class
{
    if (includes != null)
    {
        query = includes.Aggregate(query, (current, include) => current.Include(include));
    }
    return query;
}

This allows me to have methods in my repository like this:

public Patient GetById(int id, params Expression<Func<Patient, object>>[] includes)
{
    return context.Patients
        .IncludeMultiple(includes)
        .FirstOrDefault(x => x.PatientId == id);
}

I believe the extension method worked before EF Core, but now including "children" is done like this:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author);

Is there a way to alter my generic extension method to support EF Core's new ThenInclude() practice?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As said in comments by other, you can take EF6 code to parse your expressions and apply the relevant Include/ThenInclude calls. It does not look that hard after all, but as this was not my idea, I would rather not put an answer with the code for it.

You may instead change your pattern for exposing some interface allowing you to specify your includes from the caller without letting it accessing the underlying queryable.

This would result in something like:

using YourProject.ExtensionNamespace;

// ...

patientRepository.GetById(0, ip => ip
    .Include(p => p.Addresses)
    .ThenInclude(a=> a.Country));

The using on namespace must match the namespace name containing the extension methods defined in the last code block.

GetById would be now:

public static Patient GetById(int id,
    Func<IIncludable<Patient>, IIncludable> includes)
{
    return context.Patients
        .IncludeMultiple(includes)
        .FirstOrDefault(x => x.EndDayID == id);
}

The extension method IncludeMultiple:

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
    Func<IIncludable<T>, IIncludable> includes)
    where T : class
{
    if (includes == null)
        return query;

    var includable = (Includable<T>)includes(new Includable<T>(query));
    return includable.Input;
}

Includable classes & interfaces, which are simple "placeholders" on which additional extensions methods will do the work of mimicking EF Include and ThenInclude methods:

public interface IIncludable { }

public interface IIncludable<out TEntity> : IIncludable { }

public interface IIncludable<out TEntity, out TProperty> : IIncludable<TEntity> { }

internal class Includable<TEntity> : IIncludable<TEntity> where TEntity : class
{
    internal IQueryable<TEntity> Input { get; }

    internal Includable(IQueryable<TEntity> queryable)
    {
        // C# 7 syntax, just rewrite it "old style" if you do not have Visual Studio 2017
        Input = queryable ?? throw new ArgumentNullException(nameof(queryable));
    }
}

internal class Includable<TEntity, TProperty> :
    Includable<TEntity>, IIncludable<TEntity, TProperty>
    where TEntity : class
{
    internal IIncludableQueryable<TEntity, TProperty> IncludableInput { get; }

    internal Includable(IIncludableQueryable<TEntity, TProperty> queryable) :
        base(queryable)
    {
        IncludableInput = queryable;
    }
}

IIncludable extension methods:

using Microsoft.EntityFrameworkCore;

// others using ommitted

namespace YourProject.ExtensionNamespace
{
    public static class IncludableExtensions
    {
        public static IIncludable<TEntity, TProperty> Include<TEntity, TProperty>(
            this IIncludable<TEntity> includes,
            Expression<Func<TEntity, TProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity>)includes).Input
                .Include(propertySelector);
            return new Includable<TEntity, TProperty>(result);
        }

        public static IIncludable<TEntity, TOtherProperty>
            ThenInclude<TEntity, TOtherProperty, TProperty>(
                this IIncludable<TEntity, TProperty> includes,
                Expression<Func<TProperty, TOtherProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity, TProperty>)includes)
                .IncludableInput.ThenInclude(propertySelector);
            return new Includable<TEntity, TOtherProperty>(result);
        }

        public static IIncludable<TEntity, TOtherProperty>
            ThenInclude<TEntity, TOtherProperty, TProperty>(
                this IIncludable<TEntity, IEnumerable<TProperty>> includes,
                Expression<Func<TProperty, TOtherProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity, IEnumerable<TProperty>>)includes)
                .IncludableInput.ThenInclude(propertySelector);
            return new Includable<TEntity, TOtherProperty>(result);
        }
    }
}

IIncludable<TEntity, TProperty> is almost like IIncludableQueryable<TEntity, TProperty> from EF, but it does not extend IQueryable and does not allow reshaping the query.

Of course if the caller is in the same assembly, it can still cast the IIncludable to Includable and start fiddling with the queryable. But well, if someone wants to get it wrong, there is no way we would prevent him doing so (reflection allows anything). What does matter is the exposed contract.

Now if you do not care about exposing IQueryable to the caller (which I doubt), obviously just change your params argument for a Func<Queryable<T>, Queryable<T>> addIncludes argument, and avoid coding all those things above.

And the best for the end: I have not tested this, I do not use Entity Framework currently!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...