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