Předpokládejme, že máme datovou strukturu obsahující tyto tři tabulky:
(Přitom jsou hodnoty pole smeNazev v tabulce Smena jedinečné a podobně jsou jedinečné dvojice hodnot sc_IDSmeny, sc_DenVTydnu v tabulce SmenaCasy.)
Z těchto tabulek budeme chtít načíst data najednou tímto jedním SQL dotazem:
SELECT smeIDSmeny, sc_IDSmenaCasy, scpIDSmenaCasyPrestavka, smeNazev, smeOznaceniSmeny, smeTypSmeny, smeNocniDruhyDen, smePoznamka, smePlatnostDo,
sc_DenVTydnu, sc_PracovniDobaOd, sc_PracovniDobaDo, sc_ZakladniPracDobaOd, sc_ZakladniPracDobaDo, sc_VolitelnaPracDobaOd, sc_VolitelnaPracDobaDo, sc_AutomatickePrestavky, sc_FondSmeny,
scpIntervalOd, scpIntervalDo, scpDelka
FROM Smena LEFT OUTER JOIN
SmenaCasy ON SmenaCasy.sc_IDSmeny = Smena.smeIDSmeny LEFT OUTER JOIN
SmenaCasyPrestavka ON SmenaCasyPrestavka.scpIDSmenaCasy = SmenaCasy.sc_IDSmenaCasy
ORDER BY smeNazev, sc_DenVTydnu, scpIntervalOd
Předpokládejme dále, že naše datová vrstva umí načtená data vrátit pouze v podobě “flat” sekvence objektů, jehož podoba odpovídá vracenému resultsetu:
internal class SmenaRow
{
public int IDSmeny { get; private set; }
public int? IDSmenaCasy { get; private set; }
public int? IDSmenaCasyPrestavka { get; private set; }
public string Nazev { get; private set; }
public string OznaceniSmeny { get; private set; }
public TypSmeny TypSmeny { get; private set; }
public bool NocniDruhyDen { get; private set; }
public string Poznamka { get; private set; }
public DateTime? PlatnostDo { get; private set; }
public int? DenVTydnu { get; private set; }
public DateTime? PracovniDobaOd { get; private set; }
public DateTime? PracovniDobaDo { get; private set; }
public DateTime? ZakladniPracDobaOd { get; private set; }
public DateTime? ZakladniPracDobaDo { get; private set; }
public DateTime? VolitelnaPracDobaOd { get; private set; }
public DateTime? VolitelnaPracDobaDo { get; private set; }
public bool? AutomatickePrestavky { get; private set; }
public int FondSmeny { get; private set; }
public DateTime? IntervalOd { get; private set; }
public DateTime? IntervalDo { get; private set; }
public int? Delka { get; private set; }
}
(Všimněte si, že vlastnosti odpovídající tabulkám SmenaCasy a SmenaCasyPrestavka jsou v objektu SmenaRow díky použití LEFT OUTER JOIN všechny “nullable”.)
Našim úkolem je přetransformovat sekvenci objektů SmenaRow do objektové reprezentace, která by odpovídala původním tabulkám:
internal class Smena
{
public int IDSmeny { get; private set; }
public string Nazev { get; private set; }
public string OznaceniSmeny { get; private set; }
public TypSmeny TypSmeny { get; private set; }
public bool NocniDruhyDen { get; private set; }
public string Poznamka { get; private set; }
public DateTime? PlatnostDo { get; private set; }
public IList<SmenaCasy> Casy { get; private set; }
}
internal class SmenaCasy
{
public int IDSmenaCasy { get; private set; }
public int DenVTydnu { get; private set; }
public DateTime PracovniDobaOd { get; private set; }
public DateTime PracovniDobaDo { get; private set; }
public DateTime? ZakladniPracDobaOd { get; private set; }
public DateTime? ZakladniPracDobaDo { get; private set; }
public DateTime? VolitelnaPracDobaOd { get; private set; }
public DateTime? VolitelnaPracDobaDo { get; private set; }
public bool AutomatickePrestavky { get; private set; }
public int FondSmeny { get; private set; }
public IList<SmenaCasyPrestavka> Prestavky { get; private set; }
}
internal class SmenaCasyPrestavka
{
public int IDSmenaCasyPrestavka { get; private set; }
public DateTime IntervalOd { get; private set; }
public DateTime IntervalDo { get; private set; }
public int Delka { get; private set; }
}
(Předpokládejme, že každá z těchto tříd má definovaný i konstruktor beroucí hodnoty všech vlastností v daném pořadí).
Pro řešení použijeme LINQ operátor group by (odpovídající metodě GroupBy) a to hned dvakrát. První vnější group by bude za tabuku SmenaCasy a druhý vnitřní group by za tabulku SmenaCasyPrestavka.
Dále protože datový zdroj vrací data seřazená, bude zde výhodné použít extension metodu WithAdjacentGrouping převzatou od Tomáše Petříčka. Její implementace je dostupná ve třídě zde.
Pro volání datového zdroje, který vrací sekvenci objektů SmenaRow, předpokládejme existující metodu DataSource.GetSmeny.
Kód, který bude provádět vlastní transformaci vstupní sekvence na výslednou kolekci objektů Smena pak bude následující:
var smeny = new List<Smena>();
foreach (var groupSmena in from row in DataSource.GetSmeny().WithAdjacentGrouping()
group row by row.IDSmeny)
{
var casy = new List<SmenaCasy>();
foreach (var groupCasy in from rowCasy in groupSmena
where rowCasy.IDSmenaCasy != null
group rowCasy by rowCasy.IDSmenaCasy.Value)
{
var prestavky = new List<SmenaCasyPrestavka>();
foreach (var rowPrestavka in from rowPrestavka in groupCasy
where rowPrestavka.IDSmenaCasyPrestavka != null
select rowPrestavka)
{
var prestavka = new SmenaCasyPrestavka(rowPrestavka.IDSmenaCasyPrestavka.Value,
rowPrestavka.IntervalOd.Value, rowPrestavka.IntervalDo.Value, rowPrestavka.Delka.Value);
prestavky.Add(prestavka);
}
var smenaCasy = new SmenaCasy(groupCasy.Key, groupCasy.First().DenVTydnu.Value,
groupCasy.First().PracovniDobaOd.Value, groupCasy.First().PracovniDobaDo.Value,
groupCasy.First().ZakladniPracDobaOd, groupCasy.First().ZakladniPracDobaDo, groupCasy.First().VolitelnaPracDobaOd, groupCasy.First().VolitelnaPracDobaDo,
groupCasy.First().AutomatickePrestavky.Value, groupCasy.First().FondSmeny.Value, prestavky);
casy.Add(smenaCasy);
}
var smena = new Smena(groupSmena.Key, groupSmena.First().Nazev, groupSmena.First().OznaceniSmeny, groupSmena.First().TypSmeny,
groupSmena.First().NocniDruhyDen, groupSmena.First().Poznamka, groupSmena.First().PlatnostDo, casy);
smeny.Add(smena);
}