LINQ to entity - Construire où des clauses pour tester les collections dans une relation plusieurs à plusieurs

StackOverflow https://stackoverflow.com/questions/110314

Question

J'utilise donc le framework d'entités Linq. J'ai 2 entités: Contenu et Balise . Ils entretiennent une relation plusieurs à plusieurs. Contenu peut avoir plusieurs balises et Balise peut avoir plusieurs Contenu . J'essaie donc d'écrire une requête pour sélectionner tout le contenu où les noms de balises sont égaux à blah

Les deux entités ont une collection de l’autre entité sous forme de propriété (mais pas d’ID). C'est là que je me bats. J'ai une expression personnalisée pour contient (ainsi, quiconque m'aidera, vous pouvez supposer que je peux créer un "contient" pour une collection). J'ai reçu cette expression de: http://forums.microsoft.com/ MSDN / ShowPost.aspx? PostID = 2670710 & SiteID = 1

Modifier 1

J'ai fini par trouver ma propre réponse.

Était-ce utile?

La solution

Après avoir pris connaissance du PredicateBuilder , lisez tous les merveilleux messages envoyés par les internautes. moi, en postant sur d’autres sites, puis en lisant plus sur Combinaison de prédicats et de Cartographie des fonctions canoniques .. oh et j’en ai appris un peu à partir des appel des fonctions dans les requêtes LINQ (certaines de ces les classes ont été prises à partir de ces pages).

J'ai ENFIN une solution !!! Bien qu'il y ait un morceau qui est un peu piraté ...

Terminons la partie piratée avec: (

Je devais utiliser un réflecteur et copier la classe ExpressionVisitor qui est marquée comme interne. J'ai ensuite dû apporter quelques modifications mineures à son fonctionnement. Je devais créer deux exceptions (car il s'agissait de nouvelles exceptions internes. Je devais également modifier le retour de la méthode ReadOnlyCollection () à partir de:

return sequence.ToReadOnlyCollection<Expression>();

À:

return sequence.AsReadOnly();

Je posterais le cours, mais il est assez volumineux et je ne veux pas encombrer ce post plus qu’il ne le sera déjà. J'espère qu'à l'avenir cette classe pourra être supprimée de ma bibliothèque et que Microsoft la rendra publique. Passons à autre chose ...

J'ai ajouté une classe ParameterRebinder:

public class ParameterRebinder : ExpressionVisitor {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
            return new ParameterRebinder(map).Visit(exp);
        }

        internal override Expression VisitParameter(ParameterExpression p) {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement)) {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }

Ensuite, j'ai ajouté une classe ExpressionExtensions:

public static class ExpressionExtensions {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.And);
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.Or);
        }
    }

Et la dernière classe que j'ai ajoutée était PredicateBuilder:

public static class PredicateBuilder {
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

}

C'est mon résultat ... J'ai pu exécuter ce code et récupérer le "contenu" résultant. entités ayant un & tag; tag " correspondant entités des tags que je cherchais!

    public static IList<Content> GetAllContentByTags(IList<Tag> tags) {
        IQueryable<Content> contentQuery = ...

        Expression<Func<Content, bool>> predicate = PredicateBuilder.False<Content>();

        foreach (Tag individualTag in tags) {
            Tag tagParameter = individualTag;
            predicate = predicate.Or(p => p.Tags.Any(tag => tag.Name.Equals(tagParameter.Name)));
        }

        IQueryable<Content> resultExpressions = contentQuery.Where(predicate);

        return resultExpressions.ToList();
    }

Faites-moi savoir si quelqu'un a besoin d'aide pour faire la même chose, si vous souhaitez que je vous envoie des fichiers à cet effet, ou si vous souhaitez simplement plus d'informations.

Autres conseils

En résumé ...

contentQuery.Where(
    content => content.Tags.Any(tag => tags.Any(t => t.Name == tag.Name))
);

Alors, est-ce ce à quoi vous vous attendiez?

Je suis un peu confus.

C’est ce que la question elle-même demande:

contentQuery.Where(
    content => content.Tags.Any(tag => tag.Name == "blah")
);

Je ne suis vraiment pas sûr du processus de réflexion pour en arriver au code du questionneur, et je ne suis pas tout à fait sûr de ce que cela fait vraiment. La seule chose dont je suis vraiment sûr, c’est que l’appel .AsQueryable () est totalement inutile - soit .Tags est déjà un IQueryable, soit le .AsQueryable () va simplement le simuler pour vous - en ajoutant des appels supplémentaires où il n'est pas nécessaire qu'il y en ait.

L'erreur est liée à la variable 'tags'. LINQ to Entities ne prend pas en charge un paramètre qui est une collection de valeurs. Il suffit d'appeler tags.AsQueryable () - comme suggéré dans une réponse plus ancienne - ne fonctionnera pas non plus, car le fournisseur de requêtes LINQ en mémoire par défaut n'est pas compatible avec LINQ to Entities (ou d'autres fournisseurs relationnels).

Pour contourner le problème, vous pouvez créer manuellement le filtre à l'aide de l'API d'expression (voir ce message de forum ) et appliquez-le comme suit:

var filter = BuildContainsExpression<Element, string>(e => e.Name, tags.Select(t => t.Name));
var query = source.Where(e => e.NestedValues.Any(filter));
tags.Select(testTag => testTag.Name)

D'où est-ce que la variable tags est initialisée? Qu'est-ce que c'est?

REMARQUE: modifiez la question elle-même, plutôt que d'y répondre par une réponse. Il ne s'agit pas d'un fil de discussion. Ils peuvent se réorganiser à tout moment

Si vous recherchez tous les contenus marqués d'un ensemble de balises:

IEnumerable<Tag> otherTags;
...
var query = from content in contentQuery
            where content.Tags.Intersection(otherTags).Any()
            select content;

Il semblerait que vous utilisiez probablement LINQ To SQL. Dans ce cas, il serait peut-être préférable d'écrire une procédure stockée pour effectuer celle-ci: utiliser LINQ pour ce faire ne s'exécutera probablement pas sur SQL Server. essaiera de tout extraire de contentQuery et d'extraire toutes les collections .Tags . Il faudrait cependant que je mette en place un serveur pour vérifier cela.

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