EF Core 中的多个 Includes() [英] Multiple Includes() in EF Core
问题描述
我有一个扩展方法,可以让您在 EF 中一般包含数据:
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);
}
我相信扩展方法在 EF Core 之前有效,但现在包括孩子"是这样完成的:
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);
有没有办法改变我的通用扩展方法来支持 EF Core 的新 ThenInclude()
实践?
Is there a way to alter my generic extension method to support EF Core's new ThenInclude()
practice?
推荐答案
如 评论 其他人,您可以参考 EF6 代码 来解析您的表达式并应用相关的 Include
/ThenInclude
调用.毕竟看起来并不那么难,但由于这不是我的想法,我宁愿不提供代码的答案.
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.
这将导致类似:
using YourProject.ExtensionNamespace;
// ...
patientRepository.GetById(0, ip => ip
.Include(p => p.Addresses)
.ThenInclude(a=> a.Country));
命名空间上的 using
必须匹配包含在最后一个代码块中定义的扩展方法的命名空间名称.
The using
on namespace must match the namespace name containing the extension methods defined in the last code block.
GetById
现在是:
public static Patient GetById(int id,
Func<IIncludable<Patient>, IIncludable> includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.EndDayID == id);
}
扩展方法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
类和接口,它们是简单的占位符",附加扩展方法将在其上完成模仿 EF Include
和 ThenInclude
方法的工作:
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;
}
}
IIcludable
扩展方法:
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);
}
}
}
IIcludable
几乎就像 EF 的 IIncludableQueryable
,但它没有扩展 IQueryable
不允许重塑查询.
IIncludable<TEntity, TProperty>
is almost like IIncludableQueryable<TEntity, TProperty>
from EF, but it does not extend IQueryable
and does not allow reshaping the query.
当然,如果调用者在同一个程序集中,它仍然可以将 IIcludable
转换为 Includable
并开始摆弄可查询的.但是好吧,如果有人想弄错,我们没有办法阻止他这样做(反思允许任何事情).重要的是暴露的合同.
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.
现在,如果您不关心将 IQueryable
暴露给调用者(我对此表示怀疑),显然只需将 params
参数更改为 Func
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!
这篇关于EF Core 中的多个 Includes()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!