C# 是否可以在实体框架核心中创建基于字符串的Include替换?
在API上,我需要动态包含,但EF Core不支持基于字符串的包含 因此,我创建了一个映射器,将字符串映射到添加到列表中的lambda表达式,如下所示:C# 是否可以在实体框架核心中创建基于字符串的Include替换?,c#,entity-framework-core,C#,Entity Framework Core,在API上,我需要动态包含,但EF Core不支持基于字符串的包含 因此,我创建了一个映射器,将字符串映射到添加到列表中的lambda表达式,如下所示: List<List<Expression>> expressions = new List<List<Expression>>(); List expressions=newlist(); 考虑以下特定类型: public class EFContext { public DbSet<
List<List<Expression>> expressions = new List<List<Expression>>();
List expressions=newlist();
考虑以下特定类型:
public class EFContext {
public DbSet<P1> P1s { get; set; }
public DbSet<P1> P2s { get; set; }
public DbSet<P1> P3s { get; set; }
}
public class P1 {
public P2 P2 { get; set; }
public P3 P3 { get; set; }
}
public class P2 {
public P3 P3 { get; set; }
}
public class P3 { }
公共类EFContext{
公共数据库集P1s{get;set;}
公共数据库集P2s{get;set;}
公共数据库集P3s{get;set;}
}
公开课P1{
公共p2p2{get;set;}
公共P3 P3{get;set;}
}
公共类P2{
公共P3 P3{get;set;}
}
公共类P3{}
Include和Include通常按如下方式使用:
EFContext efcontext = new EFContext();
IQueryable<P1> result = efcontext.P1s.Include(p1 => p1.P2).ThenInclude(p2 => p2.P3).Include(p1 => p1.P3);
EFContext-EFContext=new-EFContext();
IQueryable result=efcontext.P1s.Include(p1=>p1.P2)。然后Include(P2=>P2.P3)。Include(p1=>p1.P3);
它们也可以通过以下方式使用:
Expression<Func<P1, P2>> p1p2 = p1 => p1.P2;
Expression<Func<P1, P3>> p1p3 = p1 => p1.P3;
Expression<Func<P2, P3>> p2p3 = p2 => p2.P3;
List<List<Expression>> expressions = new List<List<Expression>> {
new List<Expression> { p1p2, p1p3 },
new List<Expression> { p2p3 }
};
EFContext efcontext = new EFContext();
IIncludableQueryable<P1, P2> q1 = EntityFrameworkQueryableExtensions.Include(efcontext.P1s, p1p2);
IIncludableQueryable<P1, P3> q2 = EntityFrameworkQueryableExtensions.ThenInclude(q1, p2p3);
IIncludableQueryable<P1, P3> q3 = EntityFrameworkQueryableExtensions.Include(q2, p1p3);
result = q3.AsQueryable();
表达式p1p2=p1=>p1.P2;
表达式p1p3=p1=>p1.P3;
表达式p2p3=p2=>p2.P3;
列表表达式=新列表{
新列表{p1p2,p1p3},
新列表{p2p3}
};
EFContext EFContext=新EFContext();
IIncludableQueryTableQ1=EntityFrameworkQueryableExtensions.Include(efcontext.P1s,p1p2);
IIncludableQueryable q2=EntityFrameworkQueryableExtensions。然后包括(q1,p2p3);
IIncludableQueryable q3=EntityFrameworkQueryableExtensions。包括(q2,p1p3);
结果=q3.AsQueryable();
问题是我的方法接收一个表达式列表,而我只有T中的基类型:
public static class IncludeExtensions<T> {
public static IQueryable<T> IncludeAll(this IQueryable<T> collection, List<List<Expression>> expressions) {
MethodInfo include = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.Include)).Single(mi => mi.GetParameters().Any(pi => pi.Name == "navigationPropertyPath"));
MethodInfo includeAfterCollection = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude)).Single(mi => !mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter);
MethodInfo includeAfterReference = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude)).Single(mi => mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter);
foreach (List<Expression> path in expressions) {
Boolean start = true;
foreach (Expression expression in path) {
if (start) {
MethodInfo method = include.MakeGenericMethod(typeof(T), ((LambdaExpression)expression).ReturnType);
IIncludableQueryable<T,?> result = method.Invoke(null, new Object[] { collection, expression });
start = false;
} else {
MethodInfo method = includeAfterReference.MakeGenericMethod(typeof(T), typeof(?), ((LambdaExpression)expression).ReturnType);
IIncludableQueryable <T,?> result = method.Invoke(null, new Object[] { collection, expression });
}
}
}
return collection; // (to be replaced by final as Queryable)
}
}
公共静态类IncludeExtensions{
公共静态IQueryable IncludeAll(此IQueryable集合,列表表达式){
MethodInfo include=typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.include)).Single(mi=>mi.GetParameters().Any(pi=>pi.Name==“navigationPropertyPath”);
MethodInfo includeAfterCollection=typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenClude)).Single(mi=>!mi.GetParameters()[0]。ParameterType.GenericTypeArguments[1]。IsGenericParameter);
MethodInfo includeAfterReference=typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenClude)).Single(mi=>mi.GetParameters()[0]。ParameterType.GenericTypeArguments[1]。IsGenericParameter);
foreach(表达式中的列表路径){
布尔开始=真;
foreach(路径中的表达式){
如果(启动){
MethodInfo method=include.MakeGenericMethod(typeof(T),((LambdaExpression)表达式).ReturnType);
IIncludableQueryable result=method.Invoke(null,新对象[]{collection,expression});
开始=错误;
}否则{
MethodInfo method=includeAfterReference.MakeGenericMethod(typeof(T)、typeof(?)((LambdaExpression)表达式).ReturnType);
IIncludableQueryable result=method.Invoke(null,新对象[]{collection,expression});
}
}
}
退货托收;//(以可查询的最终版本替换)
}
}
主要问题是为每个Include和Include步骤以及要使用的Include解决了正确的类型
这在当前的EF7内核中是可能的吗?有人找到了动态包含的解决方案吗
and和方法是存储库中类的一部分。在查询上创建“IncludeAll”扩展需要一种与您最初所做的不同的方法
EF核心是这样的。当它看到.Include
方法时,它将此表达式解释为创建其他查询。(见和)
一种方法是添加一个额外的表达式访问者来处理IncludeAll扩展。另一种(可能更好)方法是将表达式树从.includeal解释为适当的
.Includes
,然后让EF正常处理Includes。任何一个的实现都是非常重要的,超出了SO答案的范围。更新:
从v1.1.0开始,基于字符串的include现在是efcore的一部分,因此该问题和下面的解决方案已经过时
原始答案:
周末的有趣运动
解决方案:
我最终采用了以下扩展方法:
public static class IncludeExtensions
{
private static readonly MethodInfo IncludeMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo()
.GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.Include)).Single(mi => mi.GetParameters().Any(pi => pi.Name == "navigationPropertyPath"));
private static readonly MethodInfo IncludeAfterCollectionMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo()
.GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude)).Single(mi => !mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter);
private static readonly MethodInfo IncludeAfterReferenceMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo()
.GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude)).Single(mi => mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter);
public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> source, params string[] propertyPaths)
where TEntity : class
{
var entityType = typeof(TEntity);
object query = source;
foreach (var propertyPath in propertyPaths)
{
Type prevPropertyType = null;
foreach (var propertyName in propertyPath.Split('.'))
{
Type parameterType;
MethodInfo method;
if (prevPropertyType == null)
{
parameterType = entityType;
method = IncludeMethodInfo;
}
else
{
parameterType = prevPropertyType;
method = IncludeAfterReferenceMethodInfo;
if (parameterType.IsConstructedGenericType && parameterType.GenericTypeArguments.Length == 1)
{
var elementType = parameterType.GenericTypeArguments[0];
var collectionType = typeof(ICollection<>).MakeGenericType(elementType);
if (collectionType.IsAssignableFrom(parameterType))
{
parameterType = elementType;
method = IncludeAfterCollectionMethodInfo;
}
}
}
var parameter = Expression.Parameter(parameterType, "e");
var property = Expression.PropertyOrField(parameter, propertyName);
if (prevPropertyType == null)
method = method.MakeGenericMethod(entityType, property.Type);
else
method = method.MakeGenericMethod(entityType, parameter.Type, property.Type);
query = method.Invoke(null, new object[] { query, Expression.Lambda(property, parameter) });
prevPropertyType = property.Type;
}
}
return (IQueryable<TEntity>)query;
}
}
工作原理:
给定类型TEntity
和形式为Prop1.Prop2…PropN
的字符串属性路径,我们拆分路径并执行以下操作:
对于第一个属性,我们只需通过反射调用EntityFrameworkQueryableExtensions。包括方法:
public static IIncludableQueryable<TEntity, TProperty>
Include<TEntity, TProperty>
(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TProperty>> navigationPropertyPath
)
及
公共静态IIncludableQueryable
然后包括
(
这包括可查询源,
表达式导航属性路径
)
source
是当前结果tenty
对于所有调用都是相同的。但是什么是TPreviousProperty
,以及我们如何决定调用哪个方法
首先,我们使用一个变量来记住上一次调用中的t属性是什么。然后检查它是否是集合属性类型,如果是,则使用从集合类型的泛型参数提取的TPreviousProperty
类型调用第一个重载,否则只需使用该类型调用第二个重载
就这些。没什么特别的,只是通过反射模拟显式的Include
/然后Include
调用链。EF Core 1.1中提供的基于字符串的Include()
。我建议您尝试升级并删除必须添加到代码中以解决此限制的任何变通方法。EF Core 1.1中提供的基于字符串的Include()。如果保留此扩展名,将出现错误“找到不明确匹配”。我花了半天时间寻找这个错误的解决方案。
var db = new MyDbContext();
// Sample query using Include/ThenInclude
var queryA = db.P3s
.Include(e => e.P1s)
.ThenInclude(e => e.P2)
.ThenInclude(e => e.P4)
.Include(e => e.P1s)
.ThenInclude(e => e.P3);
// The same query using string Includes
var queryB = db.P3s
.Include("P1s.P2.P4", "P1s.P3");
public static IIncludableQueryable<TEntity, TProperty>
Include<TEntity, TProperty>
(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TProperty>> navigationPropertyPath
)
public static IIncludableQueryable<TEntity, TProperty>
ThenInclude<TEntity, TPreviousProperty, TProperty>
(
this IIncludableQueryable<TEntity, ICollection<TPreviousProperty>> source,
Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath
)
public static IIncludableQueryable<TEntity, TProperty>
ThenInclude<TEntity, TPreviousProperty, TProperty>
(
this IIncludableQueryable<TEntity, TPreviousProperty> source,
Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath
)