Costruire un predicato personalizzato per fungere da filtro utilizzando un ciclo foreach

c# expression-trees linq-expressions linqkit linq-to-entities

Domanda

Devo filtrare un elenco di documenti passandoli a un filtro personalizzato che sto cercando di creare dinamicamente utilizzando un ciclo foreach :

var mainPredicate = PredicateBuilder.True<Document>();

// mainPredicate is combined to other filters successfully here ...

var innerPredicate = PredicateBuilder.False<Document>();
foreach (var period in periods)
{
    var p = period;
    Expression<Func<Document, bool>> inPeriod =
        d => d.Date >= p.DateFrom && d.Date <= p.DateTo;

    innerPredicate = innerPredicate.Or(d => inPeriod.Invoke(d));
}

mainPredicate = mainPredicate.And(innerPredicate);

Questa ultima riga:

documents = this.ObjectSet.AsExpandable().Where(mainPredicate).ToList();

Genera questa eccezione:

Il parametro 'd' non era associato nell'espressione di query LINQ to Entities specificata.

Qualcuno sa perché sto ricevendo questa eccezione? Non capisco dove si perde il parametro 'd' che sto passando al metodo InPeriod. Non so cosa manca per far funzionare tutto questo. Il mio codice è lo stesso di molti altri esempi che funzionano perfettamente. Qualsiasi ulteriore informazione teorica teorica su espressioni che invocano e come funziona dietro le quinte sono le benvenute.

Risposta accettata

Infine, ho trovato un modo per evitare di combinare più predicati con l'albero delle espressioni principali.

Dato che ogni predicato rappresenta un filtro diverso e io voglio che il filtro finale combinato sia una serie di condizioni che devono essere rispettate , possiamo dire che ognuno dei predicati deve restituire true affinché il predicato finale restituisca true.

Affinché funzioni, i predicati devono essere combinati con AND . Quindi, la query SQL risultante deve essere simile a questa:

predicate1 AND predicate2 AND predicate3 ...

Un modo migliore per combinare questi predicati con AND è concatenare Where operatori di query alla query finale, in questo modo:

var documents = this.ObjectSet.AsExpandable()
    .Where(mainPredicate)
    .Where(otherPredicate)
    .Where(yetAnotherPredicate)
    .ToList();

La query SQL risultante combinerà ciascuno di questi predicati con AND . Questo è proprio quello che volevo fare.

È più facile che scavare un albero di espressioni da solo.


Risposta popolare

Non capisco perché lo fai:

innerPredicate = innerPredicate.Or(d => inPeriod.Invoke(d));

Quando potevi semplicemente evitare l' Invoke completamente, in questo modo:

innerPredicate = innerPredicate.Or(inPeriod);

Questo dovrebbe funzionare perfettamente.


A proposito, ho la sensazione che ci sia un bug con LINQKit qui (a meno che non ci sia qualche documentazione che suggerisce che non supporta questo scenario).

Quando ho provato questo codice simile :

 Expression<Func<int, bool>> first = p1 => p1 > 4;
 Expression<Func<int, bool>> second = p2 => p2 < 2;

// Expand is similar to AsExpandable, except it works on 
// expressions, not queryables.
var composite = first.Or(d => second.Invoke(d))
                     .Expand();

... LINQKit ha generato la seguente espressione composita:

p1 => ((p1 > 4) OrElse (d < 2)) // what on earth is d?

... che in effetti ha il parametro non associato d (NodeType = Parameter, Name = 'd').

Dodging the Invoke con first.Or(second).Expand() genera il perfettamente ragionevole:

p1 => ((p1 > 4) OrElse (p1 < 2)) // much better now...


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché