无法翻译 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);
        }
    }
}