Sin lugar a dudas este es el tip de rendimiento más rebuscado de todos los visto hasta ahora, alguno podrá decir que ya los había un poco rebuscados, pero bueno, esta es mi opinión. Para tratar de explicarnos nos pondremos en situación. Supongamos que tenemos una tabla con una columna de tipo varchar, es decir, una columna no unicode, tal y como podría ser la siguiente:
CREATE TABLE [dbo].[NonUnicodeTest](
[idTest] [int] NOT NULL,
[field] [varchar](10) NOT NULL,
CONSTRAINT [PK_NonUnicodeTest] PRIMARY KEY CLUSTERED
(
[idTest] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Los que conocéis SQL sabéis que si realizamos una consulta con un filtro como WHERE field=N’ valor del campo’ podríamos tener un problema de NO uso de índices ya que esta consulta no es SARGABLE, hay una conversion de valor no unicode a valores unicode. Lo lógico es que si estamos usando Entity Framework el se ocupe por nosotros de no cometer estos errores, vamos a comprobarlo.
Si ejecutamos la siguiente consulta en Linq To Entities;
1 2 3 4 5 6 7 8 9 |
<span class="kwrd">using</span> (EF4EXEntities context = <span class="kwrd">new</span> EF4EXEntities()) { <span class="kwrd">string</span> fieldValue =<span class="str">"field value"</span>; List<NonUnicodeTest> result = (from nut <span class="kwrd">in</span> context.NonUnicodeTests <span class="kwrd">where</span> nut.field == fieldValue select nut).ToList(); Console.ReadLine(); } |
El SQL que llega a la base de datos es el siguiente:
exec sp_executesql N’SELECT
[Extent1].[idTest] AS [idTest],
[Extent1].[field] AS [field]
FROM [dbo].[NonUnicodeTest] AS [Extent1]
WHERE [Extent1].[field] = @p__linq__0′,N’@p__linq__0 varchar(8000)’,@p__linq__0=’field value’
Efectivamente, por ahora todo va bien, ADO.NET EF resuelve este problema de forma correcta, es más que ese campo sea unicode o no es una atributo de la propiedad en el modelo conceptual, CSDL.
Todo hace indicar que todo funcionará de forma correcta y este tipo de casuísticas está bien resuelto. Sin embargo vamos a probar con dos consultas adicionales:
1 2 3 4 5 6 7 8 9 10 11 |
<span class="kwrd">using</span> (EF4EXEntities context = <span class="kwrd">new</span> EF4EXEntities()) { <span class="kwrd"> string</span> field1 = <span class="str">"field1"</span>; <span class="kwrd">string</span> field2 = <span class="str">"field2"</span>; List<NonUnicodeTest> result = (from nut <span class="kwrd">in</span> context.NonUnicodeTests <span class="kwrd">where</span> nut.field == field1 || nut.field == field2 select nut).ToList(); Console.ReadLine(); } |
1 |
y |
1 2 3 4 5 6 7 8 9 10 11 |
<span class="kwrd">using</span> (EF4EXEntities context = <span class="kwrd">new</span> EF4EXEntities()) { <span class="kwrd">string</span> field1 = <span class="str">"field1"</span>; <span class="kwrd">string</span> field2 = <span class="str">"field2"</span>; List<NonUnicodeTest> result = (from nut <span class="kwrd">in</span> context.NonUnicodeTests <span class="kwrd">where</span> nut.field == field1 && nut.field == field2 select nut).ToList(); Console.ReadLine(); } |
1 |
|
Si examinamos las trazas con un profiler de SQL para estas dos consultas veremos los siguientes resultados, en el mismo orden.
exec sp_executesql N’SELECT
[Extent1].[idTest] AS [idTest],
[Extent1].[field] AS [field]
FROM [dbo].[NonUnicodeTest] AS [Extent1]
WHERE [Extent1].[field] IN (@p__linq__0,@p__linq__1)’,N’@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)’,@p__linq__0=N’field1′,@p__linq__1=N’field2′
exec sp_executesql N’SELECT
[Extent1].[idTest] AS [idTest],
[Extent1].[field] AS [field]
FROM [dbo].[NonUnicodeTest] AS [Extent1]
WHERE ([Extent1].[field] = @p__linq__0) AND ([Extent1].[field] = @p__linq__1)’,N’@p__linq__0 varchar(8000),@p__linq__1 varchar(8000)’,@p__linq__0=’field1′,@p__linq__1=’field2′
Fijese que ya hemos puesto en negrita las diferencias, en la sentencia OR Entity Framework no es capaz de reconocer el patrón de unicode/no unicode y aplicar el face correspondiente. Desde luego, esto es un bug en toda regla, seguramente porque la forma de analizar el CQT no es el mismo para la expresion OR que para la expresion AND.
Por suerte, disponemos de una forma de resolver este problema y es mediante una Model Defined Function. las mismas que vimos en un post anterior. De la misma forma que podemos crear nuevas funciones definidas en el modelo EF pone a nuestra disposición un conjunto de ellas out-of-box. Estas funciones las podemos encontrar dentro de las clases EntityFunctions y SqlFunctions, en concreto EntityFunctions nos provee de una función definida en el modelo con el nombre AsNonUnicode. Su utilización se muestra como se ve a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="kwrd">using</span> (EF4EXEntities context = <span class="kwrd">new</span> EF4EXEntities()) { <span class="kwrd"> string</span> field1 = <span class="str">"field1"</span>; <span class="kwrd">string</span> field2 = <span class="str">"field2"</span>; List<NonUnicodeTest> result = (from nut <span class="kwrd">in</span> context.NonUnicodeTests <span class="kwrd">where</span> nut.field == EntityFunctions.AsNonUnicode(field1) || nut.field == EntityFunctions.AsNonUnicode(field2) select nut).ToList(); Console.ReadLine(); } |
1 |
|
El resultado, despues del uso de EntityFunctions ya resulta el que esperamos:
exec sp_executesql N’SELECT
[Extent1].[idTest] AS [idTest],
[Extent1].[field] AS [field]
FROM [dbo].[NonUnicodeTest] AS [Extent1]
WHERE [Extent1].[field] IN (@p__linq__0,@p__linq__1)’,N’@p__linq__0 varchar(8000),@p__linq__1 varchar(8000)’,@p__linq__0=’field1′,@p__linq__1=’field2′
1 |
|
Saludos
Unai Zorrilla Castro
1 |
|
Hola Unai – gran post
Hace poco me encontre con este mismo problema en un entorno de produccion.
El problema se produccia tanto con Linq como con ObjectQuery. Al final lo solucione con ObjectQuery pero construyendo el ESQL a «pelo».
No se me hubiera ocurrido que tuviera que ver con los operadores AND y OR.
Saludos
Tremendo tip tio.. curiosisimo el bug, pero sobre todo la solución: he de confesar que no conocia la función AsNonUnicode.
Como siempre, genial! Un abrazote tio!
Hola, ya había escuchado alguna cosa al respecto.
Sabes si se plantea el mismo problema en Oracle?
Gracias.
En Oracle con que proveedor?? Esto va a depender de quien analize y genere la CQT correspondiente, por lo tanto dependerá del proveedor seleccionado….
En mi caso, se trata de DevArt.