对于EF6,我在通用存储库中有一个方法公开给所有服务层,以便根据需要从数据库中检索具有任何嵌套属性的实体:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
var queryable = set.AsQueryable();
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
这样,我可以通过以下方式使用该方法:
var data = repo.OldMethod(x => x.Papers, => x.People.Select(y => y.Addresses)).ToList();
在EF6中,这将在每个人上加载Papers
导航属性,People
导航属性和Addresses
导航属性。正如预期的那样,这会在EFCore中引发异常。由于切换到EFCore中的Include-> ThenInclude方法,因此我不确定如何在我的服务层轻松地复制它,而我不希望有关EntityFramework的任何信息。
答案 0 :(得分:3)
自EF Core最初发布以来,已经多次询问了此问题。早期的EF Core发行版甚至都支持它,但是后来它从EF Core代码中删除了(我想是为了推广新的Include
/ ThenInclude
模式)。
尽管Include
/ ThenInclude
模式看起来更清晰(除了当前的Intellisense问题),它还有一个主要缺点-需要访问EntityFrameworkQueryableExtensions
,因此引用了Microsoft.EntityFrameworkCore
部件。 params
Expression>`模式没有此要求。
好处是,可以相对容易地添加该功能。 EF6源代码可在GitHub上公开获得,从那里我们可以看到它使用了一种名为TryParsePath的方法来构建点分隔的字符串路径,然后将其传递给string
的{{1}}重载}方法。
同样可以在EF Core中应用。我们可能可以使用EF6代码,但是我将提供自己的版本。可以很容易地看出,受支持的构造是成员访问器或对带有两个参数的名为Include
的方法的调用,第二个是Select
。
以下是我对上述内容的解释,封装在两个自定义扩展方法中:
LambdaExpression
第一个仅仅是调用多个using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore
{
public static class IncludeExtensions
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> includePaths) where T : class
=> includePaths.Aggregate(source, (query, path) => query.Include(path));
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> includePaths) where T : class
=> source.Include(includePaths.Select(e => GetIncludePath(e?.Body)));
static string GetIncludePath(Expression source, bool allowParameter = false)
{
if (allowParameter && source is ParameterExpression)
return null; // ok
if (source is MemberExpression member)
return CombinePaths(GetIncludePath(member.Expression, true), member.Member.Name);
if (source is MethodCallExpression call && call.Method.Name == "Select"
&& call.Arguments.Count == 2 && call.Arguments[1] is LambdaExpression selector)
return CombinePaths(GetIncludePath(call.Arguments[0]), GetIncludePath(selector.Body));
throw new Exception("Invalid Include path.");
}
static string CombinePaths(string path1, string path2)
=> path1 != null ? path1 + "." + path2 : path2;
}
}
包含项的助手(摘自我对Entity Framework Core 2.0.1 Eager Loading on all nested related entities的回答)。第二个是有问题的方法,该方法将表达式转换为字符串并调用第一个。主要工作是通过string
私有方法完成的,该方法根据上述规则以及一个附加规则递归处理表达式-在自下而上导航时,应以lambda参数结尾。
现在该方法的实现很简单,因为问题是这样的:
GetIncludePath
答案 1 :(得分:1)
当我第一次开始使用EFCore(从EF6切换)时,我构建了这些扩展方法,将包括x => x.People.Select(y => y.Addresses)
在内的“旧”方式转换为"People.Addresses"
之类的字符串,EFCore也支持这些字符串;
public static class Extensions
{
private class ReferencedPropertyFinder : ExpressionVisitor
{
private readonly Type _ownerType;
private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();
private Expression _parameterExpression;
private int _currentPosition = 0;
public ReferencedPropertyFinder(Type ownerType)
{
_ownerType = ownerType;
}
public IReadOnlyList<PropertyInfo> Properties
{
get { return _properties; }
}
protected override Expression VisitMember(MemberExpression node)
{
var propertyInfo = node.Member as PropertyInfo;
if (propertyInfo != null) {
var currentParameter = GetParameter(node);
if (_parameterExpression == currentParameter) {
_properties.Insert(_currentPosition, propertyInfo);
} else {
_properties.Add(propertyInfo);
_parameterExpression = currentParameter;
_currentPosition = _properties.Count() - 1;
}
}
return base.VisitMember(node);
}
private ParameterExpression GetParameter(MemberExpression node)
{
if (node.Expression is ParameterExpression) {
return (ParameterExpression)node.Expression;
} else {
return GetParameter((MemberExpression)node.Expression);
}
}
}
private static IReadOnlyList<PropertyInfo> GetReferencedProperties<T, U>(this Expression<Func<T, U>> expression)
{
var v = new ReferencedPropertyFinder(typeof(T));
v.Visit(expression);
return v.Properties;
}
public static string ToPropertyPath<T>(this Expression<Func<T, object>> expression)
{
var properties = expression.GetReferencedProperties();
var path = string.Join(".", properties.Select(x => x.Name));
return path;
}
}
将这些内容合并到您的代码中,您可以这样说:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
var queryable = set.AsQueryable();
return includeProperties.Aggregate(queryable, (current, includeProperty) =>
current.Include(includeProperty.ToPropertyPath()));
}