Erweitern IQueryable Wo () oder anstelle von AND-Beziehung
-
06-09-2019 - |
Frage
ich meine eigenen Erweiterungsmethoden von IQueryable bin mit <> verkettbar Anfragen wie FindAll (). FindInZip (12345) .NameStartsWith ( „XYZ“). OrderByHowIWantIt () usw., die dann auf verzögerte Ausführung zu erstellen, eine einzelne Abfrage erstellt basiert auf meiner Erweiterungsmethoden Kette.
Das Problem dabei ist aber, dass alles Wo in der Verlängerungskettchen (FindXYZ, FindInZip etc.) wird immer kombinieren und was bedeutet, ich kann so etwas nicht tun:
FindAll (). FirstNameStartsWith ( "X"). OrLastNameStartsWith ( "Z"), weil ich weiß nicht, wie ich das oder in einem separaten injizieren kann Wo Verfahren.
Jede Idee, wie ich dieses Problem lösen kann?
Zusatz; Bis jetzt verstehe ich, wie Ketten Ausdrücke wie Oder, wenn ich sie wickeln (z CompileAsOr (FirstNameStartsWith ( "A"). LastNameStartsWith ( "Z"). SortiertNach (..))
Was ich versuche zu tun, obwohl ist etwas komplizierter (und PredicateBuilder hilft hier nicht ..), dass ich möchte ein später IQueryable im Grunde die Zugriff Wo Bedingungen, die vor eingerichtet wurden, ohne sie zu wickeln zu erstellen die Oder zwischen ihnen.
Da jede Erweiterung Methode gibt IQueryable <> Ich verstehe, dass es das Wissen über den aktuellen Stand der Abfragebedingungen irgendwo haben sollte, die mich führt zu glauben, dass es einige automatisiert sein oder eine oder das Erstellen über alle früheren Wo Bedingungen ohne mit wickeln, was Sie wollen ODER-verknüpft.
Lösung
Ich gehe davon aus, die verschiedenen Teile der Abfrage werden nur zur Laufzeit bekannt, das heißt Sie können nicht nur ||
in einem where
verwenden ...
Eine faule Option ist Concat
- aber dies neigt dazu, schlechte TSQL usw. zu führen; aber ich neige dazu, individuellen Expression
s geneigt zu sein, anstatt zu schreiben. Der Ansatz zu nehmen, hängt ab, was der Anbieter ist, wie LINQ to SQL verschiedene Optionen zu EF (zum Beispiel) unterstützt - die hier eine echte Wirkung hat (da Sie nicht Unterausdrücke mit EF verwenden können). Können Sie uns sagen, was?
Hier ist ein Code, der mit LINQ-to-SQL arbeiten sollte; wenn Sie ein Array (oder eine Liste, und rufen .ToArray()
) bauen von Ausdrücken, sollte es funktionieren; Beispiel hierfür ist LINQ-to-Objects, soll aber immer noch funktionieren:
static void Main()
{
var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();
var predicates = new List<Expression<Func<int, bool>>>();
predicates.Add(i => i % 3 == 0);
predicates.Add(i => i >= 8);
foreach (var item in data.WhereAny(predicates.ToArray()))
{
Console.WriteLine(item);
}
}
public static IQueryable<T> WhereAny<T>(
this IQueryable<T> source,
params Expression<Func<T,bool>>[] predicates)
{
if (source == null) throw new ArgumentNullException("source");
if (predicates == null) throw new ArgumentNullException("predicates");
if (predicates.Length == 0) return source.Where(x => false); // no matches!
if (predicates.Length == 1) return source.Where(predicates[0]); // simple
var param = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Invoke(predicates[0], param);
for (int i = 1; i < predicates.Length; i++)
{
body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
}
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return source.Where(lambda);
}
Andere Tipps
Verwenden Sie PredicateBuilder<T>
. Es ist wahrscheinlich das, was Sie wollen.
List<string> fruits =
new List<string> { "apple", "passionfruit", "banana", "mango",
"orange", "blueberry", "grape", "strawberry" };
var query = fruits.AsQueryable();
// Get all strings whose length is less than 6.
query = query.Where(fruit => fruit.Length < 6);
// Hope to get others where length is more than 8. But you can't, they're gone.
query = query.Where(fruit => 1 == 1 || fruit.Length > 8);
foreach (string fruit in query)
Console.WriteLine(fruit);
In einer idealen Welt denke ich persönlich ||
und &&
Betreiber die einfachste und lesbar sein würden. Allerdings wird es nicht kompiliert werden.
Operator '||' kann nicht auf Operanden vom Typ ‚
angewandt werdenExpression<Func<YourClass,bool>>
‘ und ‚Expression<Func<YourClass,bool>>
‘
Deshalb verwende ich eine Erweiterungsmethode für diese. In Ihrem Beispiel würde es wie folgt aussehen:
.Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now))
.
Statt:
.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now))
.
Expression Beispiel:
private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
return post => post.PostedOn >= cutoffDate;
};
Erweiterungsmethode:
public static class PredicateExtensions
{
/// <summary>
/// Begin an expression chain
/// </summary>
/// <typeparam id="T""></typeparam>
/// <param id="value"">Default return value if the chanin is ended early</param>
/// <returns>A lambda expression stub</returns>
public static Expression<Func<T, bool>> Begin<T>(bool value = false)
{
if (value)
return parameter => true; //value cannot be used in place of true/false
return parameter => false;
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.AndAlso);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.OrElse);
}
#region private
private static Expression<Func<T, bool>> CombineLambdas<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right, ExpressionType expressionType)
{
//Remove expressions created with Begin<T>()
if (IsExpressionBodyConstant(left))
return (right);
ParameterExpression p = left.Parameters[0];
SubstituteParameterVisitor visitor = new SubstituteParameterVisitor();
visitor.Sub[right.Parameters[0]] = p;
Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
private static bool IsExpressionBodyConstant<T>(Expression<Func<T, bool>> left)
{
return left.Body.NodeType == ExpressionType.Constant;
}
internal class SubstituteParameterVisitor : ExpressionVisitor
{
public Dictionary<Expression, Expression> Sub = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
Expression newValue;
if (Sub.TryGetValue(node, out newValue))
{
return newValue;
}
return node;
}
}
#endregion
}
Ein wirklich guter Artikel über LINQ-Abfragen durch die Ausdrücke erweitern. Auch die Quelle der Erweiterung Methode, die ich benutze.