El el primer tip de rendimiento sobre EF 4.0, #1, hablamos sobre las diferencias que se producían si en nuestras consultas sobre L2E hardcodeábamos valores o hacíamos uso de variables. Básicamente mientras el uso de variables provocaba que las consultas fueran parametrizadas el hecho de establecer valores directamente hacía que nuestras consultas fueran adhoc y por lo tanto un trashing de nuestra cache de planes de ejecución.
Este comportamiento es lo normal y la recomendación es nunca hardcodear valores, aunque hay cierto tipo de consultas que no se comportan como quizás deberían y aunque sigamos las recomendaciones podremos observar alguna consulta AdHoc. La que yo conozco, eso no quita que pueda existir algún caso más tiene que ver con el uso de los métodos extensores SKIP y TAKE de L2E, los cuales por alguna razón, que la sé y no me convence para nada, ‘funcletizan’ los valores y eliminan la posiblidad de hacer las consultas parametrizadas.
Vamos a la práctica y pongamos las siguientes lineas de código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<span class="kwrd">class</span> Program { <span class="kwrd">static</span> <span class="kwrd">void</span> Main(<span class="kwrd">string</span>[] args) { PagWithL2E(0, 10); PagWithESQL(0, 10); } <span class="kwrd">static</span> <span class="kwrd">void</span> PagWithL2E(<span class="kwrd">int</span> pageIndex, <span class="kwrd">int</span> pageCount) { <span class="kwrd">int</span> skip = pageIndex * pageCount; <span class="kwrd">using</span> (NLayerApp context = <span class="kwrd">new</span> NLayerApp()) { List<Customer> customers = context.Customers.OrderBy(c => c.CustomerCode) .Skip(skip) .Take(pageCount) .ToList(); } } <span class="kwrd">static</span> <span class="kwrd">void</span> PagWithESQL(<span class="kwrd">int</span> pageIndex, <span class="kwrd">int</span> pageCount) { <span class="kwrd">int</span> skip = pageIndex * pageCount; <span class="kwrd">using</span> (NLayerApp context = <span class="kwrd">new</span> NLayerApp()) { List<Customer> customers = context.Customers.Skip(<span class="str">"it.CustomerCode"</span>,<span class="str">"@skip"</span>,<span class="kwrd">new</span> ObjectParameter(<span class="str">"skip"</span>,skip)) .Top(<span class="str">"@limit"</span>, <span class="kwrd">new</span> ObjectParameter(<span class="str">"limit"</span>, pageCount)) .ToList(); } } } |
1 |
|
Ámbos métodos obtienen una colección de Customer de forma paginada y hacen uso de variables para indicar los parámetros de paginación. Según lo expuesto por mi en el tip #1 la consulta en L2E debería de ser parametrizada ¿verdad?. Pues veamos los resultados haciendo una consulta sobre la vista administrada que nos da los elementos almacenados en la cache de planes de ejecución de Sql Server.
Como se puede observar, la fila 2 que se corresponde en este caso a la consulta en ESQL ha parametrizado la misma, mientras que la consulta 3, L2E, es una consulta AdHoc.
Bien, ya hemos expuesto el problema, ahora toca hablar de las soluciones. Lógicamente ya hemos expuesto una de ellas, sustituir las consultas que usan paginación escritas en L2E por consultas que usen ESQL, dentro de poco trataré de enseñar como construir un método extensor que nos ayude en esta tarea. Otra de las posibles soluciones es tratar de “confiar” en el mecanismo de auto parametrización de Sql Server, aunque esto no se si sería demasiado realista. Para terminar, el tercer workaround, para intentar solventar este problema sería hacer uso de CompiledQuery tal y como vemos a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<span class="kwrd">class</span> Program { <span class="kwrd">static</span> <span class="kwrd">void</span> Main(<span class="kwrd">string</span>[] args) { PagWithL2E(0, 10); PagWithESQL(0, 10); } <span class="kwrd">static</span> Func<NLayerApp, <span class="kwrd">int</span>, <span class="kwrd">int</span>, IQueryable<Customer>> pagedQueryCompiled = CompiledQuery.Compile<NLayerApp, <span class="kwrd">int</span>, <span class="kwrd">int</span>, IQueryable<Customer>>((ctx, skip, pageCount) => ctx.Customers.OrderBy(c => c.CustomerCode) .Skip(skip) .Take(pageCount) ); <span class="kwrd">static</span> <span class="kwrd">void</span> PagWithL2E(<span class="kwrd">int</span> pageIndex, <span class="kwrd">int</span> pageCount) { <span class="kwrd">int</span> skip = pageIndex * pageCount; <span class="kwrd">using</span> (NLayerApp context = <span class="kwrd">new</span> NLayerApp()) { List<Customer> customers = pagedQueryCompiled.Invoke(context, skip, pageCount) .ToList(); } } <span class="kwrd">static</span> <span class="kwrd">void</span> PagWithESQL(<span class="kwrd">int</span> pageIndex, <span class="kwrd">int</span> pageCount) { <span class="kwrd">int</span> skip = pageIndex * pageCount; <span class="kwrd">using</span> (NLayerApp context = <span class="kwrd">new</span> NLayerApp()) { List<Customer> customers = context.Customers.Skip(<span class="str">"it.CustomerCode"</span>,<span class="str">"@skip"</span>,<span class="kwrd">new</span> ObjectParameter(<span class="str">"skip"</span>,skip)) .Top(<span class="str">"@limit"</span>, <span class="kwrd">new</span> ObjectParameter(<span class="str">"limit"</span>, pageCount)) .ToList(); } } } |
Si examinamos ahora los planes de ejecución veremos como ahora, ambas consultas son parametrizadas.
Saludos
Unai
Muy interesante …
aunque no son la misma consulta: en la consulta L2E utilizas Take y realizas un OrderBy previo, mientras que en la consulta de ESQL y usas Top y no haces ordenacion.
¿se comportaria igual la consulta de ESQL usando Take?
Saludos,
Pedro
Perdona Pedro, pero estas equivocado, SON exáctamente la misma consulta. En primer lugar el método Take no está en ESQL como método de construcción de consultas, su sinónimo es TOP, en segundo lugar claro que se ordena, si te fijas el método de construcción skip te obliga a indicar la ordenación, en este caso it.CustomerCode, que es exáctamente por lo que se ordena tb en L2E, por lo tanto creo que tu pregunta está respondida
Después de una serie un poco grande de post sobre una temática concreta siempre está bien hacer una pequeña