Compiler des requêtes Linq to SQL à partir d’un IQueryable non trivial

.net expression-trees linq-to-sql performance

Question

Y a-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 celui-ci. IQueryable a été construit à l'aide de plusieurs méthodes, chacune fournissant des composants. Par exemple, deux méthodes peuvent renvoyer IQueryables, qui sont ensuite jointes dans une troisième. Pour cette raison, je ne peux pas définir explicitement l'expression entière dans l'appel à la méthode compile ().

J'espérais transmettre l'expression à la méthode de compilation sous la forme someIQueryable.Expression. Toutefois, cette expression ne se présente pas sous la forme requise par la méthode de compilation. Si j'essaie de contourner ce problème en plaçant 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);

Lorsque je crée le formulaire d'appel dans un contexte de données, j'obtiens une erreur disant que "getUsers n'est pas mappé en tant que procédure stockée 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 lance l'appel de compilation car celui-ci utilise à son tour d'autres méthodes.

Existe-t-il un moyen de passer l'expression sur IQueryable renvoyé par "getUsers" dans la méthode Compile?

Mise à jour j'ai essayé d'imposer ma volonté sur le système avec le code suivant:

    var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
    var bar = foo(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 de proposer d'élargir les résultats et d'exécuter la requête, je ne vois que le message "L'opération pourrait déstabiliser l'exécution".

Je dois juste trouver un moyen de ne générer qu'une seule fois la chaîne SQL et de l'utiliser comme commande paramétrée lors de requêtes ultérieures. Je peux le faire manuellement à l'aide de la méthode GetCommand sur le contexte de données, mais je dois ensuite définir explicitement tous les paramètres et mapper moi-même l'objet, ce qui représente quelques centaines de lignes de code, compte tenu de la complexité de cette requête.

Actualisé

John Rusk a fourni les informations les plus utiles. Je lui ai donc décerné la victoire. Cependant, quelques ajustements supplémentaires étaient nécessaires et il y avait quelques autres problèmes que j'ai rencontrés en cours de route, alors je pensais que je devais développer la réponse. Premièrement, l'erreur «L'opération pourrait déstabiliser le runtime» n'est pas due à la compilation de l'expression, mais bien à une erreur dans l'arborescence de l'expression. À certains endroits, j'avais besoin d'appeler la .Cast<T>() pour lancer formellement des éléments, même s'ils étaient du type correct. Sans entrer trop dans les détails, cela était fondamentalement nécessaire lorsque plusieurs expressions étaient combinées dans un seul arbre et que chaque branche pouvait renvoyer un type différent, qui était 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 recherché des expressions d'appel de méthode dans l'arborescence et a tenté de les résoudre en tant qu'expression sous-jacente que cette méthode retournerait habituellement. Mes références aux expressions n'ont pas été fournies par des appels de méthodes, mais par des propriétés. Il me fallait donc modifier le visiteur d'expression qui effectue l'extension pour inclure ces types:

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

Cette méthode peut ne pas être appropriée dans tous les cas, mais elle devrait aider tous ceux qui se trouvent dans la même situation.

Réponse acceptée

Quelque chose comme ça marche, du 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 semble un peu bavard et il y a probablement des améliorations à apporter.

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, via la composition, puis de compiler le résultat final.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi