根据运行时确定的属性名称筛选列表

时间:2018-03-15 00:31:44

标签: c# linq generics reflection

我想知道是否可以在过滤器表达式

中使“属性名称”动态化

考虑场景

List<Person> GetPerson(int countryID, int stateID, int cityID, int zip)
{
//List of person can be filtered based on below line of code
  List<Person> filteredPersons= persons.FindAll(rule => rule.CountryID == countryID).ToList();
//is it possible to specify ".Country" dynamically. something like

List<Person> filteredPersons= persons.FindAll(rule => rule."propertyName"== countryID).ToList();

}

2 个答案:

答案 0 :(得分:2)

考虑到您的示例,您可以使用的一种方法是使用.Where()扩展名而不是FindAll()这可以允许您手动构建表达式。一个简单的例子如下。

static List<Person> GetPerson(int countryID, int stateID, int cityID, int zip)
{
    //create a new expression for the type of person this.
    var paramExpr = Expression.Parameter(typeof(Person));

    //next we create a property expression based on the property named "CountryID" (this is case sensitive)
    var property = Expression.Property(paramExpr, "CountryID");

    //next we create a constant express based on the country id passed in.
    var constant = Expression.Constant(countryID);

    //next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
    var idEqualsExpr = Expression.Equal(property, constant);

    //next we convert the expression into a lamba expression
    var lExpr = Expression.Lambda<Func<Person, bool>>(idEqualsExpr, paramExpr);

    //finally we query our dataset
    return persons.AsQueryable().Where(lExpr).ToList();
}

所以这看起来很多代码,但我们基本上已经完成的是手动构造表达式树,其最终结果看起来类似于(并且起作用)

return persons.AsQueryable().Where(p => p.CountryId = countryId);

现在我们可以采用这个方法,假设你想使用和/或基于方法调用来查询多个属性。也就是说,您可以将所有“过滤器”参数更改为Nullable,并检查是否传递了值,我们会对其进行过滤,例如。

static List<Person> GetPerson(int? countryID = null, int? stateID = null, int? cityID = null, int? zip = null)
{
    //create a new expression for the type of person this.
    var paramExpr = Expression.Parameter(typeof(Person));

    //var equalExpression = Expression.Empty();
    BinaryExpression equalExpression = null;

    if (countryID.HasValue)
    {
        var e = BuildExpression(paramExpr, "CountryId", countryID.Value);
        if (equalExpression == null)
            equalExpression = e;
        else
            equalExpression = Expression.And(equalExpression, e);
    }
    if (stateID.HasValue)
    {
        var e = BuildExpression(paramExpr, "StateID", stateID.Value);
        if (equalExpression == null)
            equalExpression = e;
        else
            equalExpression = Expression.And(equalExpression, e);
    }
    if (equalExpression == null)
    {
        return new List<Person>();
    }

    //next we convert the expression into a lamba expression
    var lExpr = Expression.Lambda<Func<Person, bool>>(equalExpression, paramExpr);
    //finally we query our dataset
    return persons.AsQueryable().Where(lExpr).ToList();
}

static BinaryExpression BuildExpression(Expression expression, string propertyName, object value)
{
    //next we create a property expression based on the property named "CountryID" (this is case sensitive)
    var property = Expression.Property(expression, propertyName);

    //next we create a constant express based on the country id passed in.
    var constant = Expression.Constant(value);

    //next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
    return Expression.Equal(property, constant);
}

现在这是更多的代码,但正如您所看到的,我们现在接受所有参数的null值并构建我们对其他属性的查询。

现在你可以进一步(假设需要更通用的方法)传递Dictionary<string, object>属性\值来进行查询。这可以作为下面列出的IEnumerable<T>的扩展方法来完成。

public static class LinqExtensions
{
    public static IEnumerable<T> CustomParameterQuery<T>(this IEnumerable<T> entities, Dictionary<string, object> queryVars)
    {
        if (entities.Count() == 0 || queryVars.Count == 0)
        {
            return entities;
        }

        //create a new expression for the type of person this.
        var paramExpr = Expression.Parameter(typeof(T));

        BinaryExpression equalExpression = null;
        foreach (var kvp in queryVars)
        {
            var e = BuildExpression(paramExpr, kvp.Key, kvp.Value);
            if (equalExpression == null)
                equalExpression = e;
            else
                equalExpression = Expression.And(equalExpression, e);
        }

        if (equalExpression == null)
        {
            return new T[0];
        }
        //next we convert the expression into a lamba expression
        var lExpr = Expression.Lambda<Func<T, bool>>(equalExpression, paramExpr);
        //finally we query our dataset
        return entities.AsQueryable().Where(lExpr);
    }

    static BinaryExpression BuildExpression(Expression expression, string propertyName, object value)
    {
        //next we create a property expression based on the property name
        var property = Expression.Property(expression, propertyName);

        //next we create a constant express based on the country id passed in.
        var constant = Expression.Constant(value);

        //next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
        return Expression.Equal(property, constant);
    }
}

现在可以轻松地将其称为:

var dict = new Dictionary<string, object>
{
    { "CountryID", 1 },
    { "StateID", 2 }
};

var e = persons.CustomParameterQuery(dict);

现在很明显这不是一个完美的例子,但应该让你朝着正确的方向前进。现在,您可以在组合表达式时使用Expression.Or而不是Expression.And来支持“OR”语句等。

我必须添加这是非常容易出错的,因为它要求属性名称与实体完全相同,您可以使用T上的反射并确定PropertyName是否存在以及它是否正确套管。

答案 1 :(得分:0)

在NuGet中搜索 System.Linq.Dynamic 。这是开始使用Dynamic Linq的最简单方法。