基于多个属性构建动态where子句

3

我手头有一个包含属性名称列表的 List<string> IndexFields

我的问题是,我需要根据列表中的元素构建一个 where 子句。

目前我有以下代码:

var sitem = List1.Where(p => (p.GetType().GetProperty(IndexFields[0])
  .GetValue(p, null) as string) == "red").FirstOrDefault();

但是这只允许我指定单个属性。我需要一个可以基于List<string> IndexFields列表中的所有名称构建的构建器。


1
有一个叫做PredicateBuilder的东西,它允许您构建动态where子句,您可以在Google上搜索它。 - NoPyGod
4个回答

6
最灵活的在运行时创建动态查询的方法是使用表达式API:
例如:
var type = typeof(T);
var properties = IndexFields.Select(x => type.GetProperty(x));

// x
var paramter = Expression.Parameter(type);

// x.Foo, x.Bar, ...
var leftHandSides = properties.Select(
        x => Expression.Property(parameter, x));

// "Baz"
var rightHandSide = Expression.Constant(...);

// x.Foo == "Baz", x.Bar = "Baz", ...
var equalityExpressions = leftHandSides.Select(
        x => Expression.Equal(x, rightHandSide));

// x.Foo == "Baz" && x.Bar == "Baz" && ...
var aggregatedExpressions = equalityExpressions.Aggregate(
        (x, y) => Expression.AndAlso(x, y));

// x => x.Foo == "Baz" && x.Bar == "Baz" && ...
var lambda = Expression.Lambda<Func<T,bool>>(
        aggregatedExpressions, parameter)

var item = List1.Where(lambda).FirstOrDefault();

构建查询的一个巨大优势是,生成的表达式仍然可以被翻译成SQL,并与实体框架一起使用,而在lambda体内使用反射确实有限制。但在使用表达式框架之前,我真的建议您花些时间去了解它。如果您能明白其中的原理,那么从长远来看,这将为您节省大量时间。您可以阅读更多相关内容:-

但是,如果您正在寻找快速且不太正式的方式,您可以直接在foreach中链接这些Where子句:

IEnumerable<T> query = List1;

foreach (var property in IndexFields)
{
  // The variable "property" gets hoisted out of local context
  // which messes you up if the query is being evaluated with
  // delayed execution.

  // If you're working in C# 6 though, you don't need to do this.
  var localProperty = property;

  query = query.Where(
    p => (p.GetType().GetProperty(localProperty)
                     .GetValue(p, null) as string) == "red");
}

var sitem = query.FirstOrDefault();

注意:'Where(Expression(Func(UMP, Boolean)))'是'IQueriable'中的一个方法。在'Enumerable'中,你有'Where(TSource)(IEnumerable(TSource), Func(TSource, Boolean))'。但原则是正确的,你只需要将表达式编译成委托即可。 - Paulo Morgado

1
您可以使用 PredicateBuilder 进行此操作:
var predicate = PredicateBuilder.New<string>();
 if (aCondition)
 {
  predicate = predicate.And(s => s == "this");
 }

 if (bCondition)
 {
  predicate = predicate.And(s => s == "that");
 }

0

你可以尝试像这样的东西,在linqpad中对我有用

void Main() {
    var listFields = new string[] { "Field1", "Field2" };
    var listValues = new string[] { "value1", "value2" };
    // prepare & show dummy data
    var listItems = Enumerable.Range(1, 100).Select(aaIndex => new MyItem {
        Name = string.Format("item{0}", aaIndex),
        Field1 = string.Format("value{0}", aaIndex % 3),
        Field2 = string.Format("value{0}", aaIndex % 7)
    });
    listItems.Dump();
    // apply filtering
    var filtered = listItems.Where(aaItem => Enumerable.Range(0, listFields.Length).All(aaIndex => {
        var value1 = aaItem.GetType().GetProperty(listFields[aaIndex]).GetValue(aaItem, null);
        var value2 = listValues[aaIndex];
        if (value1 is IComparable) {
            return ((IComparable)value1).CompareTo(value2) == 0;
        }
        return Convert.ToString(value1) == Convert.ToString(value2);
    }));
    filtered.Dump();
}

// Define other methods and classes here
class MyItem {
    public string Name { get; set; }
    public string Field1 { get; set; }
    public string Field2 { get; set; }
}

0

动态 Linq 库对于这类事情非常有效:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

它添加了重载以将不同的子句作为字符串传入:

var query = List1.Where("Color1=""Red"" or Color2=""Red""");

在您的情况下,您可以从索引字段构建字符串(可能需要循环,但此处进行了简化)。
var query = List1.Where(IndexFields[0] + "=""Red"" or " IndexFields[1] + "=Red");

使用时,下载示例包,然后获取 LinqSamples\DynamicQuery\DynamicQuery\Dynamic.cs 并与您的项目一起编译。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接