使用 Any 针对任何类型构建 EF 兼容表达式调用

Building a EF Compatible expression call using Any against any type

相关文章:

LINQ Expression Tree: Call Any method against a DbSet

我希望能够动态构建一个 where 语句来再次检查 属性 的值列表。

静态调用可能如下所示:

IEnumerable<Guid> ids = //.... list of guids
context.DbSet<Person>()
  .Where(p => ids.Any(i => i == p.id))
  .ToList();

Table

[Table("Person")]
class Person 
{
    public Guid Id { get; set; }
    public string GivenName { get; set; }
    public string FamilyName { get; set; }
    public int ChildrenCount { get; set; }
}

我通过 JSON 接收数据,所以它不是强类型的:

class Query
{
    public string PropertyName { get; set; } // 'Id'          or 'FamilyName' or 'ChildrenCount'
    public string AnyValues { get; set; }    // 'guid1,guid2' or 'Musk,Gates' or '1,2,3' respectively
}

与核心问题相关的主要工作示例:

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;

                    
public class Program
{
    public static void Main()
    {
      var query = new Query
      {
        PropertyName = "id",
        AnyValues = new List<Guid> { Guid.Empty }
      };

      var exp = ToExpression<Person>(query);

      var people = context.DbSet<Person>().Where(exp).ToListAsync();
    }
    
    public static Expression ToExpression<TModel>(Query query)
    {
        Expression result = null;

        var propInfo = typeof(TModel).GetProperty(query.PropertyName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance);

        if (propInfo != null)
        {
          var propType = propInfo.PropertyType;

          var parameterExpression = Expression.Parameter(typeof(TModel), nameof(TModel));

          var memberExpression = Expression.Property(parameterExpression, query.PropertyName);

          var constantExpression = GetConstantExpressionForType(propType, query.AnyValues);

          if (memberExpression != null && constantExpression != null)
          {
            // 
            // var org = Expression.Parameter(typeof(Organization), "org");
            // Expression<Func<OrganizationField, bool>> predicate = a => a.CustomField.Name == filter.Name && values.Contains(a.Value);
            // var body = Expression.Call(typeof(Enumerable), "Any", new[] { typeof(OrganizationField) },
            //   Expression.PropertyOrField(org, "OrganizationFields"), predicate);
            // var lambda = Expression.Lambda<Func<Organization, bool>>(body, org);   

            // STUCK HERE    
            result = Expression.Call(
              typeof(Enumerable),
              "Any",
              ?? );   
          }
        }

        return result;
    }
    
    // Just a Hack for the example, not relevant to the question itself
    public static ConstantExpression GetConstantExpressionForType(Type type, object values)
    {
        ConstantExpression result = null;
        
        if (type == typeof(Guid)) 
        {
          result = Expression.Constant(values, typeof(IEnumerable<Guid>));
        } 
        else if (type == typeof(string)) 
        {
          result = Expression.Constant(values, typeof(IEnumerable<string>));
        }
        else if (type == typeof(int)) 
        {
          result = Expression.Constant(values, typeof(IEnumerable<int>));
        }
        
        return result;
    }
}

public class Person 
{
    public Guid Id { get; set; }
    public string GivenName { get; set; }
    public string FamilyName { get; set; }
    public int ChildrenCount { get; set; }
}

public class Query
{
    public string PropertyName { get; set; } // 'Id'          or 'FamilyName' or 'ChildrenCount'
    //public string AnyValues { get; set; }    // 'guid1,guid2' or 'Musk,Gates' or '1,2,3' respectively
    public object AnyValues { get; set; }    // to simply the core of the question
}

使用以下扩展方法:

public static class QueryableExtensions
{
    public static IQueryable<T> AnyFromItems<T>(this IQueryable<T> source, string items, string propName)
    {
        var strItems = items.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        var entityParam = Expression.Parameter(typeof(T), "e");
        var propExpression = Expression.PropertyOrField(entityParam, propName);
        var itemType = propExpression.Type;
        var itemParam = Expression.Parameter(itemType, "i");

        var anyPredicate =
            Expression.Lambda(
                Expression.Equal(itemParam, propExpression),
                itemParam);

        // apply conversion
        var itemsExpression = Expression.Call(typeof(QueryableExtensions), nameof(QueryableExtensions.ParseItems),
            new[] { itemType }, Expression.Constant(strItems));

        var filterLambda =
            Expression.Lambda<Func<T, bool>>(
                Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new[] { itemType },
                    itemsExpression, anyPredicate),
                entityParam);

        return source.Where(filterLambda);
    }

    private static IEnumerable<TItem> ParseItems<TItem>(IEnumerable<string> items)
    {
        return items.Select(i => (TItem)Convert.ChangeType(i, typeof(TItem)));
    }
}

和用法:

var query = new Query
{
    PropertyName = "id",
    AnyValues = string.Join(", ", new List<Guid> { Guid.Empty });
};

var people = await context.DbSet<Person>()
    .AnyFromItems(query.AnyValues, query.PropertyName)
    .ToListAsync();