动态链接建筑表达
本文关键字:建筑 链接 动态 | 更新日期: 2023-09-27 18:03:46
我需要为动态搜索创建一个动态linq表达式。基本搜索工作,但它不能与收集工作。我能够得到书的标题和作者,但未能得到所需的页标题。我在"left11 = Expression行中得到异常。page1属性(,"Heading";"。我认为我构建的表达式无法识别List。这怎么可能呢?请参阅下面的代码和stacktrace异常。
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<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> 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<Books> testList = new List<Books>();
testList.Add(bok);
IQueryable<Books> queryableTestData = testList.AsQueryable<Books>();
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<Books, bool>> condition = Expression.Lambda
<Func<Books, bool>>(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<Books, bool>> condition2 = Expression.Lambda
<Func<Books, bool>>(predicateBody12, new ParameterExpression[] { pe11 });
var qq1 = queryableTestData.Where(condition2);
}
}
}
异常消息:- {"实例属性'Heading'未定义类型'System.Collections.Generic.List ' 1[XMLStorageAndFilter.Page]'"}
异常堆栈: -
在System.Linq.Expressions.Expression。属性(表达式表达式,字符串propertyName)
' users' Administrator'Documents'Visual Studio 2013'Projects'XMLStorageAndFilter'NavProperty.cs:line 61
在System.AppDomain。_nExecuteAssembly(RuntimeAssembly assembly, String[] args)
在System.AppDomain。ExecuteAssembly(String assemblyFile, Evidence, assemblySecurity, String[] args)
在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
在System.Threading.ThreadHelper。ThreadStart_Context(对象状态)
在System.Threading.ExecutionContext。RunInternal(ExecutionContext, ExecutionContext, ContextCallback, callback, Object state, Boolean, preserveSyncCtx)
在System.Threading.ExecutionContext。Run(ExecutionContext ExecutionContext, ContextCallback, callback, Object state, Boolean, preserveSyncCtx)
在System.Threading.ExecutionContext。Run(ExecutionContext ExecutionContext, ContextCallback, callback, Object state)
在System.Threading.ThreadHelper.ThreadStart ()
您可以使用这里描述的方法。
您需要将方法的结果强制转换为Expression<Func<T,bool>>
。这是你喜欢的类型。
我回家后会提供一个完整的例子。
编辑: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<Func<T,bool>> BuildPredicate<T>(object value, OperatorComparer comparer, params string[] properties)
{
var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name);
return (Expression<Func<T, bool>>)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´s a 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>{
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<Books>("Heading",OperatorComparer.Equals,"Page","Heading");
query = query.Where(predicate);
根据你的描述,我不确定你需要Expression
。创建具有复杂对象模型的Expression
是相当困难的。您真的需要创建动态表达式还是仅仅需要创建动态查询?如果对象模型是固定的,那么您不需要Expression
。
我建议首先清理你的对象模型:
- 将
Books
类重命名为Book
(该类代表一个Book而不是一个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<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> 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<Book> testList = new List<Book>();
testList.Add(bok);
var searchResult = Search(testList,
title: "Title1",
author: "FirstName1",
heading: "Heading1");
}
private static IEnumerable<Book> Search(IEnumerable<Book> 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);
}
}
}
我跳过搜索值时为空或空,只是一个例子。此代码还具有在编译时进行验证的优点。
更新
查询集合的方法如下所示:
构建一个动态表达式树来过滤集合属性
原始响应
我相信Davide Lcardi的说法是正确的:
Heading is book.Pages.Any(p => p.Heading == x) and not book.Pages.Heading == x.
如果要查询列表,则需要使用Any()方法。我不能得到它完全正确,但它应该看起来像下面使用表达式。电话:
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 );
我得到这个错误:没有方法'Any'存在类型'System.Collections.Generic.List ' 1[SO.Page]'。 SO是我的命名空间,其中类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 -树带两个——引入系统- linq表达式v4 - 0. - aspx
http://blogs.msdn.com/b/csharpfaq/archive/2009/09/14/generating动态-方法-表达式树- -视觉工作室- 2010. aspx
总的来说,在处理动态事务时考虑DynamicLinq是不错的
您应该使用Contains -您正在查看列表内部-而不是Equals。