LINQ to entity - Costruire dove clausole per testare raccolte all'interno di una relazione da molte a molte

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

Domanda

Quindi, sto usando il framework di entità Linq. Ho 2 entità: Content e Tag . Hanno una relazione molti-a-molti tra loro. Content può avere molti Tag e Tag può avere molti Contenuti . Quindi sto cercando di scrivere una query per selezionare tutti i contenuti in cui i nomi dei tag sono uguali a blah

Le entità hanno entrambe una raccolta dell'altra entità come proprietà (ma non ID). Questo è dove sto lottando. Ho un'espressione personalizzata per Contiene (quindi, chiunque mi possa aiutare, puoi presumere che io possa fare un "contiene" per una raccolta). Ho ottenuto questa espressione da: http://forums.microsoft.com/ MSDN / ShowPost.aspx PostID = 2670710 & amp;? SiteID = 1

Modifica 1

Ho finito per trovare la mia risposta.

È stato utile?

Soluzione

Dopo aver letto le PredicateBuilder , leggendo tutti i meravigliosi post a cui le persone hanno inviato io, pubblicando su altri siti e poi leggendo di più su Combinazione di predicati e Mappatura delle funzioni canoniche .. oh e ho raccolto un po 'da Chiamare le funzioni nelle query LINQ (alcune di queste le lezioni sono state prese da queste pagine).

FINALMENTE ho una soluzione !!! Anche se c'è un pezzo un po 'hackerato ...

Riprendiamo il pezzo hackerato con :(

Ho dovuto usare il riflettore e copiare la classe ExpressionVisitor contrassegnata come interna. Ho dovuto quindi apportare alcune modifiche minori per farlo funzionare. Ho dovuto creare due eccezioni (perché si trattava di nuove eccezioni interne. Ho dovuto anche modificare il ritorno del metodo ReadOnlyCollection () da:

return sequence.ToReadOnlyCollection<Expression>();

A:

return sequence.AsReadOnly();

Vorrei pubblicare la lezione, ma è piuttosto grande e non voglio ingombrare questo post più di quanto non lo sarà già. Spero che in futuro quella classe possa essere rimossa dalla mia libreria e che Microsoft la renderà pubblica. Andando avanti ...

Ho aggiunto una 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);
        }
    }

Quindi ho aggiunto una 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);
        }
    }

E l'ultima classe che ho aggiunto è stata 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; }

}

Questo è il mio risultato ... Sono stato in grado di eseguire questo codice e recuperare il "contenuto" risultante entità con corrispondenza " tag " entità dai tag che stavo cercando!

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

Per favore fatemi sapere se qualcuno ha bisogno di aiuto con questa stessa cosa, se volete che vi invii i file per questo, o che abbiate solo bisogno di maggiori informazioni.

Altri suggerimenti

Riassumendo ...

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

Quindi è quello che ti aspetti?

Sono un po 'confuso.

Questo è ciò che la domanda stessa richiede:

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

Non sono sicuro di quale sia stato il processo di pensiero per arrivare al codice dell'interrogatore, davvero, e non sono del tutto sicuro di cosa stia realmente facendo. L'unica cosa di cui sono veramente sicuro è che la chiamata .AsQueryable () è completamente inutile - o .Tags è già un IQueryable, o .AsQueryable () sta per falsificarlo per te - aggiungendo chiamate extra in cui non è necessario che ce ne siano.

L'errore è legato alla variabile 'tags'. LINQ to Entities non supporta un parametro che è una raccolta di valori. La semplice chiamata di tag.AsQueryable () - come suggerito in una risposta più efficace - non funzionerà neanche perché il provider di query LINQ in memoria predefinito non è compatibile con LINQ to Entities (o altri provider relazionali).

Per ovviare al problema, puoi creare manualmente il filtro utilizzando l'API di espressione (vedi questo post sul forum ) e applicalo come segue:

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)

Da dove viene inizializzata la variabile tag? Che cos'è?

NOTA: modifica la domanda stessa, piuttosto che rispondere con una risposta: questa non è una discussione, e possono riordinare se stessi in qualsiasi momento

Se stai cercando tutti i contenuti contrassegnati con uno qualsiasi di un set di tag:

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

Sembra che tu stia usando LINQ To SQL, nel qual caso potrebbe essere meglio se scrivi una procedura memorizzata per farlo: usare LINQ per farlo probabilmente non funzionerà su SQL Server - è molto probabile proverà a rimuovere tutto da contentQuery e a recuperare tutte le raccolte .Tags . Dovrei effettivamente impostare un server per verificarlo, però.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top