2009-06-04 13 views
5

Estoy en el proceso de acelerar las consultas de SQL en una aplicación web mediante la conversión a consultas compiladas y me encontré con un problema al convertir una declaración muy simple.Consulta compilada de Linq usando Contiene (como la instrucción IN de SQL)

quiero compilar una declaración simple que utiliza los siguientes parámetros para obtener empleados válidos a partir de la base de datos:

TestParams myParams = new TestParams 
{ 
    ValidEmps = new int[] { 1, 2, 3 } 
}; 

Ésta es la consulta de trabajo:

IQueryable<Employees> MySelectedEmps = 
    from emps in db.Employees 
    where myParams.ValidEmps.Contains(emps.EmployeeID) 
    select emps; 

Aquí está mi intento de compilar esto:

private static Func<MyDataContext, TestParams, IQueryable<Employee>> myCompiledQuery = 
    CompiledQuery.Compile((MyDataContext db, TestParams myParams) => 
     from emps in db.Employees 
     where myParams.ValidEmps.Contains(emps.EmployeeID) 
     select emps); 

Esta declaración con compilación y compilación, pero cuando lo ejecuto, Recibo el siguiente error de tiempo de ejecución:

Comparison operators not supported for type 'System.Int32[]'

También he intentado hacer pasar una lista y un IEnumerable con el mismo mensaje de error.

También he intentado reemplazar la instrucción .Contains con una declaración .Any (valemp => valemp == emps.EmployeeID) pero sigo recibiendo el mismo error.

¿Es posible tener una consulta compilada que use el equivalente de la instrucción SQL "IN"? ¿Qué estoy haciendo mal?

+1

http://stackoverflow.com/questions/813256/compiled-queries-and-parameters-cannot-be-sequences –

+0

Gracias por el enlace. Es una noticia decepcionante, no creo que nadie tenga una solución factible. –

+0

intente con un procedimiento almacenado como sugiero a continuación ... –

Respuesta

5

llamar a un procedimiento almacenado, y pasar de una lista de CVS de los valores, usando este método:

Antes de utilizar mi función, es necesario establecer una mesa "ayudante", sólo tiene que hacer esto tiempo por base de datos:

CREATE TABLE Numbers 
(Number int NOT NULL, 
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
DECLARE @x int 
SET @x=0 
WHILE @x<8000 
BEGIN 
    SET @[email protected]+1 
    INSERT INTO Numbers VALUES (@x) 
END 

uso de esta función para dividir la cadena, lo que no hace bucle y es muy rápido:

CREATE FUNCTION [dbo].[FN_ListToTable] 
(
    @SplitOn    char(1)    --REQUIRED, the character to split the @List string on 
    ,@List     varchar(8000)  --REQUIRED, the list to split apart 
) 
RETURNS 
@ParsedList table 
(
    ListValue varchar(500) 
) 
AS 
BEGIN 

/** 
Takes the given @List string and splits it apart based on the given @SplitOn character. 
A table is returned, one row per split item, with a column name "ListValue". 
This function workes for fixed or variable lenght items. 
Empty and null items will not be included in the results set. 


Returns a table, one row per item in the list, with a column name "ListValue" 

EXAMPLE: 
---------- 
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B') 

    returns: 
     ListValue 
     ----------- 
     1 
     12 
     123 
     1234 
     54321 
     6 
     A 
     * 
     ||| 
     B 

     (10 row(s) affected) 

**/ 



---------------- 
--SINGLE QUERY-- --this will not return empty rows 
---------------- 
INSERT INTO @ParsedList 
     (ListValue) 
    SELECT 
     ListValue 
     FROM (SELECT 
        LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue 
        FROM (
          SELECT @SplitOn + @List + @SplitOn AS List2 
         ) AS dt 
         INNER JOIN Numbers n ON n.Number < LEN(dt.List2) 
        WHERE SUBSTRING(List2, number, 1) = @SplitOn 
      ) dt2 
     WHERE ListValue IS NOT NULL AND ListValue!='' 



RETURN 

END --Function FN_ListToTable 

puede utilizar esta función como una tabla en una combinación:

SELECT 
    Col1, COl2, Col3... 
    FROM YourTable 
     INNER JOIN FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue 

así que para Linq, crear un procedimiento almacenado:

CREATE PROCEDURE YourProcedure (

@param1 int 
,@param2 varchar(8000) --csv list is here 

) como

SELECT 
    Col1, COl2, Col3... 
    FROM YourTable 
     INNER JOIN FN_ListToTable(',',@param2 ) s ON YourTable.ID = s.ListValue 
    WHERE [email protected] 

ir

+0

¡Vamos! abajo vote si tiene una mejor solución o puede decirme qué está mal con esto. ¡Sí, solo construye una cadena sql cargada de inyección y vete a casa! –

+1

No sé por qué fueron rechazados, pero aprecio su pensamiento. Todavía estoy buscando una solución específica de LINQ, pero esta es una solución válida. –

+0

Parece que la respuesta es "esto es imposible con el enlace", por lo que esta solución basada en SP está lo más cerca posible. Gracias por la solución KM. –

0

no funciona la

CompiledQuery.Compile 

tiene por qué ser

CompiledQuery.Compile<MyDataContext, TestParams, IQueryable<Employee>> 

en su lugar?

¿Y ha intentado usar una extensión para Int32 (supongo que EmployeeID es un Int32) con una función In?

public static class Int32Extensions 
{ 
    public static bool In(this int input, List<int> list) 
    { 
     if (list == null || list.Count == 0) return false; 
     return list.Contains(input); 
    } 
} 

private static Func<MyDataContext, TestParams, IQueryable<Employee>> myCompiledQuery = 
    CompiledQuery.Compile<MyDataContext, TestParams, IQueryable<Employee>>((MyDataContext db, TestParams myParams) => 
    from emps in db.Employees 
    where emps.EmployeeID.In(myParams.ValidEmps) 
    select emps); 

grtz,

+1

Los tipos genéricos y las funciones no necesitan siempre especificar sus argumentos, si sus argumentos se pueden deducir del uso. p.ej. IQueryable Func (IQueryable source, cosa de cadena) se puede llamar como Func (list, "thing") an return y aún devuelve el mismo IQueryable , siendo T cualquier lista T. El compilador hace el trabajo en ese caso, el mismo debate que usar var sobre si es más o menos legible o intuitivo. –

0

me encontré con este mismo problema de una vez, y estoy bastante seguro (Voy a tener a la investigación) que yo era capaz de resolver el problema mediante la creación de una clase con un método en él que devuelve un booleano que luego pasar el elemento de la fila de datos y devolver verdadero/falso.

Cuestiones relacionadas