无法翻译 LINQ 表达式(EF Core 中的深度查询)
LINQ Expression could not be translated (Deep Query in EF Core)
我的报错信息如下:
LINQ 表达式
DbSet<WorkItemEntity>
.Where(w => w.Company.Name.ToLower() != null && "com" != null &&
w.Company.Name.ToLower().StartsWith("com"))
无法翻译。以可以翻译的形式重写查询,或者通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用来显式切换到客户端评估。有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2101038。
我不确定是哪一部分导致的,我认为 ToLower/StartsWith 都受支持?
查询由表达式生成。重要的部分发生在这里:
public static IQueryable<T> ApplyFilters<T, TOut>(this IQueryable<T> query, Dictionary<Expression<Func<T, TOut>>, Filter> parameters)
{
foreach (var filterPair in parameters)
{
try
{
var parameterExpression = Expression.Parameter(typeof(T), "w");
MemberExpression propertyExpression = filterPair.Key.Body as MemberExpression;
var body = GetBody(filterPair, propertyExpression);
if (body != null)
{
query = query.Where(Expression.Lambda<Func<T, bool>>(body, parameterExpression));
}
}
catch (Exception e1)
{
// Do not throw for invalid expressions
}
}
return query;
}
这是我的“body”
var stringModel = filterPair.Value.Value as StringModel;
comparerExpression = Expression.Constant(stringModel.Value.ToLower());
string methodName = Enum.GetName(typeof(StringFilterType), filterType);
MethodInfo methodInfo = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
return Expression.Call(ToLowerMember(propertyExpression), methodInfo, comparerExpression);
除了查询的实现之外,一切似乎都有效?有什么想法吗?
当我重写自己的查询(错误)时,它工作正常。所以我想这与反射有关?
query = query.Where(w => w.Company.Name.ToLower().StartsWith("com"));
因此,这仅涵盖 属性 对顶级属性的过滤(例如,无关系)。但是,如果您执行类似 select(x => new xModel() { Name = x.Partner.Name}) 之类的操作,您将能够通过在 Deep 的 Select 之后进行过滤来访问“Name”属性。
我有一个使用 Expression of the Deep 属性 的解决方案,但最终,首先 Select 然后在一行中通过数组应用过滤器会更方便。
你可以看到我是如何解决字符串过滤的。
感谢大家的帮助! :)
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Shared.Models;
using Shared.Models.Filter;
namespace App.Extensions
{
public static class FilterExtensions
{
/// <summary>
/// FilterModel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TFilterType"></typeparam>
/// <param name="query"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static IQueryable<T> ApplyFilter<T, TFilterType>(this IQueryable<T> query, FilterModel<TFilterType> filter)
{
(Expression Body, ParameterExpression Parameter) value;
if (filter.FilterType is NumberFilterType numberFilterType)
{
value = CreateNumberFilter<T, TFilterType>(filter, numberFilterType);
}
else if(filter.FilterType is DateTimeFilterType dateTimeFilterType)
{
value = CreateDateTimeFilter<T, TFilterType>(filter, dateTimeFilterType);
}
else if(filter.FilterType is StringFilterType stringFilterType)
{
value = CreateStringFilter<T, TFilterType>(filter, stringFilterType);
}
else if (filter.FilterType is BoolFilterType)
{
value = CreateBoolFilter<T, TFilterType>(filter);
}
else if (filter.FilterType is GuidFilterType)
{
value = CreateGuidFilter<T, TFilterType>(filter);
}
else
{
throw new NotImplementedException(filter.FilterType.ToString());
}
return query.Where(Expression.Lambda<Func<T, bool>>(value.Body, value.Parameter));
}
public static IQueryable<T> ApplyFilters<T>(this IQueryable<T> query, FilterPagingParameters parameters)
{
query = query.ApplyFilters(parameters.NumberFilters);
query = query.ApplyFilters(parameters.DateTimeFilters);
query = query.ApplyFilters(parameters.StringFilters);
query = query.ApplyFilters(parameters.BoolFilters);
query = query.ApplyFilters(parameters.GuidFilters);
return query;
}
private static IQueryable<T> ApplyFilters<T, TFilterType>(this IQueryable<T> query, FilterModel<TFilterType>[] filters)
{
if (filters != null && filters.Any())
{
foreach (var filter in filters)
{
query = query.ApplyFilter(filter);
}
}
return query;
}
private static (Expression Body, ParameterExpression Parameter) CreateNumberFilter<T, TFilterType>(FilterModel<TFilterType> filter, NumberFilterType filterType)
{
var propertyExpression = GetExpression<T, decimal>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value1 = Expression.Constant(Convert.ToDecimal(filter.Value.Value));
Expression body;
if (filterType == NumberFilterType.Between)
{
var value2 = Expression.Constant(Convert.ToDecimal(filter.Value.Value2));
var bodyMin = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
var bodyMax = Expression.LessThanOrEqual(propertyExpression.Body, value2);
body = Expression.AndAlso(bodyMin, bodyMax);
}
else if (filterType == NumberFilterType.Equals)
{
body = Expression.Equal(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.GreaterThan)
{
body = Expression.GreaterThan(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.GreaterThanOrEqual)
{
body = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.LessThan)
{
body = Expression.LessThan(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.LessThanOrEqual)
{
body = Expression.LessThanOrEqual(propertyExpression.Body, value1);
}
else
{
throw new NotImplementedException(filterType.ToString());
}
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateDateTimeFilter<T, TFilterType>(FilterModel<TFilterType> filter, DateTimeFilterType filterType)
{
var propertyExpression = GetExpression<T, DateTime>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value1 = Expression.Constant(Convert.ToDateTime(filter.Value.Value)); // TODO: ToDate Required?
Expression body;
if (filterType == DateTimeFilterType.Between)
{
var value2 = Expression.Constant(Convert.ToDateTime(filter.Value.Value2));
var bodyMin = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
var bodyMax = Expression.LessThanOrEqual(propertyExpression.Body, value2);
body = Expression.AndAlso(bodyMin, bodyMax);
}
else if (filterType == DateTimeFilterType.Equals)
{
body = Expression.Equal(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.GreaterThan)
{
body = Expression.GreaterThan(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.GreaterThanOrEqual)
{
body = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.LessThan)
{
body = Expression.LessThan(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.LessThanOrEqual)
{
body = Expression.LessThanOrEqual(propertyExpression.Body, value1);
}
else
{
throw new NotImplementedException(filterType.ToString());
}
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateStringFilter<T, TFilterType>(FilterModel<TFilterType> filter, StringFilterType filterType)
{
// TODO: Try to make it the same way everywhere
string methodName = Enum.GetName(typeof(StringFilterType), filterType);
MethodInfo methodInfo = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
if(methodInfo == null)
{
throw new NotImplementedException(filterType.ToString());
}
var propertyExpression = GetExpression<T, string>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant(filter.Value.Value as string);
Expression body = Expression.Call(propertyExpression.Body, methodInfo, value);
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateGuidFilter<T, TFilterType>(FilterModel<TFilterType> filter)
{
var propertyExpression = GetExpression<T, Guid>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant(Guid.Parse(filter.Value.Value as string));
Expression body = Expression.Equal(propertyExpression.Body, value);
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateBoolFilter<T, TFilterType>(FilterModel<TFilterType> filter)
{
var propertyExpression = GetExpression<T, bool>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant((bool)filter.Value.Value);
Expression body = Expression.Equal(propertyExpression.Body, value);
return (body, parameter);
}
private static Expression<Func<T, TProperty>> GetExpression<T, TProperty>(string propertyName)
{
// x =>
var parameter = Expression.Parameter(typeof(T));
// x.Name
var mapProperty = Expression.Property(parameter, propertyName);
// (object)x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(TProperty));
// x => (object)x.Name
return Expression.Lambda<Func<T, TProperty>>(convertedExpression, parameter);
}
}
}
我的报错信息如下:
LINQ 表达式
DbSet<WorkItemEntity>
.Where(w => w.Company.Name.ToLower() != null && "com" != null &&
w.Company.Name.ToLower().StartsWith("com"))
无法翻译。以可以翻译的形式重写查询,或者通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用来显式切换到客户端评估。有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2101038。
我不确定是哪一部分导致的,我认为 ToLower/StartsWith 都受支持?
查询由表达式生成。重要的部分发生在这里:
public static IQueryable<T> ApplyFilters<T, TOut>(this IQueryable<T> query, Dictionary<Expression<Func<T, TOut>>, Filter> parameters)
{
foreach (var filterPair in parameters)
{
try
{
var parameterExpression = Expression.Parameter(typeof(T), "w");
MemberExpression propertyExpression = filterPair.Key.Body as MemberExpression;
var body = GetBody(filterPair, propertyExpression);
if (body != null)
{
query = query.Where(Expression.Lambda<Func<T, bool>>(body, parameterExpression));
}
}
catch (Exception e1)
{
// Do not throw for invalid expressions
}
}
return query;
}
这是我的“body”
var stringModel = filterPair.Value.Value as StringModel;
comparerExpression = Expression.Constant(stringModel.Value.ToLower());
string methodName = Enum.GetName(typeof(StringFilterType), filterType);
MethodInfo methodInfo = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
return Expression.Call(ToLowerMember(propertyExpression), methodInfo, comparerExpression);
除了查询的实现之外,一切似乎都有效?有什么想法吗?
当我重写自己的查询(错误)时,它工作正常。所以我想这与反射有关?
query = query.Where(w => w.Company.Name.ToLower().StartsWith("com"));
因此,这仅涵盖 属性 对顶级属性的过滤(例如,无关系)。但是,如果您执行类似 select(x => new xModel() { Name = x.Partner.Name}) 之类的操作,您将能够通过在 Deep 的 Select 之后进行过滤来访问“Name”属性。
我有一个使用 Expression of the Deep 属性 的解决方案,但最终,首先 Select 然后在一行中通过数组应用过滤器会更方便。
你可以看到我是如何解决字符串过滤的。
感谢大家的帮助! :)
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Shared.Models;
using Shared.Models.Filter;
namespace App.Extensions
{
public static class FilterExtensions
{
/// <summary>
/// FilterModel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TFilterType"></typeparam>
/// <param name="query"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static IQueryable<T> ApplyFilter<T, TFilterType>(this IQueryable<T> query, FilterModel<TFilterType> filter)
{
(Expression Body, ParameterExpression Parameter) value;
if (filter.FilterType is NumberFilterType numberFilterType)
{
value = CreateNumberFilter<T, TFilterType>(filter, numberFilterType);
}
else if(filter.FilterType is DateTimeFilterType dateTimeFilterType)
{
value = CreateDateTimeFilter<T, TFilterType>(filter, dateTimeFilterType);
}
else if(filter.FilterType is StringFilterType stringFilterType)
{
value = CreateStringFilter<T, TFilterType>(filter, stringFilterType);
}
else if (filter.FilterType is BoolFilterType)
{
value = CreateBoolFilter<T, TFilterType>(filter);
}
else if (filter.FilterType is GuidFilterType)
{
value = CreateGuidFilter<T, TFilterType>(filter);
}
else
{
throw new NotImplementedException(filter.FilterType.ToString());
}
return query.Where(Expression.Lambda<Func<T, bool>>(value.Body, value.Parameter));
}
public static IQueryable<T> ApplyFilters<T>(this IQueryable<T> query, FilterPagingParameters parameters)
{
query = query.ApplyFilters(parameters.NumberFilters);
query = query.ApplyFilters(parameters.DateTimeFilters);
query = query.ApplyFilters(parameters.StringFilters);
query = query.ApplyFilters(parameters.BoolFilters);
query = query.ApplyFilters(parameters.GuidFilters);
return query;
}
private static IQueryable<T> ApplyFilters<T, TFilterType>(this IQueryable<T> query, FilterModel<TFilterType>[] filters)
{
if (filters != null && filters.Any())
{
foreach (var filter in filters)
{
query = query.ApplyFilter(filter);
}
}
return query;
}
private static (Expression Body, ParameterExpression Parameter) CreateNumberFilter<T, TFilterType>(FilterModel<TFilterType> filter, NumberFilterType filterType)
{
var propertyExpression = GetExpression<T, decimal>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value1 = Expression.Constant(Convert.ToDecimal(filter.Value.Value));
Expression body;
if (filterType == NumberFilterType.Between)
{
var value2 = Expression.Constant(Convert.ToDecimal(filter.Value.Value2));
var bodyMin = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
var bodyMax = Expression.LessThanOrEqual(propertyExpression.Body, value2);
body = Expression.AndAlso(bodyMin, bodyMax);
}
else if (filterType == NumberFilterType.Equals)
{
body = Expression.Equal(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.GreaterThan)
{
body = Expression.GreaterThan(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.GreaterThanOrEqual)
{
body = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.LessThan)
{
body = Expression.LessThan(propertyExpression.Body, value1);
}
else if (filterType == NumberFilterType.LessThanOrEqual)
{
body = Expression.LessThanOrEqual(propertyExpression.Body, value1);
}
else
{
throw new NotImplementedException(filterType.ToString());
}
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateDateTimeFilter<T, TFilterType>(FilterModel<TFilterType> filter, DateTimeFilterType filterType)
{
var propertyExpression = GetExpression<T, DateTime>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value1 = Expression.Constant(Convert.ToDateTime(filter.Value.Value)); // TODO: ToDate Required?
Expression body;
if (filterType == DateTimeFilterType.Between)
{
var value2 = Expression.Constant(Convert.ToDateTime(filter.Value.Value2));
var bodyMin = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
var bodyMax = Expression.LessThanOrEqual(propertyExpression.Body, value2);
body = Expression.AndAlso(bodyMin, bodyMax);
}
else if (filterType == DateTimeFilterType.Equals)
{
body = Expression.Equal(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.GreaterThan)
{
body = Expression.GreaterThan(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.GreaterThanOrEqual)
{
body = Expression.GreaterThanOrEqual(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.LessThan)
{
body = Expression.LessThan(propertyExpression.Body, value1);
}
else if (filterType == DateTimeFilterType.LessThanOrEqual)
{
body = Expression.LessThanOrEqual(propertyExpression.Body, value1);
}
else
{
throw new NotImplementedException(filterType.ToString());
}
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateStringFilter<T, TFilterType>(FilterModel<TFilterType> filter, StringFilterType filterType)
{
// TODO: Try to make it the same way everywhere
string methodName = Enum.GetName(typeof(StringFilterType), filterType);
MethodInfo methodInfo = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
if(methodInfo == null)
{
throw new NotImplementedException(filterType.ToString());
}
var propertyExpression = GetExpression<T, string>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant(filter.Value.Value as string);
Expression body = Expression.Call(propertyExpression.Body, methodInfo, value);
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateGuidFilter<T, TFilterType>(FilterModel<TFilterType> filter)
{
var propertyExpression = GetExpression<T, Guid>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant(Guid.Parse(filter.Value.Value as string));
Expression body = Expression.Equal(propertyExpression.Body, value);
return (body, parameter);
}
private static (Expression Body, ParameterExpression Parameter) CreateBoolFilter<T, TFilterType>(FilterModel<TFilterType> filter)
{
var propertyExpression = GetExpression<T, bool>(filter.Name);
var parameter = propertyExpression.Parameters[0];
var value = Expression.Constant((bool)filter.Value.Value);
Expression body = Expression.Equal(propertyExpression.Body, value);
return (body, parameter);
}
private static Expression<Func<T, TProperty>> GetExpression<T, TProperty>(string propertyName)
{
// x =>
var parameter = Expression.Parameter(typeof(T));
// x.Name
var mapProperty = Expression.Property(parameter, propertyName);
// (object)x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(TProperty));
// x => (object)x.Name
return Expression.Lambda<Func<T, TProperty>>(convertedExpression, parameter);
}
}
}