任何包含的表达式树

Expression Tree for Any with Contains

我们有一个 REST-API,它采用 ProductEANs.EAN 格式的 string 查询,所以 .分开了。 searchFields 是从 string 解析而来的,如下所示:{"ProductEANs.EAN":"113"}。基类型总是已知的。现在我想动态创建一个 Expression 树来查询数据集。如果 属性 不是 List,它可以工作,但我无法让它与 List 一起工作。我想要重新创建的是 expression: productsQuery.Where(p => p.ProductEANs.Any(e => e.EAN.Contains("113")));。这是我到目前为止的位置:

IQueryable<Product> productsQuery = _context.Products
                .Include(p => p.ProductEANs);

foreach (KeyValuePair<string, string> searchField in searchFields)
{
    if (!string.IsNullOrWhiteSpace(searchField.Value))
    {
        var parameterExpression = Expression.Parameter(typeof(Product), "product");
        var constant = Expression.Constant(searchField.Value);

        MethodCallExpression expression = null;
        Expression property = parameterExpression;

        foreach (var member in searchField.Key.Split('.'))
        {
            if (property.Type.IsGenericType &&
                typeof(IEnumerable<>)
                    .MakeGenericType(property.Type.GetGenericArguments())
                    .IsAssignableFrom(property.Type))
            {
                var genType = property.Type.GenericTypeArguments.FirstOrDefault();
                if (genType != null)
                {
                    Expression subExp = Expression.Parameter(genType, "pEAN");
                    Expression subProp = Expression.PropertyOrField(subExp, member);    
                   
                    var expContains = Expression.Call(subProp, "Contains", null, constant);

                    var anyMethod = typeof(Enumerable).GetMethods().First(method => method.Name == "Any" && method.GetParameters().Length == 2).MakeGenericMethod(genType);

                    expression = Expression.Call(anyMethod, subProp, expContains);    
                }    
            }
            else
            {
                property = Expression.PropertyOrField(property, member);
            }
        }

        if (expression == null)
            expression = Expression.Call(property, "Contains", null, constant);

        var lambda = Expression.Lambda<Func<Product, bool>>(expression, parameterExpression);

        productsQuery = productsQuery.Where(lambda);
    }
}

productsQuery = productsQuery.OrderBy(p => p.Title);

var products = productsQuery.ToList().Select(p => (ProductDTO)p).ToList();

if (property.Type 的 if 部分中的所有内容...都是我在努力让它发挥作用。数据模型如下所示:

public class Product
{
    public Guid Id {get;set;}
    public string Tile {get;set;}
    public List<ProductEAN> ProductEANs { get; set; }
}

public class ProductEAN
{
    public string EAN { get; set; }
    public decimal ProductCount { get; set; }
}

这应该可以解决问题:

public static class FilterExtensions
{
    private readonly struct Node
    {
        public Node(ParameterExpression parameter, Expression body)
        {
            Parameter = parameter;
            Body = body;
        }
        
        public ParameterExpression Parameter { get; }
        public Expression Body { get; }
    }
    
    public static Expression<Func<T, bool>> BuildFilter<T>(string key, string value)
    {
        if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
        if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));
        
        var p = Expression.Parameter(typeof(T), "p");
        Expression body = p;
        
        var stack = new Stack<Node>();
        
        foreach (string member in key.Split('.'))
        {
            if (body.Type.IsGenericType)
            {
                var genericArgs = body.Type.GetGenericArguments();
                if (genericArgs.Length == 1 && typeof(IEnumerable<>)
                    .MakeGenericType(genericArgs)
                    .IsAssignableFrom(body.Type))
                {
                    stack.Push(new Node(p, body));
                    p = Expression.Parameter(genericArgs[0], "s" + stack.Count);
                    body = p;
                }
            }
            
            body = Expression.PropertyOrField(body, member);
        }
        
        var constant = Expression.Constant(value, typeof(string));
        body = Expression.Call(body, nameof(string.Contains), null, constant);
        
        while (stack.Count != 0)
        {
            var childFilter = Expression.Lambda(body, p);
            var parent = stack.Pop();
            
            body = Expression.Call(typeof(Enumerable), 
                nameof(Enumerable.Any), 
                new[] { p.Type }, 
                parent.Body, 
                childFilter);
            
            p = parent.Parameter;
        }
        
        return Expression.Lambda<Func<T, bool>>(body, p);
    }
}

用法:

IQueryable<Product> productsQuery = _context.Products
    .Include(p => p.ProductEANs);

foreach (KeyValuePair<string, string> searchField in searchFields)
{
    if (!string.IsNullOrWhiteSpace(searchField.Value))
    {
        var filter = FilterExtensions.BuildFilter<Product>(searchField.Key, searchField.Value);
        productsQuery = productsQuery.Where(filter);
    }
}