Ich muss einen dynamischen Linq-Ausdruck für eine dynamische Suche erstellen. Die grundlegende Suche funktioniert, funktioniert jedoch nicht mit der Sammlung. Ich kann den Titel und den Autor des Buches abrufen, erhalte jedoch nicht die erforderliche Seitenüberschrift. Ich erhalte die Ausnahme in Zeile "left11 = Expression.Property (Seite 1," Überschrift ");" . Ich denke, der Ausdruck, den ich erstellt habe, kann die Liste nicht erkennen. Wie könnte das möglich sein? Bitte beachten Sie den folgenden Code und die Stacktrace-Ausnahme.
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);
}
}
}
Ausnahmemeldung: - {"Die Instanzeigenschaft 'Überschrift' ist für Typ> 'System.Collections.Generic.List`1 [XMLStorageAndFilter.Page]'"} nicht definiert.
StackTrace: -
at System.Linq.Expressions.Expression.Property (Ausdruck Ausdruck, String propertyName)
unter XMLStorageAndFilter.Program2.Main () in c: \ Benutzer \ Administrator \ Dokumente \ Visual Studio 2013 \ Projekte \ XMLStorageAndFilter \ NavProperty.cs: Zeile 61
at System.AppDomain._nExecuteAssembly (RuntimeAssembly-Assembly, String [] args)
at System.AppDomain.ExecuteAssembly (Zeichenfolge AssemblyFile, Evidence AssemblySecurity, String [] Argumente)
bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly ()
bei System.Threading.ThreadHelper.ThreadStart_Context (Objektstatus)
at System.Threading.ExecutionContext.RunInternal (ExecutionContext executeContext, ContextCallback-Rückruf, Objektstatus, BooleanerveSyncCtx)
at System.Threading.ExecutionContext.Run (ExecutionContext executeContext, ContextCallback-Rückruf, Objektstatus, BooleanerveSyncCtx)
at System.Threading.ExecutionContext.Run (ExecutionContext executeContext, ContextCallback-Rückruf, Objektstatus)
bei System.Threading.ThreadHelper.ThreadStart ()
Sie können die hier beschriebene Methode verwenden.
Sie müssten das Ergebnis der Methode in Expression<Func<T,bool>>
. Ich bin dein Typ.
Ich werde ein vollständiges Beispiel geben, wenn ich nach Hause komme.
Bearbeiten:
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;
}
}
}
}}
Dies kann wie verwendet werden
var predicate = ExpressionBuilder.BuildPredicate<Books>("Heading",OperatorComparer.Equals,"Page","Heading");
query = query.Where(predicate);
Aufgrund Ihrer Beschreibung bin ich mir nicht sicher, ob Sie Expression
benötigen. Das Erstellen eines Expression
mit einem komplexen Objektmodell ist ziemlich schwierig. Müssen Sie wirklich einen dynamischen Ausdruck erstellen oder müssen Sie einfach eine dynamische Abfrage erstellen? Wenn das Objektmodell festgelegt ist, benötigen Sie keinen Expression
.
Ich schlage zunächst vor, Ihr Objektmodell zu bereinigen:
Books
Klasse in Book
(diese Klasse repräsentiert ein Buch, keine Liste von Büchern). Page
in Pages
(diese Eigenschaft gibt eine Liste der Seiten zurück). Jetzt können Sie eine Dynamik schreiben, bei der Sie nur LINQ und eine oder mehrere Hilfsfunktionen verwenden, eine für jede Eigenschaft, die Sie durchsuchen müssen. Um beispielsweise nach Heading
zu suchen, können Sie schreiben:
private static bool SearchByHeading(Book b, string heading)
{
if (string.IsNullOrEmpty(heading))
return true;
else
return b.Pages.Any(p => p.Heading == heading);
}
Hier können Sie auch sehen, warum Ihr vorheriger Code nicht funktioniert hat. Der Ausdruck für die Suche nach einer bestimmten Heading
lautet book.Pages.Any(p => p.Heading == x)
und nicht book.Pages.Heading == x
.
In jedem Fall können Sie bei einer oder mehreren Funktionen wie dieser Ihren Code wie folgt umschreiben:
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);
}
}
}
Ich habe Suchwerte übersprungen, wenn sie null oder leer sind, nur ein Beispiel. Dieser Code hat auch den Vorteil, dass er beim Kompilieren überprüft wird.