【问题标题】:How to convent viewmodel to Expression<Func<T,bool>>?如何将视图模型转换为 Expression<Func<T,bool>>?
【发布时间】:2018-10-24 20:04:49
【问题描述】:

捎带very similar question...

我需要从 ViewModel 生成一个 Expression 以作为 IQueryable.Where 的搜索谓词传递。我需要能够根据用户提供的内容包含/排除查询参数。示例:

public class StoresFilter
{
    public int[] Ids { get; set; }

    [StringLength(150)]
    public string Name { get; set; }

    [StringLength(5)]
    public string Abbreviation { get; set; }

    [Display(Name = "Show all")]
    public bool ShowAll { get; set; } = true;

    public Expression<Func<Store, bool>> ToExpression()
    {
        List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>();

        if (Ids != null && Ids.Length > 0)
        {
            expressions.Add(x => Ids.Contains(x.Id));
        }
        if (Name.HasValue())
        {
            expressions.Add(x => x.Name.Contains(Name));
        }
        if (Abbreviation.HasValue())
        {
            expressions.Add(x => x.Abbreviation.Contains(Abbreviation));
        }
        if (!ShowAll)
        {
            expressions.Add(x => x.Enabled == true);
        }
        if (expressions.Count == 0)
        {
            return x => true;
        }

        // how to combine list of expressions into composite expression???
        return compositeExpression;
    }
}

有没有一种从表达式列表构建复合表达式的简单方法?还是我需要通过使用ParameterExpressionExpression.AndAlsoExpressionVisitor 等手动构建表达式的过程?

【问题讨论】:

  • 据我了解,您正在寻找一种在运行时生成 Expression&lt;Func&lt;T,bool&gt;&gt; 的方法,可以将其提供给 IQueryable&lt;T&gt; where 子句,它可以很容易地成为任何输入T。否则需要针对特定​​类型的逻辑分别为每种类型编码
  • 更新您提供的使用示例模型Store的代码,为您提到的所有用例添加代码

标签: c# ienumerable expression-trees iqueryable func


【解决方案1】:

您不应该构建和组合Expressions,而是应该通过IQuerable&lt;Store&gt; 通过.Where 链来完成。此外,source.Expression 将包含所需的表达式:

public IQueryable<Store> ApplyFilter(IQueryable<Store> source)
{
    if (Ids != null && Ids.Length > 0)  
        source = source.Where(x => Ids.Contains(x.Id)); 

    if (Name.HasValue())    
        source = source.Where(x => x.Name.Contains(Name));  

    if (Abbreviation.HasValue())    
        source = source.Where(x => x.Abbreviation.Contains(Abbreviation));  

    if (!ShowAll)   
        source = source.Where(x => x.Enabled == true);      

    //or return source.Expression as you wanted
    return source;
}

用法:

var filter = new StoresFilter { Name = "Market" };
var filteredStores = filter.ApplyFilter(context.Stores).ToList();

【讨论】:

  • 这会将IQueryable&lt;T&gt; 视为IEnumerable&lt;T&gt; 并将Expression&lt;Func&lt;T&gt;&gt; 替换为Func&lt;T&gt;,当所有数据都加载到内存中时这将起作用,但实际上IQueryable&lt;T&gt; 将执行远程操作数据处理,这就是为什么 Expression 而不是普通的 Func。这个link 解释得很好。对于内存中的所有数据 IEnumerable 服务于目的
  • @MrinalKamboj,你能具体指定行吗,它发生在哪里(“treating”)?只有filteredStores变量是IEnumerable&lt;Store&gt;,只是因为我调用了.ToLIst()方法。
  • IQueryable&lt;T&gt; 实现了IEnumerable&lt;T&gt;,它在内部调用了 MethodCallExpression ,在这里查看referencesource.microsoft.com/#System.Core/System/Linq/…,对于像SelectWhere 这样的Linq 方法,在这种情况下它将直接转换为Func,没有别的可能,因为数据在内存中并且没有表达式树实现,它不能序列化到远程数据源来解析/执行以获取数据。在这种情况下这是正确的,但在 Queryable 上下文中的应用有限
  • @MrinalKamboj,你为什么决定,数据已经在内存中了? context.Stores 不在内存中。再次阅读问题:“作为 IQueryable.Where 的搜索谓词传递”,而不是 IEnumerable.Where。数据仅在 ToLIst() 调用后具体化。在哪里转换(impl/expl)到Func&lt;Store&gt;,指定点。
  • @SlavaUtesinov 从问题本身不清楚什么样的远程上下文,创建基于 Lambda 的表达式并不总是可行/支持的,尤其是在类型元数据不可访问的情况下。另一方面,通过 ExpressionTree 类创建表达式在大多数情况下都可以使用。据我了解,在远程环境中,您提供的解决方案可能无法按预期工作,需要显式 ExpressionTrees 翻译
【解决方案2】:
void Main()
{
    var store = new Store
    {
      Id = 1,
      Abbreviation = "ABC",
      Enabled = true,
      Name = "DEF"
    };

   var filter =  new Filter<Store>
   {
    Ids = new HashSet<int>(new [] {1,2,3,4}),
    Abbreviation = "GFABC",
    Enabled = true,
    Name = "SDEFGH",
    ShowAll = false
   }

   var expression = filter.ToExpression(store);

   var parameterType = Expression.Parameter(typeof(Store), "obj");

   // Generate Func from the Expression Tree
   Func<Store,bool> func = Expression.Lambda<Func<Store,bool>>(expression,parameterType).Compile();
}

public class Store
{
    public int Id {get; set;}

    public string Name {get; set;}

    public string Abbreviation { get; set; }

    public bool Enabled { get; set; }   
}

public class Filter<T> where T : Store
{
    public HashSet<int> Ids { get; set; }

    public string Name { get; set; }

    public string Abbreviation { get; set; }

    public bool Enabled {get; set;}

    public bool ShowAll { get; set; } = true;

    public Expression ToExpression(T data)
    {
        var parameterType = Expression.Parameter(typeof(T), "obj");

        var expressionList = new List<Expression>();

        if (Ids != null && Ids.Count > 0)
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Id");

            ConstantExpression idConstantExpression = Expression.Constant(data.Id, typeof(int));

            MethodInfo filtersMethodInfo = typeof(HashsetExtensions).GetMethod("Contains", new[] { typeof(HashSet<int>), typeof(int) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!string.IsNullOrEmpty(Name))
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Name");

            ConstantExpression idConstantExpression = Expression.Constant(data.Name, typeof(string));

            MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!string.IsNullOrEmpty(Abbreviation))
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Abbreviation");

            ConstantExpression idConstantExpression = Expression.Constant(data.Abbreviation, typeof(string));

            MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!ShowAll)
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Enabled");

            var binaryExpression = Expression.Equal(idExpressionColumn, Expression.Constant(true, typeof(bool)));

            expressionList.Add(binaryExpression);
        }

        if (expressionList.Count == 0)
        {
            expressionList.Add(BinaryExpression.Constant(true));
        }

        // Aggregate List<Expression> data into single Expression

        var returnExpression = expressionList.Skip(1).Aggregate(expressionList.First(), (expr1,expr2) => Expression.And(expr1,expr2));      

        return returnExpression;

        // Generate Func<T,bool> - Expression.Lambda<Func<T,bool>>(returnExpression,parameterType).Compile();
    }

}

public static class StringExtensions
{
    public static bool Contains(this string source, string subString)
    {
        return source?.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

public static class HashsetExtensions
{
    public static bool Contains(this HashSet<string> source, string subString)
    {
        return source.Contains(subString,StringComparer.OrdinalIgnoreCase);
    }
}

它是如何工作的?

  • 只有在简单的相等情况下,您才能使用BinaryExpression,例如Expression.EqualExpression.GreaterThan,这会显示为像“ShowAll”这样的属性
  • 对于其他情况,如string / Array / List Contains,您需要扩展方法,该方法可以采用两种类型并提供结果。一个单独的Contains 用于字符串以使其大小写中立。同样对于集合Hashset 有更好的选择,它的时间复杂度为 O(1),不像数组的 O(N)
  • 我们使用MethodCallExpression 来调用扩展方法
  • 最后我们聚合所有的表达式,可以编译创建Func&lt;T,bool&gt;
  • 如果您需要x =&gt; true 之类的东西,那么BinaryExpression.Constant(true) 就足够了
  • 我已经使用您定义的 Store 类提供了一个示例实现

【讨论】:

  • Downvoter 大多是你不了解表达式树,我提到的是自定义处理表达式树生成Function&lt;T,bool&gt;的正确方法@
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-05
相关资源
最近更新 更多