动态linq建筑表达

我需要为动态搜索创建一个动态linq表达式。基本搜索工作正常,但无法使用集合。 我能够获得该书的标题和作者,但未能获得所需的页面标题。 我在行中获得了exception“left11 = Expression.Property(page1,”Heading“);”我认为我建立的表达式无法识别List。 怎么可能这样呢? 请参阅以下代码和stacktraceexception。

using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace XMLStorageAndFilter { public class Books { public Books() { Page = new List(); } public string Title { get; set; } public Author Author { get; set; } public List Page { get; set; } } public class Author { public string FirstName { get; set; } } public class Page { public string Heading { get; set; } } public class Program2 { static void Main() { Page page = new Page(); page.Heading = "Heading"; Books bok = new Books(); bok.Title = "Title"; bok.Author = new Author() { FirstName = "FirstName" }; bok.Page.Add(page); List testList = new List(); testList.Add(bok); IQueryable queryableTestData = testList.AsQueryable(); ParameterExpression pe11 = Expression.Parameter(typeof(Books), "p"); Expression left11 = Expression.Property(pe11, "Title"); Expression right11 = Expression.Constant("Title"); Expression e11 = Expression.Equal(left11, right11); var author = Expression.Property(pe11, "Author"); left11 = Expression.Property(author, "FirstName"); right11 = Expression.Constant("FirstName"); Expression e21 = Expression.Equal(left11, right11); Expression predicateBody11 = Expression.And(e11, e21); Expression<Func> condition = Expression.Lambda <Func>(predicateBody11, new ParameterExpression[] { pe11 }); var q = queryableTestData.Where(condition); var page1 = Expression.Property(pe11, "Page"); left11 = Expression.Property(page1, "Heading"); right11 = Expression.Constant("Heading"); Expression e22 = Expression.Equal(left11, right11); Expression predicateBody12 = Expression.And(e11, e22); Expression<Func> condition2 = Expression.Lambda <Func>(predicateBody12, new ParameterExpression[] { pe11 }); var qq1 = queryableTestData.Where(condition2); } } } 

exception消息: – {“未为类型>’System.Collections.Generic.List`1 [XMLStorageAndFilter.Page]’”定义实例属性’标题’

堆栈跟踪:-
at System.Linq.Expressions.Expression.Property(Expression expression,String propertyName)
位于c:\ Users \ Administrator \ Documents \ Visual Studio 2013 \ Projects \ XMLStorageAndFilter \ NavProperty.cs中的XMLStorageAndFilter.Program2.Main():第61行
在System.AppDomain._nExecuteAssembly(RuntimeAssembly程序集,String [] args)
在System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args)
在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
在System.Threading.ThreadHelper.ThreadStart_Context(对象状态)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback callback,Object state,Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback callback,Object state,Boolean preserveSyncCtx)
在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,对象状态)
在System.Threading.ThreadHelper.ThreadStart()

您可以使用此处描述的方法。

您需要将方法的结果Expression>Expression> 。 是你的类型。

我回家后会提供一个完整的例子。

编辑:

 using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Collections; using System.Reflection; namespace ExpressionPredicateBuilder { public enum OperatorComparer { Contains, StartsWith, EndsWith, Equals = ExpressionType.Equal, GreaterThan = ExpressionType.GreaterThan, GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual, LessThan = ExpressionType.LessThan, LessThanOrEqual = ExpressionType.LessThan, NotEqual = ExpressionType.NotEqual } public class ExpressionBuilder { public static Expression> BuildPredicate(object value, OperatorComparer comparer, params string[] properties) { var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name); return (Expression>)BuildNavigationExpression(parameterExpression, comparer, value, properties); } private static Expression BuildNavigationExpression(Expression parameter, OperatorComparer comparer, object value, params string[] properties) { Expression resultExpression = null; Expression childParameter, predicate; Type childType = null; if (properties.Count() > 1) { //build path parameter = Expression.Property(parameter, properties[0]); var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type); //if it´sa collection we later need to use the predicate in the methodexpressioncall if (isCollection) { childType = parameter.Type.GetGenericArguments()[0]; childParameter = Expression.Parameter(childType, childType.Name); } else { childParameter = parameter; } //skip current property and get navigation property expression recursivly var innerProperties = properties.Skip(1).ToArray(); predicate = BuildNavigationExpression(childParameter, comparer, value, innerProperties); if (isCollection) { //build subquery resultExpression = BuildSubQuery(parameter, childType, predicate); } else { resultExpression = predicate; } } else { //build final predicate resultExpression = BuildCondition(parameter, properties[0], comparer, value); } return resultExpression; } private static Expression BuildSubQuery(Expression parameter, Type childType, Expression predicate) { var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2); anyMethod = anyMethod.MakeGenericMethod(childType); predicate = Expression.Call(anyMethod, parameter, predicate); return MakeLambda(parameter, predicate); } private static Expression BuildCondition(Expression parameter, string property, OperatorComparer comparer, object value) { var childProperty = parameter.Type.GetProperty(property); var left = Expression.Property(parameter, childProperty); var right = Expression.Constant(value); var predicate = BuildComparsion(left, comparer, right); return MakeLambda(parameter, predicate); } private static Expression BuildComparsion(Expression left, OperatorComparer comparer, Expression right) { var mask = new List{ OperatorComparer.Contains, OperatorComparer.StartsWith, OperatorComparer.EndsWith }; if(mask.Contains(comparer) && left.Type != typeof(string)) { comparer = OperatorComparer.Equals; } if(!mask.Contains(comparer)) { return Expression.MakeBinary((ExpressionType)comparer, left, Expression.Convert(right,left.Type)); } return BuildStringCondition(left, comparer, right); } private static Expression BuildStringCondition(Expression left, OperatorComparer comparer, Expression right) { var compareMethod = typeof(string).GetMethods().Single(m => m.Name.Equals(Enum.GetName(typeof(OperatorComparer), comparer)) && m.GetParameters().Count() == 1); //we assume ignoreCase, so call ToLower on paramter and memberexpression var toLowerMethod = typeof(string).GetMethods().Single(m => m.Name.Equals("ToLower") && m.GetParameters().Count() == 0); left = Expression.Call(left, toLowerMethod); right = Expression.Call(right, toLowerMethod); return Expression.Call(left, compareMethod, right); } private static Expression MakeLambda(Expression parameter, Expression predicate) { var resultParameterVisitor = new ParameterVisitor(); resultParameterVisitor.Visit(parameter); var resultParameter = resultParameterVisitor.Parameter; return Expression.Lambda(predicate, (ParameterExpression)resultParameter); } private class ParameterVisitor : ExpressionVisitor { public Expression Parameter { get; private set; } protected override Expression VisitParameter(ParameterExpression node) { Parameter = node; return node; } } } 

}

这可以像

 var predicate = ExpressionBuilder.BuildPredicate("Heading",OperatorComparer.Equals,"Page","Heading"); query = query.Where(predicate); 

根据您的描述,我不确定您是否需要Expression 。 使用复杂对象模型创建Expression非常困难。 您真的需要创建动态表达式,还是只需要创建动态查询? 如果对象模型已修复,则不需要Expression

我建议首先清理你的对象模型:

  • Books类重命名为Book (此类表示书而不是书籍列表)
  • 将属性Page重命名为Pages (此属性返回页面列表)

现在,您可以编写一个动态,其中只使用LINQ和一个或多个辅助函数,每个函数对应一个需要搜索的属性。 例如,要搜索Heading您可以写:

  private static bool SearchByHeading(Book b, string heading) { if (string.IsNullOrEmpty(heading)) return true; else return b.Pages.Any(p => p.Heading == heading); } 

在这里,您还可以了解为什么以前的代码不起作用。 搜索给定Heading的表达式是book.Pages.Any(p => p.Heading == x)而不是book.Pages.Heading == x

在任何情况下,如果给出一个或多个这样的函数,您可以使用以下内容重写代码:

 using System.Collections.Generic; using System.Linq; namespace XMLStorageAndFilter { public class Book { public Book() { Pages = new List(); } public string Title { get; set; } public Author Author { get; set; } public List Pages { get; set; } } public class Author { public string FirstName { get; set; } } public class Page { public string Heading { get; set; } } public class Program2 { static void Main() { Page page = new Page(); page.Heading = "Heading1"; Book bok = new Book(); bok.Title = "Title1"; bok.Author = new Author() { FirstName = "FirstName1" }; bok.Pages.Add(page); List testList = new List(); testList.Add(bok); var searchResult = Search(testList, title: "Title1", author: "FirstName1", heading: "Heading1"); } private static IEnumerable Search(IEnumerable books, string author = null, string title = null, string heading = null) { return books .Where((b) => SearchByAuthor(b, author)) .Where((b) => SearchByHeading(b, heading)) .Where((b) => SearchByTitle(b, title)) .ToList(); } private static bool SearchByAuthor(Book b, string author) { if (string.IsNullOrEmpty(author)) return true; else return b.Author.FirstName == author; } private static bool SearchByTitle(Book b, string title) { if (string.IsNullOrEmpty(title)) return true; else return b.Title == title; } private static bool SearchByHeading(Book b, string heading) { if (string.IsNullOrEmpty(heading)) return true; else return b.Pages.Any(p => p.Heading == heading); } } } 

我在空或空时跳过搜索值,只是一个例子。 此代码还具有在编译时validation的优点。

更新

以下是对查询集合的方法的回答:

构建动态表达式树以过滤集合属性

原始回应

我相信Davide Lcardi的陈述是正确的:

 Heading is book.Pages.Any(p => p.Heading == x) and not book.Pages.Heading == x. 

如果要查询列表,则需要使用Any()方法来执行此操作。 我无法完全正确但它应该使用Expression.Call看起来像下面这样:

  ParameterExpression pe41 = Expression.Parameter(typeof (Page), "pg"); Expression left41 = Expression.Property(pe41, "Heading"); Expression right41 = Expression.Constant("Heading"); Expression e41 = Expression.Equal(left41, right41); var methodCall = Expression.Call( Expression.Property(pe11, "Pages"), "Any", new Type[] {typeof(Page), typeof(Boolean)}, e41 ); 

我收到此错误: 类型’System.Collections.Generic.List`1 [SO.Page]’上没有方法’Any’。 这是我的NameSpace,其中存在类Page。

我想我没有发送正确的类型或整个电话可能不正确。 我认为这是帮助您找到解决方案的正确方向。

以下是我看到的一些例子:

http://msdn.microsoft.com/en-us/library/bb349020(v=vs.110).aspx

http://msdn.microsoft.com/en-us/library/dd402755(v=vs.110).aspx

http://community.bartdesmet.net/blogs/bart/archive/2009/08/10/expression-trees-take-two-introducing-system-linq-expressions-v4-0.aspx

http://blogs.msdn.com/b/csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx

总的来说,在处理动态问题时考虑DynamicLinq也不错

你应该使用Contains – 你正在查看列表 – 而不是Equals。