2009-03-25 5 views
6

Existe-t-il un moyen d'utiliser la méthode CompiledQuery.Compile pour compiler l'expression associée à un IQueryable? Actuellement, j'ai un IQueryable avec un très grand arbre d'expression derrière lui. L'IQueryable a été construit en utilisant plusieurs méthodes qui fournissent chacune des composants. Par exemple, deux méthodes peuvent renvoyer IQueryables qui sont ensuite jointes dans un tiers. Pour cette raison, je ne peux pas définir explicitement l'expression entière dans l'appel de méthode compile().Compiler des requêtes Linq vers SQL à partir d'un IQueryable non trivial

J'espérais passer l'expression dans la méthode de compilation sous la forme someIQueryable.Expression, mais cette expression n'est pas dans la forme requise par la méthode de compilation. Si je tente de contourner ce problème en mettant la requête directement dans la méthode de compilation, par exemple:

var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers()); 
    var bar = foo(this); 

où je fais le formulaire d'appel dans un datacontext, je reçois une erreur disant que « getUsers ne sont pas mises en correspondance comme stocké procédure ou fonction définie par l'utilisateur ". Encore une fois, je ne peux pas simplement copier le contenu de la méthode getUsers à l'endroit où je fais l'appel de compilation car il utilise à son tour d'autres méthodes.

Existe-t-il un moyen de transmettre l'expression sur le IQueryable retourné de "getUsers" dans la méthode Compile?

Mise à jour J'ai essayé de forcer ma volonté sur le système avec le code suivant:

var phony = Expression.Lambda<Func<DataContext, IQueryable<User>>>(
     getUsers().Expression, Expression.Parameter(typeof(DataContext), "dc")); 

    Func<DataContext, IQueryable<User>> wishful = CompiledQuery.Compile<DataContext, IQueryable<User>>(phony); 
    var foo = wishful(this); 

foo finit par être:

{System.Data.Linq.SqlClient.SqlProvider + OneTimeEnumerable `1 [Model.Entities.User]}

Je n'ai pas la possibilité de voir les résultats dans foo, car au lieu d'offrir d'étendre les résultats et d'exécuter la requête, je ne vois que le message" Operation coul d déstabilise l'exécution ". J'ai juste besoin de trouver un moyen pour que la chaîne sql ne soit générée qu'une seule fois et utilisée comme commande paramétrée sur les requêtes suivantes, je peux le faire manuellement en utilisant la méthode GetCommand sur le contexte de données, mais je dois explicitement définir tous les paramètres et faire moi-même le mappage des objets, qui est quelques centaines de lignes de code étant donné la complexité de cette requête particulière.

Mise à jour

John Rusk a fourni les informations les plus utiles, alors je lui ai accordé la victoire sur celui-ci. Cependant, quelques ajustements supplémentaires ont été nécessaires et il y avait quelques autres problèmes que j'ai rencontrés en cours de route, alors j'ai pensé que j'allais «élargir» sur la réponse. Tout d'abord, l'erreur 'Operation could destalization the runtime' n'était pas due à la compilation de l'expression, c'était en fait à cause d'une coulée profonde dans l'arbre d'expression. Dans certains endroits, j'avais besoin d'appeler la méthode .Cast<T>() pour lancer des objets, même s'ils étaient du bon type. Sans entrer dans trop de détails, cela était essentiellement nécessaire lorsque plusieurs expressions étaient combinées dans un seul arbre et que chaque branche pouvait renvoyer un type différent, qui étaient chacun le sous-type d'une classe commune. Après avoir résolu le problème de déstabilisation, je suis revenu au problème de compilation. La solution d'expansion de John était presque là. Il a cherché des expressions d'appel de méthode dans l'arbre et a tenté de les résoudre à l'expression sous-jacente que cette méthode retournerait habituellement. Mes références aux expressions n'étaient pas fournies par les appels de méthodes, mais plutôt par les propriétés.Donc, je devais modifier le visiteur d'expression qui effectue l'expansion pour inclure ces types:

protected override Expression VisitMemberAccess(MemberExpression m) { 
    if(m.Method.DeclaringType == typeof(ExpressionExtensions)) { 
     return new ExpressionExpander().Visit((Expression)(((System.Reflection.PropertyInfo)m.Member).GetValue(null, null))); 
    } 
    return base.VisitMemberAccess(m); 
} 

Cette méthode ne peut pas être appropriée dans tous les cas, mais il devrait aider toute personne qui se trouve dans la même situation.

+2

Pouvez-vous s'il vous plaît fournir plus de détails sur la façon dont vous avez réglé ce qui précède? Signification Je peux voir le code que vous avez mis dans la mise à jour mais où le mettre et il ne compile pas - "m.Method.DeclaringType" ne fait pas partie de MemberExpression et ExpressionExtensions n'existe pas. –

+0

J'essaie de faire quelque chose de similaire avec la manipulation de l'arbre d'expression, et j'ai quelques questions concernant l'implémentation ci-dessus. Où sont définies ExpressionExpander et ExpressionExtensions? Pour autant que je puisse voir, ils ne font pas partie de LinqKit. Peut-être qu'ils faisaient partie d'une version plus ancienne? – Mel

+0

En effet, il est très ennuyeux que l'échantillon de code soit incomplet. ExpressionExtensions est probablement sa propre classe qui remplace VisitMemberAccess, et m.Method.DeclaringType est probablement supposé être m.Member.DeclaringType. Mais le vrai problème est. GetValue (null, null) ne fonctionne tout simplement pas. – Charles

Répondre

2

Quelque chose comme cela fonctionne, au moins dans mes tests:

Expression<Func<DataContext, IQueryable<User>> queryableExpression = GetUsers(); 
var expressionWithSomeAddedStuff = (DataContext dc) => from u in queryableExpression.Invoke(dc) where ....; 
var expressionThatCanBeCompiled = expressionWithSomeAddedStuff.Expand(); 
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(expressionThatCanBeCompiled); 

Cela ressemble un peu bavard, et il y a probablement des améliorations que vous pouvez faire. Le point clé est qu'il utilise les méthodes Invoke et Expand de LinqKit. Ils vous permettent essentiellement de créer une requête, à travers la composition, puis de compiler le résultat final.

+0

Merci pour ça, je vais devoir vérifier. Bien que mon approche compile, je ne suis pas sûr si elle compile réellement l'arbre d'expression en une seule fonction, ou si elle fait simplement passer les appels de méthode dans la fonction retournée, ce qui apporterait très peu d'amélioration des performances. Si l'approche expand est meilleure, je la définirai comme la réponse acceptée – LaserJesus

+0

Lorsque j'essaie d'exécuter la méthode Expand sur ma requête, j'obtiens l'erreur suivante: Impossible de lancer l'objet de type 'System.Linq.Expressions.MemberExpression' vers tapez 'System.Linq.Expressions.LambdaExpression' – LaserJesus

+0

Je ne suis pas sûr à 100% ce que cela signifie. Il se peut que l'expression enfant invoquée avec Invoke soit une expression MemberExpression plutôt qu'une expression Lambda. (Le premier correspond à un ".Quelque" alors que le dernier correspond à "() => ..." - c'est-à-dire un corps avec 0 ou plus de paramètres LinqKit est livré avec la source complète.tout ce que je peux suggérer, je suis peur, est que vous déboguez à travers elle .... –