2017-06-08 4 views
3

Dans l'un de mes projets, j'ai un ExpressionVisitor pour traduire l'expression fournie en une chaîne de requête. Mais avant de le traduire, j'ai besoin d'évaluer toutes les références dans l'expression à des valeurs réelles. Pour ce faire, j'utilise la méthode Evaluator.PartialEval de EntityFramework Project.Evaluator.PartialEval reduce expression fournie

En supposant que j'ai cette requête:

var page = 100; 
var query = myService.AsQueryable<Product>() 
       //.Where(x=>x.ProductId.StartsWith(p.ProductId)) 
       .Skip(page) 
       .Take(page); 

var evaluatedQueryExpr = Evaluator.PartialEval(query.Expression); 

Comme vous pouvez le voir, je l'ai commenté Où méthode. Dans ce cas, evaluateQueryExpr ne contiendra pas les méthodes Take et Skip.

Cependant, si j'utilise une autre méthode avec Expression avant que tout fonctionne ou que tout fonctionne, Evaluator évalue correctement une expression et la renvoie complètement.

Je trouve que le problème se produit dans la ligne 80 de la classe Evaluator:

return Expression.Constant(fn.DynamicInvoke(null), e.Type); 

Pouvez-vous expliquer pourquoi cela se produit et suggérer une solution de contournement?

Mise à jour ici est a project on github

LinqToSolrQueriable inherited from IOrderedQueryable LinqToSolrProvider inherited from IQueryProvider y compris la gamme de ligne l'origine du problème

+0

Je ne vois pas ... 'var = evaluatedQueryExpr Evaluator.PartialEval (nouveau [] {{} nouveau produit, nouveau produit {}} .AsQueryable () .Skip (page) .Take (page) .Expression) 'sur autre' IQueryable <> '... Cela pourrait être un" effet spécial "de EF' IQueryable <> '. – xanatos

+0

Mais notez que pour le 'Skip (page)' vous n'entrerez pas dans la ligne 80: le 'Skip (page)' est déjà 'Skip (100)' dans la 'query'. La variable 'page' est" read "quand la variable' query' est initialisée. Les lignes 74-76 sont donc exécutées: 'if (e.NodeType == ExpressionType.Constant) {return e;' (* 100 * est une constante). – xanatos

+0

c'est ce que je ne peux pas comprendre. Je débogue la méthode Evaluate. Je vois que je passe une expression à cette méthode '{value (Linq.SolrQueryable'1 [Product]). Skip (100) .Take (100)}'. Mais à l'intérieur dans la ligne 80 'Expression.Constant (fn.DynamicInvoke (null), e.Type))' il est déjà converti en '{value (SolrQueryable'1 [Product])}'. Cependant, votre exemple pur fonctionne correctement :(donc pour une raison quelconque 'fn.DynamicInvoke' lâche prendre et sauter quand aucun 'where' fourni auparavant – DolceVita

Répondre

2

Les bonnes nouvelles sont que l'expression est pas vraiment réduite (Skip et Take sont toujours là :), mais est simplement converti de MethodCallExpression à ConstantExpressioncontenant l'expression originale:

query.Expression:

.Call System.Linq.Queryable.Take(
    .Call System.Linq.Queryable.Skip(
     .Constant<LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product]>(LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product]), 
     100), 
    100) 

evaluatedQueryExpr:

.Constant<System.Linq.IQueryable`1[LinqToSolrTest.Product]>(LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product]) 

Ici, l'affichage de débogage vous donne une fausse impression. Si vous prenez le ConstaintExpression.Value, vous verrez que c'est une propriété IQueryable<Product> avec Expression étant exactement la même que l'original query.Expression.

Les mauvaises nouvelles sont que ce n'est pas ce que vous attendez de PartialEval - en fait, il ne fait rien d'utile dans ce cas (sauf potentiellement casser votre logique de traduction de requête).

Alors, pourquoi cela se produit-il?

La méthode que vous utilisez de la bibliothèque EntityFramework.Extended est à son tour pris (comme il est indiqué dans les commentaires) de MSDN Sample Walkthrough: Creating an IQueryable LINQ Provider.On peut remarquer que la méthode PartialEval a deux surcharges - une avec Func<Expression, bool> fnCanBeEvaluated paramètre utilisé pour identifier si un noeud d'expression donné peut faire partie de la fonction locale (en d'autres termes, pour être partiellement évalué ou non), et un sans tel paramètre (utilisé par vous) qui appelle simplement le premier passage de l'attribut suivant:

private static bool CanBeEvaluatedLocally(Expression expression) 
{ 
    return expression.NodeType != ExpressionType.Parameter; 
} 

l'effet est qu'il arrête l'évaluation des expressions de type ParameterExpressionet des expressions contenant des directement ou indirectement ParameterExpression. Le dernier devrait expliquer le comportement que vous observez. Lorsque la requête contient Where (et essentiellement tout opérateur LINQ) avec l'expression paramétrisé lambda (d'où paramètre) avant que les appels Skip/Take, il arrêterait l'évaluation des méthodes contenant (que vous pouvez voir dans la vue de débogage query.Expression ci-dessus - l'appel Where sera à l'intérieur du Skip).

Maintenant, cette surcharge est utilisé par l'exemple MSDN pour évaluer une méthode imbriquéeWhere béton expression lambda et est généralement pas applicable pour tout type d'expression comme IQueryable.Expression. En fait, le projet lié utilise la méthode PartialEval en un seul endroit à l'intérieur de la classe QueryCache, et appelle également l'autre surcharge en passant un different predicate qui, en plus de ParameterExpressions, arrête l'évaluation de toute expression avec le type de résultat IQueryable.

qui je pense est la solution de votre problème ainsi:

var evaluatedQueryExpr = Evaluator.PartialEval(query.Expression, 
    // can't evaluate parameters or queries 
    e => e.NodeType != ExpressionType.Parameter && 
     !typeof(IQueryable).IsAssignableFrom(e.Type) 
); 
+1

Salut Ivan, merci beaucoup de passer du temps et de trouver cela! réponse et explication, maintenant cela fonctionne comme prévu – DolceVita

+0

Vous êtes wel Viens mec, content que ça a aidé :) Codage heureux! –