Question

Je me sers de mes propres méthodes d'extension de IQueryable <> pour créer des requêtes chainables telles que FindAll (). FindInZip (12 345) .NameStartsWith ( « XYZ »). OrderByHowIWantIt (), etc., qui ensuite l'exécution différée crée une seule requête en fonction de ma chaîne de méthodes d'extension.

Le problème avec ceci cependant, est que tous les associera toujours comme et qui signifie que je ne peux pas faire Où est la chaîne d'extension (FindXYZ, FindInZip etc.) quelque chose comme ceci:

FindAll (). FirstNameStartsWith ( "X"). OrLastNameStartsWith ( "Z") parce que je ne sais pas comment je peux injecter le OU dans un document distinct où la méthode.

Toute idée comment je peux résoudre ce problème?


supplémentaire

; Jusqu'à présent, je comprends comment des expressions comme chaîne ou si je les envelopper (par exemple CompileAsOr (FirstNameStartsWith ( "A"). LastNameStartsWith ( "Z"). OrderBy (..))

Ce que je suis en train de faire est bien un peu plus compliqué (et PredicateBuilder ne permet pas ici ..) dans ce que je veux un IQueryable plus tard pour accéder fondamentalement Lorsque les conditions qui ont été établies avant sans avoir à les envelopper pour créer Ou l'entre eux.

Comme chaque méthode d'extension retourne IQueryable <> Je comprends qu'il devrait avoir les connaissances sur l'état actuel des conditions de requête quelque part, ce qui me porte à croire qu'il devrait y avoir un moyen automatisé ou la création d'un ou dans toutes les précédentes Lorsque les conditions sans avoir à envelopper ce que vous voulez séparées par OR.

Était-ce utile?

La solution

Je suppose que les différentes parties de la requête ne sont connus à l'exécution, à savoir que vous ne pouvez pas simplement utiliser || dans un where ...

L'une des options est paresseux Concat - mais cela tend à conduire à une mauvaise TSQL etc; cependant, j'ai tendance à être enclin à écrire Expressions personnalisées à la place. L'approche à prendre dépend de ce que le fournisseur est, comme LINQ to SQL prend en charge les différentes options pour EF (par exemple) - qui a un impact réel ici (puisque vous ne pouvez pas utiliser des sous-expressions avec EF). Pouvez-vous nous dire qui?


Voici un code qui devrait fonctionner avec LINQ to SQL; si vous construisez un tableau (ou la liste, et appelez .ToArray()) des expressions, il devrait fonctionner correctement; exemple LINQ-à-objets, mais doit encore travailler:

    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);
    }

Autres conseils

Utilisez PredicateBuilder<T> . Il est probablement ce que vous voulez.

    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);

Dans un monde idéal, je pense personnellement les opérateurs de || et && seraient les plus simples et lisibles. Cependant, il ne compilera pas.

  

opérateur '||' ne peut pas être appliquée aux opérandes de type « Expression<Func<YourClass,bool>> » et « Expression<Func<YourClass,bool>> »

Par conséquent, j'utilise une méthode d'extension pour cela. Dans votre exemple, il ressemblerait à ceci:  .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)).

Au lieu de:

.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now)).

Exemple d'expression:

private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
      return post => post.PostedOn >= cutoffDate;
};

Méthode d'extension:

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
} 

Un très bon article sur les requêtes LINQ par extension des expressions. En outre la source de la méthode d'extension que je l'utilise.

https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top