Construire une arborescence d'expression pour LINQ en utilisant List Méthode contenant

c# entity-framework expression-trees linq

Question

Problème

Je travaille sur la refactorisation de requêtes LINQ pour plusieurs rapports dans notre application Web et j'essaie de déplacer certains prédicats de requête en double dans leurs propres méthodes exension IQueryable afin que nous puissions les réutiliser ultérieurement. Comme vous pouvez probablement en déduire, j'ai déjà modifié le prédicat pour les groupes, mais le prédicat pour les codes me pose des problèmes. Voici un exemple de l'une des méthodes de rapport que j'ai jusqu'à présent:

Méthode DAL:

public List<Entities.QueryView> GetQueryView(Filter filter)
{
    using (var context = CreateObjectContext())
    {
        return (from o in context.QueryViews
                    where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
                    && (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
                    select o)
                .WithCode(filter)
                .InGroup(filter)
                .ToList();
    }
}

Extension IQueryable :

public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
    List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);

    if (codes.Count > 0)
        return query.Where(Predicates.FilterByCode<T>(codes));

    return query;
}

Prédicat:

public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
    // Method info for List<string>.Contains(code).
    var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });

    // List of codes to call .Contains() against.
    var instance = Expression.Variable(typeof(List<string>), "codes");

    var param = Expression.Parameter(typeof(T), "j");
    var left = Expression.Property(param, "Code");
    var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));

    // j => codes.Contains(j.Code)
    return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}

Le problème que je rencontre est que Queryable.Where n'accepte pas un type d' Expression<Func<T, List<string>, bool> . La seule façon pour moi de créer ce prédicat de manière dynamique consiste à utiliser deux paramètres, la partie qui me stoppe vraiment.

Ce que je ne comprends pas, c'est que la méthode suivante fonctionne. Je peux transmettre l'expression lambda exacte que je tente de créer de manière dynamique et filtre correctement mes données.

public List<Entities.QueryView> GetQueryView(Filter filter)
{
    // Get the codes here.
    List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);

    using (var context = CreateObjectContext())
    {
        return (from o in context.QueryViews
                    where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
                    && (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
                    select o)
                .Where(p => codes.Contains(p.Code)) // This works fine.
                //.WithCode(filter)
                .InGroup(filter)
                .ToList();

        }

    }

Des questions

  1. Puis-je implémenter mon propre Queryable.Where overload? Si oui, est-ce même faisable?
  2. Si une surcharge n'est pas réalisable, existe-t-il un moyen de construire dynamiquement le prédicat p => codes.Contains(p.Code) sans utiliser deux paramètres?
  3. Y a-t-il un moyen plus facile de faire cela? Je sens que je manque quelque chose.

Réponse acceptée

  1. Vous pouvez créer votre propre méthode d’extension, le nommer Where , accepter un IQueryable<T> , renvoyer un IQueryable<T> et le contraindre à émuler la forme des méthodes LINQ. Il ne serait pas une méthode LINQ, mais il ressemblerait à un. Je vous découragerais d'écrire une telle méthode simplement parce que cela confondrait probablement les autres; même si vous voulez créer une nouvelle méthode d'extension, utilisez un nom non utilisé dans LINQ afin d'éviter toute confusion. En bref, faites ce que vous faites maintenant, créez de nouvelles extensions sans les nommer réellement Where . Si vous voulez vraiment un nom Where si rien ne vous empêche.

  2. Bien sûr, utilisez un lambda:

    public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
        where T : ICoded //some interface with a `Code` field
    {
        return p => codes.Contains(p.Code);
    }
    

    Si vos entités ne peuvent vraiment pas implémenter une interface (indice: vous le pouvez presque certainement), le code sera alors identique au code que vous avez, mais utilisez la liste que vous transmettez comme constante plutôt que comme un nouveau paramètre:

    public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
    {
        var methodInfo = typeof(List<string>).GetMethod("Contains", 
            new Type[] { typeof(string) });
    
        var list = Expression.Constant(codes);
    
        var param = Expression.Parameter(typeof(T), "j");
        var value = Expression.Property(param, "Code");
        var body = Expression.Call(list, methodInfo, value);
    
        // j => codes.Contains(j.Code)
        return Expression.Lambda<Func<T, bool>>(body, param);
    }
    

    J'encourage fortement l'utilisation de l'ancienne méthode; cette méthode perd la sécurité de type statique, et est plus complexe et donc plus difficile à maintenir.

    Une autre remarque, le commentaire que vous avez dans votre code: // j => codes.Contains(j.Code) n’est pas précis. À quoi ressemble réellement ce lambda est: (j, codes) => codes.Contains(j.Code); ce qui est en fait sensiblement différent.

  3. Voir la première moitié du # 2.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow