2012-06-20 11 views
5

Tengo el siguiente código, que está portando mal:Cómo fijar super lento consulta EF/LINQ ejecutar varias instrucciones SQL

TPM_USER user = UserManager.GetUser(context, UserId); 
var tasks = (from t in user.TPM_TASK 
      where t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
      orderby t.DUEDATE, t.PROJECTID 
      select t); 

La primera línea, UserManager.GetUser simplemente realiza una búsqueda simple en la base de datos para obtener la correcta TPM_USER registro. Sin embargo, la segunda línea causa todo tipo de caos de SQL.

En primer lugar, está ejecutando dos instrucciones SQL aquí. El primero de ellos agarra cada hilera en TPM_TASK que está vinculado a ese usuario, lo cual es a veces decenas de miles de filas:

SELECT 
-- Columns 
FROM TPMDBO.TPM_USERTASKS "Extent1" 
INNER JOIN TPMDBO.TPM_TASK "Extent2" ON "Extent1".TASKID = "Extent2".TASKID 
WHERE "Extent1".USERID = :EntityKeyValue1 

Esta consulta tarda unos 18 segundos en los usuarios con un montón de tareas. Esperaría que la cláusula WHERE también contenga los filtros STAGEID, lo que eliminaría la mayoría de las filas.

A continuación, parece para ejecutar una consulta nueva para cada TPM_PROJECTVERSION par en la lista anterior:

SELECT 
-- Columns 
FROM TPMDBO.TPM_PROJECTVERSION "Extent1" 
WHERE ("Extent1".PROJECTID = :EntityKeyValue1) AND ("Extent1".VERSIONID = :EntityKeyValue2) 

A pesar de que esta consulta es rápido, es ejecutado varios cientos de veces si el usuario tiene tareas en un montón de proyectos.

La consulta me gustaría generar sería algo como:

SELECT 
-- Columns 
FROM TPMDBO.TPM_USERTASKS "Extent1" 
INNER JOIN TPMDBO.TPM_TASK "Extent2" ON "Extent1".TASKID = "Extent2".TASKID 
INNER JOIN TPMDBO.TPM_PROJECTVERSION "Extent3" ON "Extent2".PROJECTID = "Extent3".PROJECTID AND "Extent2".VERSIONID = "Extent3".VERSIONID 
WHERE "Extent1".USERID = 5 and "Extent2".STAGEID > 0 and "Extent2".STAGEID <> 3 and "Extent3".STAGEID <= 10 

La consulta anterior se ejecutaría en aproximadamente 1 segundo. Normalmente, podría especificar que JOIN usando el método Include. Sin embargo, esto no parece funcionar en las propiedades. En otras palabras, no puedo hacer:

from t in user.TPM_TASK.Include("TPM_PROJECTVERSION") 

¿Hay alguna manera de optimizar esta declaración de LINQ? Estoy usando .NET4 y Oracle como el back-end DB.

Solución:

Esta solución se basa en sugerencias de Kirk abajo, y trabaja desde context.TPM_USERTASK no se puede consultar directamente:

var tasks = (from t in context.TPM_TASK.Include("TPM_PROJECTVERSION") 
      where t.TPM_USER.Any(y => y.USERID == UserId) && 
      t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
      orderby t.DUEDATE, t.PROJECTID 
      select t); 

Se hace resultado en un anidado SELECT en lugar de consultar TPM_USERTASK directamente, pero parece bastante eficiente sin embargo.

Respuesta

4

Sí, está retirando a un usuario específico y haciendo referencia a la relación TPM_TASK. Que está reduciendo cada tarea asociada a ese usuario es exactamente lo que se supone que debe hacer. No hay traducción ORM SQL cuando lo haces de esta manera. Obtiene un usuario, luego todas sus tareas en la memoria y luego realiza un filtrado del lado del cliente. Todo esto se hace mediante la carga diferida, por lo que SQL será excepcionalmente ineficiente ya que no puede procesar nada por lotes.

su lugar, vuelva a escribir la consulta para ir directamente contra TPM_TASK y el filtro contra el usuario:

var tasks = (from t in context.TPM_TASK 
     where t.USERID == user.UserId && t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
     orderby t.DUEDATE, t.PROJECTID 
     select t); 

Nota cómo estamos comprobando t.USERID == user.UserId.Esto produce el mismo efecto que user.TPM_TASK pero ahora todo el trabajo pesado lo realiza la base de datos en lugar de la memoria.

+0

Lamentablemente, esta idea no funcionará. 'TPM_TASK' no tiene la propiedad' USERID'. Los usuarios se relacionan con las tareas a través de la tabla 'TPM_USERTASK', que no puede consultar directamente ya que se usa en una relación muchos a muchos. ¿Tal vez sería mejor crear una vista en la base de datos o algo así? –

+0

Sin duda, puede escribir la consulta para ir contra 'TPM_USERTASK' también. No estoy seguro de cómo se configura exactamente su esquema, pero algo en la línea de 'donde t.TPM_TASK.Any (y => y.USERID == t.USER)' o simplemente use 'join' para traer el muchos a muchos. Definitivamente no debes jugar con las vistas para lograr esto. –

+0

De acuerdo, jugando con esa idea ... Te lo haré saber en unos minutos ... –

Cuestiones relacionadas