8
votes

Comment mettre à jour une expression LINQ avec des paramètres supplémentaires?

J'ai une expression LINQ, qui peut être modifiée en fonction de certaines conditions. Un exemple de ce que j'aimerais faire (laissé vide le bit, je ne suis pas sûr de): xxx pré>

Comment mettre à jour le filtre pour ajouter des paramètres supplémentaires? P>

Au moment où tous les enregistrements sont récupérés, alors j'utilise un où code> pour filtrer davantage les résultats. Toutefois, cela entraîne plus de questions à la base de données que de manière strictement nécessaire. P>

public static class LinqExtensionMethods
{
    public static Expression<Func<T, bool>> CombineOr<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.CombineOr();
    }

    public static Expression<Func<T, bool>> CombineOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }
        Expression<Func<T, bool>> firstFilter = filters.First();

        var lastFilter = firstFilter;
        Expression<Func<T, bool>> result = null;
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body);
            result = Expression.Lambda<Func<T, bool>>(Expression.OrElse(nextExpression, nextFilter.Body), nextFilter.Parameters);
            lastFilter = nextFilter;
        }
        return result;
    }

    public static Expression<Func<T, bool>> CombineAnd<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.CombineAnd();
    }

    public static Expression<Func<T, bool>> CombineAnd<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }
        Expression<Func<T, bool>> firstFilter = filters.First();

        var lastFilter = firstFilter;
        Expression<Func<T, bool>> result = null;
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body);
            result = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(nextExpression, nextFilter.Body), nextFilter.Parameters);
            lastFilter = nextFilter;
        }
        return result;
    }

    class ReplaceVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public ReplaceVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
}


4 commentaires

Quel est le type de retour et les interlans de projecteur.get (filtre); ?


Qu'est-ce que showached ? Est-ce qu'il énumère variables ?


ShowRarchived est juste un booléen


Maintenant que vous avez ajouté obtenir, la Tolist () est une douleur; J'ai ajouté un exemple d'expression-réécriture que vous devriez pouvoir utiliser pour combiner les deux filtres avant appeler


5 Réponses :


15
votes

Si je comprends la question, c'est probablement le problème: xxx

tout travail sur Projets va utiliser énumérable , pas requérablement ; Il devrait probablement être: xxx

Ce dernier est composable et . Où devrait fonctionner comme vous vous attendez, construisez Une requête plus restrictive avant l'envoyant au serveur.

Votre autre option consiste à réécrire le filtre à combiner avant d'envoyer: xxx

ou réécrit de manière à permettre une utilisation pratique: xxx


9 commentaires

Le type de données ici n'a pas d'importance.


@Oybek la différence entre énumérable.Où et requisable.Lowle matière Très - pouvez-vous clarifier ce que vous dites peu importe?


Cela fonctionne, c'est juste que le projecteur.get (filtre) obtient tous les enregistrements de la base de données, puis le lieu de la base de données à nouveau. Je veux faire la requête de la base de données une seule fois. Le deuxième bit de code est comment je le fais maintenant


@Samwm Il existe une distinction importante entre ienumbertables et iquéryable . Cependant, l'appel à obtenir ne devrait probablement pas toucher la base de données du tout si elle renvoie une requête; Pouvez-vous montrer comment obtenir est écrit?


Lorsque vous récupérez à l'aide de la méthode ici, il renvoie un certain objet. iEnumerable ou Iquerible Voici simplement des références qui pointent sur cet objet. L'objet tangible se comportera sur comme il se doit. S'il est requis, il produira une arborescence d'expression prologie dans tous les cas.


@Oybek non, pas vrai; Le type de la variable (pas l'objet) est crucial ici; S'il est seulement connu d'être ienumerable (même s'il s'agit en réalité quelque chose qui implémente iquéryable ), il utilisera énumérable. , qui est linq-to-objets. Il ne "compose" pas la requête. Désolé, mais ce que vous dites est faux.


@Samwm j'ai ajouté une deuxième approche, de réécriture d'expression-arborescence; Vous pouvez utiliser cela pour combiner deux filtres avant appeler Get - Toute utilisation?


@Samwm oui, mais il faudrait devoir être filtre = filtre.combine (x => x.b> 2.5); etc - c'est-à-dire une valeur de retour que vous attrapez. Et vous voudrez peut-être appeler cela andalso ou quelque chose


@Samwm en fait, il est probablement préférable d'utiliser andalso dans l'expression elle-même (éditera cela)



0
votes

Si vous Get CODE> La méthode Retroie les données et renvoie dans des objets de mémoire, vous pouvez le faire

Expression<Func<Project, bool>> filter = (Project p) => p.UserName == "Bob";
if(showArchived) {
     filter = (Project p) => p.UserName == "Bob" && p.Archived;
}
IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);


1 commentaires

Qui ajoute du code supplémentaire redondant. Le filtre initial peut être plus que de vérifier le nom d'utilisateur.



0
votes

Tout cela dépend de la façon dont projecteur.get () se comporter et ce qu'il revient. La voie habituelle (par exemple, Linq à SQL fait quelque chose comme ça) est qu'il renvoie un iquéryable et vous permettant (entre autres) ajoutez plus où () Clauses Avant de l'envoyer au serveur sous la forme d'une requête SQL, avec tous les où () clauses incluses. Si tel est le cas, la solution de Mark (utilisez iquerybale ) fonctionnera pour vous.

Mais si la méthode get () exécute la requête en fonction du filtre immédiatement, vous devez le transmettre tout le filtre dans l'expression. Pour ce faire, vous pouvez utiliser prédicatbuilder .


0 commentaires

0
votes

se débarrasser de tolist () et tout ira bien.


0 commentaires

1
votes

Je pense que vous voulez combiner des filtres de cette façon:

    public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.OrTheseFiltersTogether();
    }

    public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }

        Expression<Func<T, bool>> firstFilter = filters.First();

        var body = firstFilter.Body;
        var param = firstFilter.Parameters.ToArray();
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextBody = Expression.Invoke(nextFilter, param);
            body = Expression.OrElse(body, nextBody);
        }
        Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
        return result;
    }


    public static Expression<Func<T, bool>> AndTheseFiltersTogether<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.AndTheseFiltersTogether();
    }

    public static Expression<Func<T, bool>> AndTheseFiltersTogether<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }
        Expression<Func<T, bool>> firstFilter = filters.First();

        var body = firstFilter.Body;
        var param = firstFilter.Parameters.ToArray();
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextBody = Expression.Invoke(nextFilter, param);
            body = Expression.AndAlso(body, nextBody);
        }
        Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
        return result;
    }


3 commentaires

Cette approche n'est pas faux , mais est mal soutenue par de nombreux moteurs LINQ; LINQ-TO-SQL convient parfaitement à l'expression.Invoke, cependant, EF ne l'aime pas. En tant que tel, il est plus fiable (et pas de travail supplémentaire) d'utiliser l'approche "visiteur" pour combiner les prédicats directement .


Nous semblions prometteurs, veulent être aussi génériques que possible, car il existe de nombreuses classes différentes impliquées. Utiliser un cadre d'entité, mais dans l'espoir de la preuve future si quelque chose d'autre serait jamais utilisé (comme NHibernate). Comme l'idée de construire une liste de filtres, il suffit de combiner avant d'exécuter


@Samwm Idéalement, vous devriez être capable de combiner mes signatures de méthode simples avec les visiteurs d'expression de Marc pour vous rendre à une solution EF utilisée.