2012-02-17 6 views
9

Deseo obtener registros de la base de datos utilizando EF y asignar los valores a una clase de DTO. Considere las siguientes tablas para una consulta de Linq.Asignación de resultados de consulta de Linq a una clase de DTO

TableA, TableB, TableC

Para cada registro TableA hay varios registros en la Tabla B. Para cada registro TableB hay múltiples registros en TableC. Ahora mis dtos ven así

public class TableA_DTO 
{ 
    public int tableA_rowid { get; set; } 
    //remaining tableA field definitions 

    public List<TableB_DTO> TableB_records { get; set; } 
} 

public class TableB_DTO 
{ 
    public int tableB_rowid { get; set; } 
    //remaining tableB field definitions 

    public List<TableC_DTO> TableC_records { get; set; } 
} 

public class TableC_DTO 
{ 
    public int tableC_rowid { get; set; } 
    //remaining tableC field definitions 
} 

mi consulta

LINQ se ve algo como esto

var qry = from ent in TableA 
      select ent; 

En mi asignación bucle de clase I por los elementos de resultado de la consulta de este modo:

foreach (var dataitem in query) 
    { 
     TableA_DTO dto = new TableA_DTO(); 
     dto.tableA_rowid = dataitem.ID; 
     //remaining field definitions here 
    } 

Ahora esto funciona para todos los campos en la Tabla A donde saca un registro de la base de datos y establece las propiedades requeridas en TableA_DTO para cada campo en la tabla TableA. También quiero rellenar todos los registros coincidentes en TableB en el campo de propiedad TableA con el nombre TableB_records y también en TableB_DTO todos los registros coincidentes desde TableC en la propiedad TableB_DTO con el nombre TableC_records

¿Se puede hacer esto? ¿Qué necesito cambiar? ¿Es la consulta LINQ o la forma en que hago mi asignación

Gracias por su tiempo ...

+3

¿Hay alguna razón por la que no pueda usar Entity Framework POCO's (también conocido como DbContext, a veces denominado erróneamente Code First)? Básicamente, ¿puedes eliminar la necesidad de DTO y usar EF POCO en su lugar? – JMarsch

+1

¿Ha considerado usar AutoMapper? Dependiendo de cuán diferentes sean sus DTO, esto podría ser tan simple como dos o tres líneas de código para hacer el mapeo. – Robaticus

+0

@jMarsch: La base de datos ya está allí, así que fue por el camino de Edmx – user20358

Respuesta

6

Yo cambiaría su DTO de List a IEnumerable y que hago todo en una consulta LINQ.

var query = 
    from ent in TableA 
    select new TableA_DTO 
    { 
     TableAProperty = a.Property, 
     TableB_records = 
      from b in TableB 
      where ent.Key == b.Key 
      select new TableB_DTO 
      { 
       TableBProperty = b.Property, 
       TableC_records = 
        from c in TableC 
        where b.Key == c.Key 
        select new TableC_DTO 
        { 
         TableCProperty = c.Property 
        } 
      } 
    }; 
+0

Sin embargo, el problema con esto es que esto dispara incluso mucho más que las consultas 'N + 1'; desencadena consultas 'M * (N + 1) + 1' con casi seguramente dará lugar a un rendimiento muy pobre. – Steven

+3

@Steven - Incorrecto. Solo envía una consulta. – Aducci

+0

Sí, lo hace, hasta que empiece a iterar sobre las propiedades 'TableB_records' y' TableV_records'. Mire de cerca esa única consulta que se ejecuta con el generador de perfiles de SQL. Notará que le falta toda la información sobre 'TableB' y' TableC'. – Steven

0

Me gustaría hacer un método de fábrica, es decir: TableA_DTO CreateDTO(TableAItem item);

usando esto, se podía simplemente volver a escribir la consulta como:

IEnumerable<TableA_DTO> = TableA.AsEnumerable().Select(CreateDTO); 

Esto le daría la colección de objetos "DTO" directamente.

Dicho esto, si está utilizando Entity Framework, el EF Code First agregado en versiones recientes podría ser más útil en este caso.

+0

¿qué es CreateDTO? es una clase? ¿cuál sería su definición? – user20358

+0

@ user20358 Sería un método que escribiría, que hace las asignaciones. Aún deberías hacer esas asignaciones, pero se limitaría a un método (que se convierta desde la entidad -> DTO) –

+0

@Reed: Llamar a 'AsEnumerable()' en un 'IQueryable ' asegurará que tiras hacia abajo todas las filas de la base de datos. A menos que la tabla tenga menos de mil filas (y todos sus datos), o a menos que desee obtener todos los registros de todos modos, esto será muy malo para el rendimiento. – Steven

4

En primer lugar, solo tengo que preguntar si puede usar Entity Framework 4.1 y POCO (DbContext) y evitar la necesidad de DTO's altoghther?

Suponiendo que la respuesta es no, debe ser porque no está retirando todos los campos, o está alterando la "forma" de los datos.

En ese caso, se podría cambiar la consulta LINQ a ser algo como esto:

from t in table 
where ... 
select new DTOA() 
{ 
    TheDtoProperty = theTableProperty, 
    AndSoOn = AndSoOn 
}; 

La ventaja de hacerlo de esta manera: Si activa el Analizador de SQL, se debe ver que sólo las columnas que usted solicita ingresar a la consulta SQL real. Si lo consulta todo primero y luego extrae los valores, todas las columnas se bajarán por el cable.

0

ACTUALIZACIÓN

Como otros señalaron, aplanando los resultados (como se muestra más abajo) no es necesaria cuando se trabaja con Entity Framework 4.0, ya que puede traducir la consulta LINQ a un resultado eficiente aplanado para tú. Por lo tanto, el siguiente código solo es necesario cuando se trabaja con LINQ to SQL (o posiblemente otros proveedores LINQ). Tenga en cuenta que solo he probado esto con EF sobre SQL Server y no sobre Oracle, ya que este comportamiento podría ser específico de LINQ provider, lo que significa que el proveedor de Oracle (aún en beta) o el proveedor comercial de Devart para Oracle aún podría estar haciendo N + 1.


Lo que estamos tratando de hacer es conseguir un conjunto de objetos que se estructuran como un árbol. Sin ningún cuidado especial, provocará muchas consultas en la base de datos. Con un nivel de anidación estarías desencadenando consultas N + 1, pero como su anidación tiene dos niveles de profundidad, activará M x (N + 1) + 1 consultas, lo que seguramente será muy malo para rendimiento (sin importar el tamaño de su conjunto de datos). Lo que desea es asegurarse de que solo se envíe una única consulta a la base de datos. Para garantizar esto, debe crear una consulta intermedia que aplana el resultado, tal como lo haría en los buenos viejos días de SQL, para recuperar datos tipo árbol :-). Tome una mirada en el siguiente ejemplo:

var records = 
    from record in db.TableC 
    where ... // any filtering can be done here 
    select record; 

// important to call ToArray. This ensures that the flatterned result 
// is pulled in one single SQL query. 
var results = (
    from c in records 
    select new 
    { 
     tableA_rowid = c.B.A.Id, 
     tableA_Prop1 = c.B.A.Property1, 
     tableA_Prop2 = c.B.A.Property2, 
     tableA_PropN = c.B.A.PropertyN, 
     tableB_rowid = c.B.Id, 
     tableB_Property1 = c.B.Property1, 
     tableB_Property2 = c.B.Property2, 
     tableB_PropertyN = c.B.PropertyN, 
     tableC_rowid = c.Id, 
     tableC_Property1 = c.Property1, 
     tableC_Property2 = c.Property2, 
     tableC_PropertyN = c.PropertyN, 
    }) 
    .ToArray(); 

El siguiente paso es transformar esa estructura de datos en memoria (usando ese tipo anónimo) en la estructura de árbol de DTO objetos:

// translate the results to DTO tree structure 
TableA_DTO[] dtos = (
    from aresult in results 
    group aresult by aresult.tableA_rowid into group_a 
    let a = group_a.First() 
    select new TableA_DTO 
    { 
     tableA_rowid = a.tableA_rowid, 
     tableA_Prop1 = a.tableA_Prop1, 
     tableA_Prop2 = a.tableA_Prop2, 
     TableB_records = (
      from bresult in group_a 
      group bresult by bresult.tableB_rowid into group_b 
      let b = group_b.First() 
      select new TableB_DTO 
      { 
       tableB_rowid = b.tableB_rowid, 
       tableB_Prop1 = b.tableB_Prop1, 
       tableB_Prop2 = b.tableB_Prop2, 
       TableC_records = (
        from c in group_b 
        select new TableC_DTO 
        { 
         tableC_rowid = c.tableC_rowid, 
         tableC_Prop1 = c.tableC_Prop1, 
         tableC_Prop2 = c.tableC_Prop2, 
        }).ToList(), 
      }).ToList() 
    }) 
    .ToArray(); 

Como Como puede ver, la primera parte de la solución es en realidad la "vieja" forma de hacer esto, mucho antes, cuando todavía escribiríamos nuestras consultas SQL a mano. Sin embargo, es agradable que una vez que obtengamos este conjunto de datos en memoria interna, podamos aprovechar LINQ (para Objetos) nuevamente para obtener estos datos en la estructura que queremos.

Tenga en cuenta que esto también le permite realizar paginación y clasificación. Esto será un poco más complicado, pero ciertamente no imposible.

+1

El paso de "aplanamiento" completo es totalmente innecesario, ya que Entity Framework hace esto por usted. El uso de la estrategia de @ Aducci hará que una consulta de base de datos única devuelva resultados en filas planas de SQL, y luego compondrá automáticamente estos valores en una estructura jerárquica. – StriplingWarrior

+0

@StriplingWarrior: después de hacer algunas pruebas, parece que tienes toda la razón al respecto. Entity Framework me deslumbró aquí :-) Finalmente algo sobresale en LINQ to SQL, ya que LINQ to SQL no hace consultas N + 1. Esto de hecho es genial. – Steven

+0

Sí, parece que LINQ to SQL maneja el patrón anidado hasta un nivel de profundidad (TableA y TableB), pero más profundo que eso y termina con un viaje de ida y vuelta separado para cada elemento en TableC. – StriplingWarrior

Cuestiones relacionadas