Pobieranie db joina do obiektów

Na początku uprzedzam, że bazodanowcem nie jestem, ale w codziennych zmaganiach z różnego rodzaju systemami prędzej czy później każdy z nas z jakąś bazą danych będzie się musiał połączyć. Załóżmy, że mamy trzy tabele:

db

Zespoły, instrumenty oraz instrumenty w zespole, które łączymy tabelą InstrumentWZespole. W wyniku zapytania za pomocą linq:

from z in Zespolies
join instr in InstrumentWZespoles on z.IdZespolu equals instr.ZespolId into tempzesp
from instr in tempzesp.DefaultIfEmpty()
join instrnazwa in Instrumenties on instr.InstrumentId equals instrnazwa.IdInstrumentu into tempinstrnazwa
from instrnazwa in tempinstrnazwa.DefaultIfEmpty()
select new {Id=z.IdZespolu,Nazwa=z.NazwaZesplu,InstrumentId=(int?)instr.InstrumentId,NazwaInstrumentu=instrnazwa.NazwaInstrumentu}

czy też jak kto woli, za pomocą sql:

SELECT [t0].[idZespolu] AS [Id], [t0].[NazwaZesplu] AS [Nazwa], [t1].[InstrumentId] AS [InstrumentId], [t2].[NazwaInstrumentu] AS [NazwaInstrumentu]
FROM [Zespoly] AS [t0]
LEFT OUTER JOIN [InstrumentWZespole] AS [t1] ON [t0].[idZespolu] = [t1].[ZespolId]
LEFT OUTER JOIN [Instrumenty] AS [t2] ON [t1].[InstrumentId] = [t2].[IdInstrumentu]

otrzymujemy następującą tabelę:

Id Nazwa InstrumentId NazwaInstrumentu
1 Solista 1 Mikrofon
2 Zespół rockowy 1 Mikrofon
2 Zespół rockowy 2 Gitara
2 Zespół rockowy 3 Klawisze
3 Jeszcze nie zespół NULL NULL

Tak na marginesie, to polecam narzędzie LINQPad, dzięki któremu możemy tworzyć sobie zapytania i potem podglądać jego wyniki w tabelce – przydaje się szczególnie, gdy nasz projekt jest dużą kobyłą, obiekty na WCF’ie dość skomplikowanego i nie chce nam się tworzyć testów jednostkowych, żeby sprawdzić co tak naprawdę wychodzi z tego zapytania linq’owego. Niestety w darmowej wersji linqpad nie ma podpowiadania, co jest kłopotliwe zwłaszcza, gdy przyzwyczailiśmy się do wygód Visual Studio.

Teraz załóżmy, że chcemy za pomocą WCF wysłać listę zawierającą następujące klasy:

[DataContract]
public class Zespoly
{
[DataMember]
public int IdZEspolu { get; set; }
 
[DataMember]
public string NazwaZespolu { get; set; }
 
[DataMember]
public List<int> LstIdInstrumentow { get; set; }
}

Pokusa, aby to zrobić w sposób prosty jest duża – po wygenerowaniu obiektów linq2sql możemy spróbować zrobić to w następujący sposób:

using (testDataContext tdc = new testDataContext())
{
 
try
{
tdc.Log = Console.Out;
List<ZespolyWCF> lstZespoly = new List<ZespolyWCF>();
 
List<Zespoly> lstZespDb = tdc.Zespolies.ToList();
foreach (Zespoly zDb in lstZespDb)
{
ZespolyWCF z = new ZespolyWCF();
z.IdZEspolu = zDb.idZespolu;
z.NazwaZespolu = zDb.NazwaZesplu;
if (tdc.InstrumentWZespoles.Where(q => q.ZespolId ==   z.IdZEspolu).FirstOrDefault() != null)
{
z.LstIdInstrumentow = new List<int>();
tdc.InstrumentWZespoles.Where(q => q.ZespolId == z.IdZEspolu).ToList().ForEach(r => z.LstIdInstrumentow.Add(r.InstrumentId));
 
}
}
 
Console.ReadKey();
 
}
catch (Exception ex)
{
 
}

Niestety, jak popatrzymy sobie na konsolę, która w tej chwili pokazuje zapytania sql, to okazuje się, że wykonujemy tą jedną wygodną konstrukcją kilka zapytań. Takie rozwiązanie może i jest akceptowalne dla kilkunastu rekordów, ale przy powyżej tysiącu na pewno baza zacznie zwalniać – a kiedy dołożymy jeszcze kilku jednoczesnych użytkowników, bardziej będziemy bazę przymulać. W związku z tym należy starać się wykonywać zapytania pojedynczo i zwracać jak najwięcej rezultatów w pojedynczej tabeli, tak jak to było pokazane na początku, za pomocą joinów. Ale teraz stajemy przed dylematem – jak to zrobić wygodnie – w klasie, którą zwracamy znajduje się lista – w kolejnych wierszach tabelki są duplikaty i id kolejnych instumentów – przetwarzając iteracyjnie taką odpowiedź nie wiemy, czy kolejny wiersz zawiera jeszcze ten sam zespól, czy już kolejny? W związku z powyższym możemy skorzystać z precedury wbudowanej, która nam zwróci XML’a wykonująć zapytanie, należy zakończyć je klauzulą for xml auto i wyniku tego otrzymamy następujący xml:

<t0 Id="1" Nazwa="Solista">
<t1 InstrumentId="1">
<t2 NazwaInstrumentu="Mikrofon" />
</t1>
</t0>
<t0 Id="2" Nazwa="Zespół rockowy">
<t1 InstrumentId="1">
<t2 NazwaInstrumentu="Mikrofon" />
</t1>
<t1 InstrumentId="2">
<t2 NazwaInstrumentu="Gitara" />
</t1>
<t1 InstrumentId="3">
<t2 NazwaInstrumentu="Klawisze" />
</t1>
</t0>
<t0 Id="3" Nazwa="Jeszcze nie zespół">
<t1>
<t2 />
</t1>
</t0>

Teraz każdy .netowiec wie, że jak ma xml’a, to poradzi sobie już łatwo – w tym xml’u wiemy gdzie “kończy się” dany zespół i wystarczy patrzeć czy w potomnych gałęziach znajdują się atrybuty i dopisywać je do listy przetwarzając iteracyjnie xml’a sprawdzając czy są atrybuty, czy nie ma. A może ktoś ma lepszy sposób na takie przypadki?