Dědičnost tabulek je zajímavá vlastnost Dynamics AX 2012, díky které je možné modelovat hierarchie typů v databázi velmi podobně jako v objektovém modelování. Dynamics AX nejen že umožňuje definovat dědičnost v metadatech, ale také ji automaticky zohledňuje v dotazech do databáze, na formulářích a podobně.
Problém je, že ji řada vývojářů používá bez pochopení, jak vlastně funguje, a její nesprávné použití může mít podstatný dopad na kvalitu datového modelu a na výkon. (A také komplikuje implementaci formulářů a podobně, ale tomu se dnes nebudu věnovat.)
Začněme tímto triviálním dotazem:
OMInternalOrganization org;
select org;
Bude nás zajímat, jaký dotaz je ve skutečnosti poslán do databáze – to snadno zjistíme pomocí jiné funkcionality přidané v AX2012.
OMInternalOrganization org; select generateOnly org; info(org.getSQLStatement());
Nepřehlédněte klíčové slovo generateOnly
, bez nějž by metoda getSQLStatement() vrátila prázdný řetězec. Žádný výstup také nedostanete tehdy, když jsou data načtena z cache, protože se skutečně žádný dotaz do databáze neposílá.
Kdo čekal, že výsledkem bude něco jako SELECT * FROM OMInternalOrganization
, bude asi dost překvapen. Výsledný dotaz vypadá v AX2012 takto (seznam sloupců jsem v zájmu čitelnosti nahradil hvězdičkou) :
SELECT * FROM DIRPARTYTABLE T1 CROSS JOIN DIRORGANIZATIONBASE T2 CROSS JOIN OMINTERNALORGANIZATION T3 LEFT OUTER JOIN OMTEAM T4 ON (T3.RECID=T4.RECID) LEFT OUTER JOIN OMOPERATINGUNIT T5 ON (T3.RECID=T5.RECID) LEFT OUTER JOIN COMPANYINFO T6 ON (T3.RECID=T6.RECID) WHERE (T2.RECID=T1.RECID) AND (T3.RECID=T2.RECID)
V X++ vidíme dotaz na jedinou tabulku, ale do databáze jde dotaz na šest tabulek. Jak to?
Kdyby systém vrátil pouze pole z tabulky OMInternalOrganization, nebylo by to příliš užitečné (vlastně je tam jediné aktivní pole: OrganizationType). V první řadě chceme dostat také všechna zděděná pole, tudíž AX se musí dotázat také na pole všech předků, v tomto případě tabulek DirPartyTable a DirOrganizationBase. (Nenechte se zmást těmi CROSS JOINy, klauzule WHERE z nich efektivně dělá INNER JOINy.)
Záznam v tabulce OMInternalOrganization může ve skutečnosti reprezentovat jednoho z jejích potomků (OMInternalOrganization je navíc abstraktní, takže sama o sobě vůbec nemá smysl). Abychom dostali kompletní údaje ke všem záznamům, musíme nalinkovat i pole ze všech potomků, tedy OMTeam, OMOperatingUnit a CompanyInfo. K tomu je pochopitelně třeba použít OUTER JOIN.
Celý ten komplikovaný dotaz se zkrátka snaží získat všechna potřebná pole. Pokud ale požadujeme jen některá pole, AX může dotaz podstatně zjednodušit. Například:
OMInternalOrganization org; select OrganizationType from org;
… vygeneruje tento dotaz:
SELECT T1.INSTANCERELATIONTYPE,T1.RECID,T2.ORGANIZATIONTYPE,T2.RECID FROM DIRPARTYTABLE T1 CROSS JOIN OMINTERNALORGANIZATION T2 WHERE (T2.RECID=T1.RECID)
Abyste se vyhnuli spojování velkého počtu tabulek, neměli byste vytvářet příliš rozsáhlé hierarchie dědičnosti. Také byste se měli snažit omezit vracené sloupce, protože pak není třeba načítat všechny tabulky v daném podstromu.
Pokud chcete vědět více, zkuste třeba Developing with Table Inheritance.
AX2012 R2
Aby to nebylo tak jednoduché, pojďme se podívat, jak to funguje v AX 2012 R2 (CTP). Spustíme v zásadě identický dotaz (forceLiterals
nám pomůže dostat podrobnosti, které budeme potřebovat později):
OMInternalOrganization org; select generateOnly forceLiterals org;
A dostaneme tento T-SQL kód:
SELECT * FROM DIRPARTYTABLE T1 WHERE ((T1.PARTITION=5637144576) AND (T1.INSTANCERELATIONTYPE IN (2376,41,2377,5329) ))
Je zřejmé, že celý dotaz je mnohem jednodušší než v předchozí verzi, ale asi není příliš jasné, jak vlastně funguje.
Mělo by vás zarazit, že v dotazu nefiguruje jiná tabulka než DirPartyTable, dokonce ani OMInternalOrganization, na kterou jsme se dotazovali v X++. Pokud bych zde zobrazil všechna pole, mohli byste zjistit (po chvíli pátrání), že všechna pole definovaná v Dynamics AX v potomcích jsou v databázi přímo v tabulce DirPartyTable.
Ale pokud jsou všechna pole v DirPartyTable, k čemu pak jsou rozšiřující tabulky jako OMInternalOrganization nebo CompanyInfo? Odpověď je: V databázi potřeba nejsou a vůbec v ní neexistují. Jako samostatné objekty existují až v aplikační vrstvě.
Přestože jsou data všech typů z jedné hierarchie uložena v jediné tabulce, informace o struktuře v ní samozřejmě existuje. V T-SQL můžete vidět, jak AX získá podstrom hierarchie, v které se nachází dotazovaná tabulka:
WHERE ((T1.PARTITION=5637144576) AND (T1.INSTANCERELATIONTYPE IN (2376,41,2377,5329) ))
Pole Partition souvisí s jinou novinkou v AX2012 R2, ale na dědičnost nemá vliv, tudíž ho zde můžeme ignorovat. To podstatné je InstanceRelationType – dříve toto pole určovalo, z jakých dalších tabulek je třeba načíst data, nyní se podle něj zkrátka vyfiltrují požadované typy záznamů. Hodnoty v InstanceRelationType představují ID tabulek:
2376 | OMInternalOrganization |
41 | CompanyInfo |
2377 | OMOperatingUnit |
5329 | OMTeam |
Veškerá pole, která nejsou definovaná v rodičovské tabulce, mají smysl jen pro určité typy záznamů. Pokud v určitém podtypu nějaké pole neexistuje, jeho hodnota v databázi je jednoduše NULL.
Přestože dědičnost tabulek vypadá v AX stále stejně, implementace v databázi se zcela změnila. AX2012 R2 se obejde bez spojování mnoha tabulek, ale zas vytváří jednu potenciálně velikou tabulku. Způsob návrhu a optimalizace se tedy může v AX2012 a AX2012 lišit, ale základní pravidla platí obecně: chápat co všechno se děje na pozadí, vyhnout se příliš komplikovaným hierarchiím, vyhnout se dědičnosti v oblastech s vysokými požadavky na výkon a výslednou implementaci (včetně výkonu) otestovat.