2009-05-13 6 views
7

Tengo una consulta LINQ to SQL:LINQ a SQL Tome w/o Skip Causas Varias sentencias SQL

from at in Context.Transaction 
select new { 
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select new { 
      Amount = tb.Amount, 
      Description = tb.Desc 
     } 
} 

Esto se traduce en una instrucción SQL que se ejecuta. Todo es bueno.

Sin embargo, si intento devolver tipos conocidos de esta consulta, incluso si tienen la misma estructura que los tipos anónimos, obtengo una instrucción SQL ejecutada para el nivel superior y luego una declaración SQL adicional para cada "hijo" conjunto.

¿Hay alguna manera de obtener LINQ to SQL para emitir una declaración SQL y usar tipos conocidos?

EDITAR: debo tener otro problema. Cuando conecté una versión muy simplista (pero todavía jerárquica) de mi consulta en LINQPad y usé tipos conocidos recién creados con solo 2 o 3 miembros, obtuve una declaración SQL. Publicaré y actualizaré cuando sepa más.

EDIT 2: Esto parece deberse a un error en Take. Ver mi respuesta a continuación para más detalles.

Respuesta

11

Primero: algún razonamiento para el error Take.

Si acaba de tomar, el traductor de consultas simplemente utiliza la parte superior. Top10 no dará la respuesta correcta si la cardinalidad se rompe uniéndose a una colección de niños. Por lo tanto, el traductor de consultas no se une a la colección secundaria (en cambio, se vuelve a consultar para los hijos).

Si Skip y Take, entonces el traductor consulta entra en acción con cierta lógica RowNumber sobre las filas primarias ... estos rownumbers dejar que tome 10 padres, incluso si eso es realmente 50 registros debido a cada padre que tiene 5 hijos .

Si Salta (0) y toma, el traductor omite Omitir como no operado, es como si nunca hubiera dicho Omitir.

Esto va a ser un salto conceptual duro desde donde se encuentra (llamando a Skip y Take) a una "solución simple". Lo que tenemos que hacer es forzar la traducción para que ocurra en un punto donde el traductor no puede eliminar el Omitir (0) como una falta de operación. Necesitamos llamar a Skip y suministrar el número omitido en un momento posterior.

DataClasses1DataContext myDC = new DataClasses1DataContext(); 
    //setting up log so we can see what's going on 
myDC.Log = Console.Out; 

    //hierarchical query - not important 
var query = myDC.Options.Select(option => new{ 
    ID = option.ParentID, 
    Others = myDC.Options.Select(option2 => new{ 
    ID = option2.ParentID 
    }) 
}); 
    //request translation of the query! Important! 
var compQuery = System.Data.Linq.CompiledQuery 
    .Compile<DataClasses1DataContext, int, int, System.Collections.IEnumerable> 
    ((dc, skip, take) => query.Skip(skip).Take(take)); 

    //now run the query and specify that 0 rows are to be skipped. 
compQuery.Invoke(myDC, 0, 10); 

Esto produce la siguiente consulta:

SELECT [t1].[ParentID], [t2].[ParentID] AS [ParentID2], (
    SELECT COUNT(*) 
    FROM [dbo].[Option] AS [t3] 
    ) AS [value] 
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID]) AS [ROW_NUMBER], [t0].[ParentID] 
    FROM [dbo].[Option] AS [t0] 
    ) AS [t1] 
LEFT OUTER JOIN [dbo].[Option] AS [t2] ON 1=1 
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
ORDER BY [t1].[ROW_NUMBER], [t2].[ID] 
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [10] 
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1 

Y aquí es donde ganamos!

WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
+0

Esto funciona pero tuve que usar IEnumerable en lugar de solo IEnumerable para llamar a ToList. También es medio segundo más rápido en mi caso de prueba. Buena respuesta. – JohnOpincar

0

No he tenido la oportunidad de probar esto, pero dado que el tipo anónimo no es parte de LINQ más bien un constructo C# Me pregunto si usted podría utilizar:

from at in Context.Transaction 
select new KnownType(
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select KnownSubType(
       Amount = tb.Amount, 
       Description = tb.Desc 
     ) 
} 

Obviamente detalles tendría que haber una colección IEnumerable.

Podría tener millas de ancho en esto, pero al menos podría darle una nueva línea de pensamiento para seguir lo que no puede doler, así que disculpe mi divagación.

2

He determinado que este es el resultado de un error horrible. El tipo anónimo versus conocido resultó no ser la causa. La verdadera causa es Take.

El siguiente resultado en la declaración SQL 1:

query.Skip(1).Take(10).ToList(); 
query.ToList(); 

Sin embargo, la siguiente exposición de la declaración de una sql por fila principal problema.

query.Skip(0).Take(10).ToList(); 
query.Take(10).ToList(); 

¿Alguien puede pensar en alguna solución simple para esto?

EDITAR: La única solución que he encontrado es verificar si estoy en la primera página (IE Omitir (0)) y luego hacer dos llamadas, una con Take (1) y la otra con Skip (1) .Take (pageSize - 1) y addRange the lists together.

+0

no es el problema aquí simplemente que se invoca la ejecución de SQL con el primer 'ToList()' y luego la segunda ToList '()' invoca una consulta para cada elemento de los resultados de la primera que ahora están en ¿memoria? –

+0

No, ese no es el problema. – JohnOpincar