2012-02-17 11 views
9

Chcę uzyskać rekordy z bazy danych za pomocą EF i przypisać wartości do klasy DTO. Rozważ poniższe tabele dla zapytania Linq.Mapowanie wyników Linq Query do klasy DTO

TABLEA, TableB, TableC

Dla każdego rekordu TABLEA istnieje wiele rekordów w TableB. Dla każdego rekordu TableB istnieje wiele rekordów w TableC. Teraz moje DTOs wyglądać następująco

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 
} 

mojej kwerendy LINQ wyglądać tak

var qry = from ent in TableA 
      select ent; 

W moim mapowania klasy I pętli poprzez elementów w wyniku kwerendy tak:

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

teraz działa to dla wszystkich pól w Tabeli A, gdzie wyprowadza jeden rekord z bazy danych i ustawia wymagane właściwości w TableA_DTO dla każdego pola w tabeli TableA. Chcę również wypełnić wszystkie pasujące rekordy w tabeli B w polu właściwości TableA przez nazwę TableB_records, a także w TableB_DTO wszystkie pasujące rekordy od TableC w TableB_DTO właściwości o nazwie TableC_records

Czy można to zrobić? Co muszę zmienić? Czy jest kwerenda LINQ lub sposób dołożę mapowanie

Dziękuję za poświęcony czas ...

+3

Czy jest jakikolwiek powód, dla którego nie można używać Entity Framework POCO (aka DbContext, czasami błędnie nazywanego Code First)? Zasadniczo, czy możesz wyeliminować potrzebę DTO i użyć EF POCO? – JMarsch

+1

Czy rozważałeś użycie AutoMappera? W zależności od tego, jak różne są twoje DTO, może to być tak proste jak dwa lub trzy linie kodu do wykonania mapowania. – Robaticus

+0

@jMarsch: Baza danych już tam jest, więc poszła droga edmx – user20358

Odpowiedz

6

Zmieniłbym twoje DTO od List na IEnumerable i zrobię wszystko w kwerendzie 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

Problem polega jednak na tym, że wyzwala to znacznie więcej niż zapytania "N + 1"; uruchamia zapytania 'M * (N + 1) + 1', które prawie na pewno doprowadzą do bardzo niskiej wydajności. – Steven

+3

@Steven - Niepoprawnie. Wysyła tylko jedno zapytanie. – Aducci

+0

Tak, dopóki nie zaczniesz iterować po właściwościach 'TableB_records' i' TableV_records'. Przyjrzyj się dokładnie temu pojedynczemu zapytaniu, które jest wykonywane przy użyciu profilera SQL. Zauważysz, że brakuje w nim wszystkich informacji o 'TableB' i' TableC'. – Steven

0

chciałbym złożyć metody fabryki, czyli: TableA_DTO CreateDTO(TableAItem item);

Używanie tego można po prostu przepisać zapytanie jako:

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

To da ci kolekcję obiektów "DTO" bezpośrednio.

Mimo to, jeśli korzystasz z Entity Framework, EF Code First dodany w najnowszych wersjach może być bardziej przydatny w tym przypadku.

+0

co to jest CreateDTO? czy to jest klasa? jaka byłaby jego definicja? – user20358

+0

@ user20358 Byłaby to metoda, którą piszesz, która wykonuje zadania. Wciąż będziesz musiał wykonywać te zadania, ale będzie to ograniczone do jednej metody (która konwertuje z encji -> DTO). –

+0

@Reed: Wywołanie 'AsEnumerable()' na 'IQueryable ' zapewni, że ściągniesz wszystkie wiersze z bazy danych. O ile tabela nie zawiera mniej niż tysiąca wierszy (i wszystkich ich danych), lub jeśli mimo wszystko chcesz zdobyć wszystkie rekordy, będzie to bardzo niekorzystne dla wydajności. – Steven

4

Pierwsza rzecz, po prostu muszę zapytać, czy można użyć Entity Framework 4.1 i POCO (DbContext) i uniknąć potrzeby zmiany DTO?

Zakładając, że odpowiedź brzmi "nie", musi to być spowodowane tym, że nie wycofujesz wszystkich pól lub w jakiś sposób zmieniasz "kształt" danych.

W takim przypadku, można zmienić zapytanie LINQ to wyglądać mniej więcej tak:

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

Zaletą robi to w ten sposób: Jeśli włączysz SQL Profiler, należy zauważyć, że tylko te kolumny, które Żądasz, aby wprowadzić go do rzeczywistego zapytania SQL. Jeśli najpierw zapytasz o to wszystko, a następnie pobierzesz wartości, wszystkie kolumny zostaną wyciągnięte z drutu.

0

UPDATE

Jak inni wskazał, spłaszczenie wyników (jak pokazano poniżej) nie jest potrzebna podczas pracy z Entity Framework 4.0, ponieważ może to tłumaczyć kwerendy LINQ do efektywnego spłaszczonej wyniku za ty. Dlatego poniższy kod jest potrzebny tylko podczas pracy z LINQ do SQL (lub ewentualnie innych dostawców LINQ). Zauważ, że testowałem to tylko z EF przez SQL Server, a nie przez Oracle, ponieważ to zachowanie może być specyficzne dla LINQ, co oznacza, że ​​dostawca Oracle (nadal w wersji beta) lub komercyjny dostawca Devart dla Oracle nadal może robić N + 1.


Co próbujesz zrobić, to zbiór obiektów, które są tak skonstruowane, jak drzewo. Bez specjalnej troski uruchomisz wiele zapytań do bazy danych. Przy jednym poziomie zagnieżdżania wyzwalane są zapytania o wartości N + 1, ale ponieważ twoje zagnieżdżenie ma dwa poziomy głębokości, będziesz wyzwalać zapytania M x (N + 1) + 1, które prawie na pewno będą bardzo złe dla wydajność (bez względu na rozmiar zestawu danych). To, czego chcesz, to upewnić się, że istnieje tylko jedno zapytanie wysłane do bazy danych. Aby to zapewnić, należy utworzyć zapytanie pośrednie, które spłaszcza wynik, tak jak w starych dobrych dniach SQL, w celu pobrania takich danych jak drzewa :-). Spójrz na poniższy przykład:

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(); 

Następnym krokiem jest przekształcenie tej struktury danych w pamięci (za pomocą anonimowej typ) w strukturę drzewa DTO obiektów:

// 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(); 

Jak widać, pierwsza część rozwiązania jest w rzeczywistości "starym" sposobem na zrobienie tego, wracając, gdy nadal będziemy pisać zapytania SQL ręcznie. Miło jest jednak, że gdy otrzymamy ten typowy zestaw danych w pamięci, możemy ponownie wykorzystać LINQ (do obiektów), aby uzyskać te dane w pożądanej przez nas strukturze.

Należy pamiętać, że pozwala to również na stronicowanie i sortowanie. To będzie trochę trudniejsze, ale na pewno nie niemożliwe.

+1

Cały "spłaszczający" krok jest zupełnie niepotrzebny, ponieważ Entity Framework robi to za Ciebie. Użycie strategii @ Aducci spowoduje, że pojedyncze zapytanie bazy danych zwróci wyniki w spłaszczonych wierszach z SQL, a następnie automagicznie skomponuje te wartości w hierarchiczną strukturę. – StriplingWarrior

+0

@StriplingWarrior: Po przeprowadzeniu testów wydaje się, że masz absolutną rację. Entity Framework mnie tu olśniło :-) Wreszcie coś, co wyróżnia LINQ do SQL, ponieważ LINQ do SQL robi zapytania N + 1. To naprawdę jest fajne. – Steven

+0

Tak, wygląda na to, że LINQ do SQL obsługuje wzorzec zagnieżdżony do głębokości jednego poziomu (TableA i TableB), ale głębiej niż to, a kończy się oddzielną wycieczką dla każdego elementu w TableC. – StriplingWarrior