C# 如何为.编写存储库方法,然后将其包含在EF Core 2中
我正在尝试为Entity Framework Core 2.0编写一个repository方法,该方法可以使用.ThenClude处理返回属性的子集合,但第二个表达式有问题。以下是.Include的工作方法,它将返回实体的子属性(您提供了lambda列表)C# 如何为.编写存储库方法,然后将其包含在EF Core 2中,c#,lambda,.net-core,entity-framework-core,repository-pattern,C#,Lambda,.net Core,Entity Framework Core,Repository Pattern,我正在尝试为Entity Framework Core 2.0编写一个repository方法,该方法可以使用.ThenClude处理返回属性的子集合,但第二个表达式有问题。以下是.Include的工作方法,它将返回实体的子属性(您提供了lambda列表) public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] incl
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
public T GetSingle(表达式谓词,参数表达式[]includeProperties)
{
IQueryable查询=_context.Set();
foreach(includeProperty中的var includeProperty)
{
query=query.Include(includeProperty);
}
返回query.Where(谓词).FirstOrDefault();
}
现在,我尝试编写一个方法,该方法将获取两个表达式的元组,并将它们馈送到.Include(a=>a.someChild).然后Include(b=>b.aChildOfSomeChild)链中。这不是一个完美的解决方案,因为它只处理一个孩子的一个孩子,但这只是一个开始
public T GetSingle(Expression<Func<T, bool>> predicate, params Tuple<Expression<Func<T, object>>, Expression<Func<T, object>>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty.Item1).ThenInclude(includeProperty.Item2);
}
return query.Where(predicate).FirstOrDefault();
}
public T GetSingle(表达式谓词,参数元组[]includeProperties)
{
IQueryable查询=_context.Set();
foreach(includeProperty中的var includeProperty)
{
query=query.Include(includeProperty.Item1),然后Include(includeProperty.Item2);
}
返回query.Where(谓词).FirstOrDefault();
}
Intellisense返回一个错误,提示“无法从用法推断类型,请尝试显式指定类型”。我有一种感觉,这是因为Item2中的表达式需要被归类为与Item1有某种关联,因为它需要知道它的子关系
写这样的方法有什么想法或更好的技巧吗 我也遇到了同样的问题,因为EF Core不支持延迟加载,但我尝试通过以下方式解决此问题: 首先创建一个属性类,从给定类的其他属性中标记所需的导航属性
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class NavigationPropertyAttribute : Attribute
{
public NavigationPropertyAttribute()
{
}
}
使用基于字符串的即时加载筛选出导航属性并应用Include/thenclude的扩展方法
public static class DbContextHelper
{
public static Func<IQueryable<T>, IQueryable<T>> GetNavigations<T>() where T : BaseEntity
{
var type = typeof(T);
var navigationProperties = new List<string>();
//get navigation properties
GetNavigationProperties(type, type, string.Empty, navigationProperties);
Func<IQueryable<T>, IQueryable<T>> includes = ( query => {
return navigationProperties.Aggregate(query, (current, inc) => current.Include(inc));
});
return includes;
}
private static void GetNavigationProperties(Type baseType, Type type, string parentPropertyName, IList<string> accumulator)
{
//get navigation properties
var properties = type.GetProperties();
var navigationPropertyInfoList = properties.Where(prop => prop.IsDefined(typeof(NavigationPropertyAttribute)));
foreach (PropertyInfo prop in navigationPropertyInfoList)
{
var propertyType = prop.PropertyType;
var elementType = propertyType.GetTypeInfo().IsGenericType ? propertyType.GetGenericArguments()[0] : propertyType;
//Prepare navigation property in {parentPropertyName}.{propertyName} format and push into accumulator
var properyName = string.Format("{0}{1}{2}", parentPropertyName, string.IsNullOrEmpty(parentPropertyName) ? string.Empty : ".", prop.Name);
accumulator.Add(properyName);
//Skip recursion of propert has JsonIgnore attribute or current property type is the same as baseType
var isJsonIgnored = prop.IsDefined(typeof(JsonIgnoreAttribute));
if(!isJsonIgnored && elementType != baseType){
GetNavigationProperties(baseType, elementType, properyName, accumulator);
}
}
}
}
存储库中的使用
public async Task<T> GetAsync(Expression<Func<T, bool>> predicate)
{
Func<IQueryable<T>, IQueryable<T>> includes = DbContextHelper.GetNavigations<T>();
IQueryable<T> query = _context.Set<T>();
if (includes != null)
{
query = includes(query);
}
var entity = await query.FirstOrDefaultAsync(predicate);
return entity;
}
我在网上找到了这个存储库方法,它正是我想要的。亚瑞德的回答是好的,但并非完全正确
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public TResult GetFirstOrDefault<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (orderBy != null)
{
return orderBy(query).Select(selector).FirstOrDefault();
}
else
{
return query.Select(selector).FirstOrDefault();
}
}
回到EF6,我们可以这样写:
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));
它完美而简单。我们可以在存储库中公开它,而无需将引用从EF程序集拖到其他项目
这已从EF Core中删除,但由于EF6是开源的,因此可以轻松提取在路径中转换lambda表达式的方法,以便在EF Core中使用,从而获得完全相同的行为
下面是完整的扩展方法
/// <summary>
/// Provides extension methods to the <see cref="Expression" /> class.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Converts the property accessor lambda expression to a textual representation of it's path. <br />
/// The textual representation consists of the properties that the expression access flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The property selector expression.</param>
/// <returns>The extracted textual representation of the expression's path.</returns>
public static string AsPath(this LambdaExpression expression)
{
if (expression == null)
return null;
TryParsePath(expression.Body, out var path);
return path;
}
/// <summary>
/// Recursively parses an expression tree representing a property accessor to extract a textual representation of it's path. <br />
/// The textual representation consists of the properties accessed by the expression tree flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The expression tree to parse.</param>
/// <param name="path">The extracted textual representation of the expression's path.</param>
/// <returns>True if the parse operation succeeds; otherwise, false.</returns>
private static bool TryParsePath(Expression expression, out string path)
{
var noConvertExp = RemoveConvertOperations(expression);
path = null;
switch (noConvertExp)
{
case MemberExpression memberExpression:
{
var currentPart = memberExpression.Member.Name;
if (!TryParsePath(memberExpression.Expression, out var parentPart))
return false;
path = string.IsNullOrEmpty(parentPart) ? currentPart : string.Concat(parentPart, ".", currentPart);
break;
}
case MethodCallExpression callExpression:
switch (callExpression.Method.Name)
{
case nameof(Queryable.Select) when callExpression.Arguments.Count == 2:
{
if (!TryParsePath(callExpression.Arguments[0], out var parentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
if (!(callExpression.Arguments[1] is LambdaExpression subExpression))
return false;
if (!TryParsePath(subExpression.Body, out var currentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
path = string.Concat(parentPart, ".", currentPart);
return true;
}
case nameof(Queryable.Where):
throw new NotSupportedException("Filtering an Include expression is not supported");
case nameof(Queryable.OrderBy):
case nameof(Queryable.OrderByDescending):
throw new NotSupportedException("Ordering an Include expression is not supported");
default:
return false;
}
}
return true;
}
/// <summary>
/// Removes all casts or conversion operations from the nodes of the provided <see cref="Expression" />.
/// Used to prevent type boxing when manipulating expression trees.
/// </summary>
/// <param name="expression">The expression to remove the conversion operations.</param>
/// <returns>The expression without conversion or cast operations.</returns>
private static Expression RemoveConvertOperations(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
expression = ((UnaryExpression)expression).Operand;
return expression;
}
}
然后在存储库中,您可以像在EF6中一样正常地调用它:
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));
参考资料:
这已经被问了好几次了,因为它实际上是使用EF6指定所需Include to repository方法的标准。听到一些EFC团队成员将模式更改为
Include
/然后Include
的决定背后的原因是什么会很有趣,这显然不能以这种方式表示,更重要的是,EFC的替代品是什么。@IvanStoev你在EFC中找到替代品了吗?@DavidG根据他们自己提供的扩展的源代码,他们的愿景中的替代品是Func include=null
参数。@IvanStoev Hmm,害怕类似的东西,不幸的是不适合我的用例。非常感谢您的回复。(顺便说一句,这个扩展不是微软做的,我想他们只是喜欢链接到它)@DavidG我想你是对的。我假设“他们”是基于微软的,名字是:),但现在我看到它和OP的自我回答是一样的。无论如何,如果你想保留旧的语法,EF6的源代码是可用的,可以相对容易地翻译成包含/然后包含语法。这里甚至有这样的帖子,只是不记得确切的帖子。虽然这样做有效,但它公开了一个实体框架类,这可能并不可取。有时候,知道什么时候保持简单易读是值得的。实在的和过度的抽象肯定会走得太远。而且,我也愿意接受一些替代方案,因为这个问题没有太多的答案。非常好的方法,你在哪里找到的?我想我在这里找到了:@SebastianR,但如果你把它变成一个通用的抽象类,后代存储库从中继承,你可以让每个子存储库公开IEnumerable,对吗?
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));
/// <summary>
/// Provides extension methods to the <see cref="Expression" /> class.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Converts the property accessor lambda expression to a textual representation of it's path. <br />
/// The textual representation consists of the properties that the expression access flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The property selector expression.</param>
/// <returns>The extracted textual representation of the expression's path.</returns>
public static string AsPath(this LambdaExpression expression)
{
if (expression == null)
return null;
TryParsePath(expression.Body, out var path);
return path;
}
/// <summary>
/// Recursively parses an expression tree representing a property accessor to extract a textual representation of it's path. <br />
/// The textual representation consists of the properties accessed by the expression tree flattened and separated by a dot character (".").
/// </summary>
/// <param name="expression">The expression tree to parse.</param>
/// <param name="path">The extracted textual representation of the expression's path.</param>
/// <returns>True if the parse operation succeeds; otherwise, false.</returns>
private static bool TryParsePath(Expression expression, out string path)
{
var noConvertExp = RemoveConvertOperations(expression);
path = null;
switch (noConvertExp)
{
case MemberExpression memberExpression:
{
var currentPart = memberExpression.Member.Name;
if (!TryParsePath(memberExpression.Expression, out var parentPart))
return false;
path = string.IsNullOrEmpty(parentPart) ? currentPart : string.Concat(parentPart, ".", currentPart);
break;
}
case MethodCallExpression callExpression:
switch (callExpression.Method.Name)
{
case nameof(Queryable.Select) when callExpression.Arguments.Count == 2:
{
if (!TryParsePath(callExpression.Arguments[0], out var parentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
if (!(callExpression.Arguments[1] is LambdaExpression subExpression))
return false;
if (!TryParsePath(subExpression.Body, out var currentPart))
return false;
if (string.IsNullOrEmpty(parentPart))
return false;
path = string.Concat(parentPart, ".", currentPart);
return true;
}
case nameof(Queryable.Where):
throw new NotSupportedException("Filtering an Include expression is not supported");
case nameof(Queryable.OrderBy):
case nameof(Queryable.OrderByDescending):
throw new NotSupportedException("Ordering an Include expression is not supported");
default:
return false;
}
}
return true;
}
/// <summary>
/// Removes all casts or conversion operations from the nodes of the provided <see cref="Expression" />.
/// Used to prevent type boxing when manipulating expression trees.
/// </summary>
/// <param name="expression">The expression to remove the conversion operations.</param>
/// <returns>The expression without conversion or cast operations.</returns>
private static Expression RemoveConvertOperations(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
expression = ((UnaryExpression)expression).Operand;
return expression;
}
}
/// <summary>
/// Specifies related entities to include in the query result.
/// </summary>
/// <typeparam name="T">The type of entity being queried.</typeparam>
/// <param name="source">The source <see cref="IQueryable{T}" /> on which to call Include.</param>
/// <param name="paths">The lambda expressions representing the paths to include.</param>
/// <returns>A new <see cref="IQueryable{T}" /> with the defined query path.</returns>
internal static IQueryable<T> Include<T>(this IQueryable<T> source, params Expression<Func<T, object>>[] paths)
{
if (paths != null)
source = paths.Aggregate(source, (current, include) => current.Include(include.AsPath()));
return source;
}
query.Include(t => t.Navigation1, t => t.Navigation2.Select(x => x.Child1));