entity-framework - 如何在 EF.Core 中构建动态 where 过滤器来处理等于、LIKE、gt、lt 等

标签 entity-framework dynamic .net-core

请问我们如何在 EF.Core 中构建一个动态的 where 过滤器来处理:

Query.Where(fieldName, compareMode, value)

我基本上期望像下面这样使用它:
    [HttpGet(Name = nameof(GetStaff))]
    public IActionResult GetStaffAsync([FromQuery] QueryParams p)
    {
      var s = db.Staff.AsNoTracking()
   .Where(p.filter_field, p.filter_mode, p.filter_value)
   .OrderByMember(p.sortBy, p.descending);

      var l = new Pager<Staff>(s, p.page, p.rowsPerPage);

      return Ok(l);
    }

//Helpers
      public class QueryParams
      {
        public bool descending { get; set; }
        public int page { get; set; } = 1;
        public int rowsPerPage { get; set; } = 5;

        public string sortBy { get; set; }

        public onject filter_value { get; set; }
        public string filter_field { get; set; }
        public string filter_mode { get; set; }
      }

  public class Pager<T>
  {
    public int pages { get; set; }
    public int total { get; set; }
    public IEnumerable<T> Items { get; set; }

    public Pager(IEnumerable<T> items, int offset, int limit)
    {
      Items = items.Skip((offset - 1) * limit).Take(limit).ToList<T>();
      total = items.Count();
      pages = (int)Math.Ceiling((double)total / limit);
    }
  }

最佳答案

假设您拥有的只是表示属性、比较运算符和值的实体类型和字符串,则可以使用以下方法构建动态谓词:

public static partial class ExpressionUtils
{
    public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
    {
        var parameter = Expression.Parameter(typeof(T), "x");
        var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
        var body = MakeComparison(left, comparison, value);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    private static Expression MakeComparison(Expression left, string comparison, string value)
    {
        switch (comparison)
        {
            case "==":
                return MakeBinary(ExpressionType.Equal, left, value);
            case "!=":
                return MakeBinary(ExpressionType.NotEqual, left, value);
            case ">":
                return MakeBinary(ExpressionType.GreaterThan, left, value);
            case ">=":
                return MakeBinary(ExpressionType.GreaterThanOrEqual, left, value);
            case "<":
                return MakeBinary(ExpressionType.LessThan, left, value);
            case "<=":
                return MakeBinary(ExpressionType.LessThanOrEqual, left, value);
            case "Contains":
            case "StartsWith":
            case "EndsWith":
                return Expression.Call(MakeString(left), comparison, Type.EmptyTypes, Expression.Constant(value, typeof(string)));
            default:
                throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
        }
    }

    private static Expression MakeString(Expression source)
    {
        return source.Type == typeof(string) ? source : Expression.Call(source, "ToString", Type.EmptyTypes);
    }

    private static Expression MakeBinary(ExpressionType type, Expression left, string value)
    {
        object typedValue = value;
        if (left.Type != typeof(string))
        {
            if (string.IsNullOrEmpty(value))
            {
                typedValue = null;
                if (Nullable.GetUnderlyingType(left.Type) == null)
                    left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
            }
            else
            {
                var valueType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
                typedValue = valueType.IsEnum ? Enum.Parse(valueType, value) :
                    valueType == typeof(Guid) ? Guid.Parse(value) :
                    Convert.ChangeType(value, valueType);
            }
        }
        var right = Expression.Constant(typedValue, left.Type);
        return Expression.MakeBinary(type, left, right);
    }
}

基本上构建属性访问器(具有嵌套属性支持),解析比较运算符并调用相应的运算符/方法,处理 from/to string和从/到可为空的类型转换。它可以扩展为处理 EF Core 特定功能,如 EF.Functions.Like通过添加相应的分支。

它可以直接使用(以防您需要将它与其他谓词结合使用)或通过像这样的自定义扩展方法:
public static partial class QueryableExtensions
{
    public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string comparison, string value)
    {
        return source.Where(ExpressionUtils.BuildPredicate<T>(propertyName, comparison, value));
    }
}

关于entity-framework - 如何在 EF.Core 中构建动态 where 过滤器来处理等于、LIKE、gt、lt 等,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46306955/

相关文章:

javascript - JS表单-动态填充下拉列表-多选、表单填写

c - 结构体动态链表中的内存分配

c# - 银光 2 : INotifyPropertyChanged on dynamically created object?

.net - 了解 .Net Core 和 Mono

c# - 第 2 组类似请求 Entity Framework/Linq in 1

asp.net-mvc - 如何将身份成员资格与现有数据库(n 层)一起使用

c# - SQLite 1.0.94 未出现在 EDM 提供程序上

c# - Entity Framework 正在通过 .NetFramework 恢复,而不是我的项目框架 Core V2.0

c# - 无法连接到 Azure 服务总线队列

asp.net-core - dnx 和 dnu 不在 Ubuntu 15.10 上运行