c# - 如何将现有的 IQueryable 元素插入到 EF Core 中的 ThenInclude 方法
问题描述
如何IQueryable
在方法中插入现有元素ThenInclude
?
public IQueryable<Store> Store => GetDbSet<Store>()
.Include(st => st.App)
.ThenInclude(app => app.Client)
.ThenInclude(cl => cl.Country)
.ThenInclude(co => co.Culture)
.Include(st => st.Features)
.ThenInclude(this.StoreFeatures);
public IQueryable<StoreFeatures> StoreFeatures => GetDbSet<StoreFeatures>()
.Include(ft => ft.Cultures)
.ThenInclude(ct => ct.Culture);
解决方案
有趣的问题。
问题是Include
/ ThenInclude
chain 是不可组合的。理论上,可以从IQueryable
表达式中提取链,然后Include
将其转换为ThenInclude
。
但这还不够。所有这些调用都返回IIncludableQueryable<TEntity, TProperty>
,其中 theTEntity
来自原始IQueryable
。因此,ThneInclude
呼叫也需要重新映射。
另一个问题是当包含链包含多个Include
调用时。除了Include
第一个“重新启动”链,因此应在将原始链转换为ThenInclude
.
话虽如此,以下是执行此操作的示例实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
namespace Microsoft.EntityFrameworkCore
{
public static class IncludeExtensions
{
const string IncludeMethodName = nameof(EntityFrameworkQueryableExtensions.Include);
const string ThenIncludeMethodName = nameof(EntityFrameworkQueryableExtensions.ThenInclude);
public static IQueryable<TEntity> ThenInclude<TEntity, TProperty>(
this IIncludableQueryable<TEntity, IEnumerable<TProperty>> source,
IQueryable<TProperty> includes) => source.Include(includes);
public static IQueryable<TEntity> ThenInclude<TEntity, TProperty>(
this IIncludableQueryable<TEntity, TProperty> source,
IQueryable<TProperty> includes) => source.Include(includes);
static IQueryable<TEntity> Include<TEntity, TProperty>(
this IQueryable<TEntity> source, IQueryable<TProperty> includes)
{
var targetChain = GetIncludeChain(includes.Expression);
if (targetChain.Count == 0) return source;
var sourceChain = GetIncludeChain(source.Expression);
var result = source.Expression;
foreach (var targetInclude in targetChain)
{
bool isInclude = targetInclude.Method.Name == IncludeMethodName;
if (isInclude && result != source.Expression)
{
result = sourceChain.Aggregate(result, (r, i) =>
Expression.Call(i.Method, r, i.Arguments[1]));
}
var typeArgs = targetInclude.Method.GetGenericArguments();
var prevPropertyType = isInclude ? typeof(TProperty) : typeArgs[1];
var propertyType = typeArgs[isInclude ? 1 : 2];
result = Expression.Call(
typeof(EntityFrameworkQueryableExtensions), ThenIncludeMethodName,
new[] { typeof(TEntity), prevPropertyType, propertyType },
result, targetInclude.Arguments[1]);
}
return source.Provider.CreateQuery<TEntity>(result);
}
static Stack<MethodCallExpression> GetIncludeChain(Expression source)
{
var result = new Stack<MethodCallExpression>();
while (source is MethodCallExpression methodCall && methodCall.IsIncludeOrThenInclude())
{
result.Push(methodCall);
source = methodCall.Arguments[0];
}
return result;
}
static bool IsIncludeOrThenInclude(this MethodCallExpression source)
=> source.Method.DeclaringType == typeof(EntityFrameworkQueryableExtensions)
&& source.Method.IsGenericMethod
&& (source.Method.Name == IncludeMethodName || source.Method.Name == ThenIncludeMethodName);
}
}
两个自定义ThenInclude
方法重载是为了支持引用和集合导航属性(类似于标准ThenInclude
重载)。
现在您的示例将编译并将第二个查询包含插入到第一个查询包含链中。
推荐阅读
- docker - 使用 JDBC 输入插件在 Docker 上的 Logstash 不会从 SQL Server 获取所有行
- flutter - 如何突出显示我在字符串中搜索的确切单词,而不仅仅是字符串的开头?
- flutter - 如何实时获取文档长度
- javascript - Reactjs 从获取请求中返回一个对象
- javascript - Nginx 正在尝试打开文件而不是重定向到代理
- python - _joint_log_likelihood 给我错误的值
- sql - 如何按日期获取最后一件商品的价格
- python-3.x - 定期执行功能而不停止其他操作
- r - 使用 R 从 PDF 表单到数据框的文本挖掘
- r - 如果 r 中出现错误,则转到 lapply() 的下一次迭代