Skip to content

Konzolový výstup z Dynamics AX

Přestože Dynamics AX nabízí celou řadu možností, jak volat aplikační logiku (například Business Connector nebo WCF), velmi často je třeba použít přímo klienta (ax32.exe) z příkazové řádky. Týká se to zejména administrace a vývoje – automatizovaná konfigurace, kompilace, aktualizace křížových referencí a podobně.

Bohužel ax32.exe nevrací do konzole žádný výstup, je proto těžké zjistit, zda všechno běží nebo došlo k nějaké chybě. To je samozřejmě problém zejména pro automatizované skripty, protože ty se nemohou podívat, co se děje v GUI.

Jeden možný přístup je zapisovat stavové informace z Dynamics AX do nějakého logu (soubor, EventLog) a zpětně je analyzovat, ale zápis do konzole má řadu výhod – výstup je jasně svázán s konkrétním procesem a příkazem, není třeba řešit přístup k externím logům, konzole může výstup snadno dále zpracovávat (včetně přesměrování do souboru) a tak dále.

Na konzolový výstup se dá pohodlně zapisovat pomocí .NET třídy System.Console, vytvoříme si proto X++ třídu volající System.Console přes velmi zjednodušené rozhraní.

class SysConsole
{
    public static void writeLine(str _message)
    {
        ;
        System.Console::WriteLine(_message);
    }
    public static void writeErrorLine(str _message)
    {
        System.IO.TextWriter writer = System.Console::get_Error();
        ;
        writer.WriteLine(_message);
        writer.Close();
    }
}

Zprávy na konzolový výstup pak můžeme jednoduše zasílat voláním těchto metod, jako v následujícím zkušebním jobu:

SysConsole::writeLine("First!");
SysConsole::writeLine("Another message");
SysConsole::writeErrorLine("Something is wrong");

Pokud spustíte klienta Dynamics AX z příkazové řádky a následně testovací job, nic se bohužel nestane. Bude třeba zvolit trochu komplikovanější postup.

První problém je v tom, že příkazová řádka nečeká na výstup z programu, jen ho spustí a pokračuje s dalšími příkazy. Pouhé čekání na ukončení programu, jako v následujícím PowerShell skriptu, ale stále nic nezobrazuje.

Start-Process ax32.exe -Wait

Pokud ale přesměrujete výstupní a chybový proud do souboru, korektně se zapíší.

Start-Process ax32.exe -Wait -RedirectStandardOutput c:\Temp\out.txt -RedirectStandardError c:\Temp\error.txt

Výstup tedy získat lze a to je nejdůležitější zpráva. Pro některé případy je prostý zápis do souboru vyhovující (rozdíl oproti zápisu přímo z AX je v tom, že si jména souborů volí volající skript), ale stále to není „normální“ výstup do konzole.

V dalším pokusu vytvoříme proces přímo pomocí .NET třídy System.Diagnostics.Process, přesměrujeme výstup a budeme ho číst z vlastností procesu:

$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = 'C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\ax32.exe'
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
 
$process.Start() | Out-Null
$process.WaitForExit()
 
Write-Host $process.StandardOutput.ReadToEnd()
Write-Host $process.StandardError.ReadToEnd() -ForegroundColor Red
 
$process.Close()

To skutečně zobrazí výstup z našeho testovacího jobu do konzole:

Toto řešení má ale stále nedostatky, zejména v tom, že se výstup zobrazí až po skončení procesu. To je sice dostatečné pro zobrazení konečných výsledků, ne ale pro jakékoli zobrazování průběhu zpracování.

V C# se nabízí celkem přímočaré řešení pomocí událostí OutputDataReceived a ErrorDataReceived – viz následující jednoduchou konzolovou aplikaci.

class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }
 
    void Run()
    {
        string file = @"c:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\ax32.exe";
 
        using (Process ax = new Process())
        {
            ax.StartInfo.FileName = file;
            ax.StartInfo.UseShellExecute = false;
            ax.StartInfo.RedirectStandardOutput = true;
            ax.StartInfo.RedirectStandardError = true;
            ax.OutputDataReceived += new DataReceivedEventHandler(outputDataReceived);
            ax.ErrorDataReceived += new DataReceivedEventHandler(errorDataReceived);
            ax.Start();
 
            ax.BeginOutputReadLine();
            ax.BeginErrorReadLine();
            ax.WaitForExit();
        }
        Console.ReadLine();
    }
    private void outputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!String.IsNullOrEmpty(e.Data))
        {
            Console.WriteLine(e.Data);
        }
    }
    private void errorDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!String.IsNullOrEmpty(e.Data))
        {
            Console.WriteLine("Error: {0}", e.Data);
        }
    }
}

Přestože Powershell umožňuje zpracovávat ty samé události, musel jsem zvolit trochu komplikovanější řešení.

$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = "ax32.exe"
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
 
Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -SourceIdentifier AxOutput
Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -SourceIdentifier AxError
 
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
 
Function GetAxMessages
{
    Get-Event -SourceIdentifier AxOutput -ErrorAction SilentlyContinue | %{
        if ($_.SourceEventArgs.Data)
        {
            $_.SourceEventArgs.Data
        }
        Remove-Event -EventIdentifier $_.EventIdentifier
    }
 
    Get-Event -SourceIdentifier AxError -ErrorAction SilentlyContinue | %{
        if ($_.SourceEventArgs.Data)
        {
            Write-Error $_.SourceEventArgs.Data
        }
        Remove-Event -EventIdentifier $_.EventIdentifier
    }
}
 
while (!$process.WaitForExit(1000))
{
    GetAxMessages
}
 
$process.WaitForExit()
$process.Close()

Skript spustí ax32.exe, přihlásí se k událostem RedirectStandardOutput a RedirectStandardError a každou vteřinu zpracovává přidané události. Možná by se to dalo napsat jednodušeji, každopádně to dělá přesně to, co od toho potřebuji, a to je to hlavní. Veškeré zprávy zapsané z AX do výstupního nebo chybového proudu jsou odchyceny a se zanedbatelným zpožděním zapsány do konzole Powershellu. Chyby jsou zapsány pomocí Write-Error, takže mohou být zpracovávany běžným způsobem (třeba použít parametr -ErrorAction Stop k zastavení na první chybě).

Výše uvedený Powershellový kód určitě není něco, co byste chtěli ručně psát do konzole. Naštěstí jsem to již stihl začlenit do DynamicsAxCommunity modulu, takže jediné, co musíte udělat, je zavolat Start-AXClient s parametrem -Wait.  (Stáhněte si poslední verzi z repozitáře, zatím to není v žádném releasu.)

Zápis do konzole lze využít mnoha různými způsoby – automatizované skripty mohou dostávat zprávy z Dynamics AX, lze zobrazovat průběh dlouhotrvajících procesů (např. aktuálně kompilovaný objekt) nebo třeba pro ladící zprávy.

Můžete dokonce zapisovat všechny chyby z Dynamics AX do chybového proudu (tzn. volat SysConsole::writeErrorLine() z Info.add()). Pokud dojde k chybě, volající skript o ní dostane informaci a může nějak reagovat. Bez odchycení chyby (ať už do konzole nebo jinak) by zkrátka proces dále běžel, nebo (jako v následujícím příkladu) skončil bez indikace chyby.

Pro jistotu ještě upozorňuji, že Dynamics AX na konzoli normálně nezapisuje (alespoň pokud vím) a ani DynamicsAxCommunity žádný takový kód neobsahuje. Stejně tak třída SysConsole není součást Dynamics AX. Aby vám fungoval příklad na předchozím obrázku, musíte sami naimplementovat volání v Dynamics AX.

Kvalita je proces

Jedna část mé práce na všech projektech od doby, kdy jsem opustil svého prvního zaměstanavatele ve světě Dynamics AX, bylo zlepšit kvalitu softwarových produktů. To není náhoda – je to proto, že chci dělat kvalitní software and pomoci společnostem toho dosáhnout.

Vysoká kvalita je dobrá pro všechny – má očividné výhody pro uživatele, ale je dobrá i pro nás – nemusíme ztrácet tolik času podporou, hledáním chyb, vývojem a nasazováním oprav a tak dále a můžeme se soustředit na vývoj nových vlastností (to je naše skutečná práce, ne?). A samozřejmě, spokojení zákazníci nám spíše svěří další práci a doporučí nás svým obchodním partnerům.

Všechny ty společnosti, pro které jsem pracoval, cítily, že kvalita jejich produktů není příliš vysoká, nebo alespoň že je dobré o kvalitě mluvit. Zda byla kvalita skutečně jejich prioritou by bylo jiné vyprávění, nyní chci poukázat na celková očekávání ohledně zlepšování kvality. Abych citoval jistého manažera:

„Ty chceš měnit proces? My mysleli, že jen změníš něco v kódu.“

Tohle mi výrazně pomohlo pochopit, jak někteří lidé vidí zlepšování kvality softwaru – „kód je špatný, jdi do kódu, oprav kód“.

Zkuste se nad tím chvilku zamyslet. Kvalita našeho softwaru je nízká, protože jsme správně nepochopili požadavky, protože jsme navrhli suboptimální řešení, protože náš kód neřeší mnoho mezních situací, protože naše GUI není intuitivní a podobně. Jistě, všechny tyto věci mohou být opraveny, teoreticky – můžeme nově navrhnout vlastnosti, můžeme změnit technickou architekturu, můžeme přepsat kód. To může být extrémně nákladné a ty činnosti jsou vlastně stejné jako ty, které jsme dělali původně – a selhali jsme.  Bude to nyní lepší? A pokud chceme jen prohlédnout kód a trochu ho změnit, jak opravíme, co náš software dělá by design?

To, co doručujeme zákazníkům, je pouhý výstup procesu vytváření softwaru. Všechny ty analýzy požadavků, design GUI, návrh databáze, objektové modelování atd. definují produkt a jejich kvalita se odrazí v kvalitě produktu. Pokud náš software ignoruje důležité požadavky, nemůže být dobrý. Pokud naši vývojáři neví jak pracovat s výjimkami, celý produkt nebude zpracovávat výjimky správně. A tak dále.

Můžeme jen tak kouknout do kódu a změnit takový software na vysoce kvalitní? To je očividně nemožné. Pohledem do kódu nezjistíme, jaké požadavky jsme opomněli. To vyžaduje nový a lepší sběr požadavků. Stejně tak nemůžeme jednoduše přidat lepší zpracování výjimek – musíme udělat lepší analýzu možných výjimečných situací a jejich řešení. To vše jsme měli udělat lépe napoprvé.

Když lidé říkají, že kvalita jejich softwaru je nízká, automaticky to znamená, že kvalitní nejsou jejich postupy. Jejich process generuje nekvalitní software. Dokud není problematická část opravena, process bude stále generovat podobné výsledky.

Co vývojové týmy skutečně potřebují není někdo, kdo přijde a opraví jejich špatný kód. Potřebují přemýšlet o tom, jak pracují a jestli jsou všechny postupy dostatečně dobré. Potřebují identifikovat problémy a změnit postupy tak, aby se stejný problém neopakoval znovu. Musí si uvědomit, že to není jen o psaní kódu, a myslet také na kvalitu požadavků, návrhu atd. Bez všech těchto věcí nemohou lidé očekávat žádnou skutečnou změnu kvality.

Mimochodem, tohle je jedna z věcí, kde excelují iterativní postupy – v každé iteraci projdete všechny fáze projektu, takže vidíte, zda jsou funkční designy dostatečně dobré pro vývoj, zda je snadné přidávat nové vlastnosti do existujícího kódu, co si lidé myslí o GUI… A pokud něco jde špatně, můžete upravit process a vidět hned v další iteraci, zda došlo ke zlepšení.

Included columns v Dynamics AX 2012

Included columns* je schopnost SQL Serveru (od verze 2005) týkající se indexů. Díky ní můžete připojit k indexu další sloupce, které nejsou používány pro vyhledávání (takže se nemusí tak nákladně udržovat), ale které může databáze použít pro vrácení dat. Pokud jsou všechny sloupce vracené dotazem zahrnuty v indexu, databázový server může rovnout vrátit data a vůbec nemusí přistupovat k tabulce jako takové, což samozřejmě šetří čas.

(* není mi znám žádný ustálený český překlad, tak to raději ponechám v původním znění)

Included columns mají i další výhody:

  • mohou být přidány do unikátního indexu, aniž by měly vliv na unikátnost
  • podporují i (některé) datové typy, které nemohou být použity v běžných indexech
  • nepočítají se do limitu sloupců v indexu (16)
  • nepočítají se do limitu velikosti indexu (900 bytů)

Platí pro ně následující (logická) omezení:

  • jsou vždy až na konci indexu
  • index musí obsahovat alespoň jeden „normálně“ indexovaný sloupec
  • nemohou být použity v klastrovaných indexech

Samozřejmě i included columns mají vliv na velikost indexu, takže mohou zpomalovat operace s indexem a zabírat místo na disku.

Podpora v Dynamics AX 2012

Vytvořit included column je v AX2012 velmi snadné. Přidejte pole do indexu stejným způsobem jako obvykle a pak na tomto sloupci indexu změňte vlastnost IncludedColumns na Yes.

Na obrázku je konkrétní příklad z AX2012 – index TransIdIx na tabulce CustInvoiceTrans. Má tři „normální“ pole použitelná pro vyhledávání a dvě dodatečná pole (Qty a QtyPhysical) pro pokrytí nějakých dotazů na tato pole.

Obdobným způsobem můžete rozšířit i unikátní indexy včetně primárního klíče. Například vytvoříte index s unikátním polem Id a k němu připojíte pole Name, na které se často dotazujete. Takový index můžete stále přiřadit jako primární (což byste nemohli, pokud by druhé pole nebylo included column), ale nemůžete ho použít jako ClusterIndex.

AX vám sice dovolí přepnout vlastnost IncludedColumn na libovolném poli v indexu, ale neplatnou konfiguraci vám samozřejmě odmítne SQL Server při synchronizaci.

Reflexe

Může se vám také hodit podpora pro included columns pro reflexi (můžete si chtít třeba vypsat prodrobnosti o všech indexech ve svém modulu). Konkrétně jde o tyto metody ve třídě DictIndex:

  • int numberOfIncludedColumns()
  • FieldId includedColumns(int _inclColumnIdx)

Jen jsem narazil na nějaký problém s automaticky generovaným indexem RecId. To je vidět i na následujcím obrázku – AX vrací správný počet included columns pro ostatní indexy (1 a 0), ale 35888 sloupců v indexu RecId je zjevný nesmysl. Každé spuštění navíc vrací jinou hodnotu – skoro to vypadá, že AX čte špatnou paměťovou adresu, ale kdoví.

Included columns se určitě vyplatí používat – bez nich buďto musíte oželet pokrytí některých dotazů, nebo vytvářet plnohodnotné (a nákladněji udržované) indexy. Někdy vám included columns umožní i snížit počet potřebných indexů.

To ale neznamená, že je možné začít přidávat bezmyšlenkovitě sloupce do indexů – i správa included columns něco stojí.

Team Foundation Build – Šablona procesu

Jak jsem zmínil v Lehký úvod do Team Foundation Build 2010, jednotlivé akce buildu a jejich sekvence jsou definovány v šabloně procesu. Ta je založena na Windows Workflow Foundation 4.0, takže je to vlastně textový XAML soubor. Hlavně ale existuje vizuální návrhář integrovaný do Visual Studia, díky kterému lze definovat proces prostým přetažením akcí do editoru a nastavením parametrů.

Funkcionalita týkající se buildů, včetně šablony procesu, je přístupná z Team Exploreru (uzel Builds v týmovém projektu). Vytváření definice buildu zahájíme volbou New Build Definition.

Otevře se dialog, kde můžete nastavit mnoho užitečných parametrů, ale v tuto chvíli je podstatná záložka Process a sekce Default process template v její horní části.


Po kliknutí na Show details uvidíte rozbalovací seznam s výběrem z existujících šablon a (krom jiného) tlačítko New pro vytvoření nové šablony a cestu k souboru šablony.


Zde můžete vidět, že šablony se ukládájí do správy verzí – tam k nim má Team Foundation Build pohodlný přístup.

Po stisknutí tlačítka New se zobrazí dialog, kde můžete zvolit na jaké šabloně bude vytvářená šablona založena, jméno nové šablony apod.


Na pozadí se vytvoří nový soubor šablony a nastaví jako výchozí v dialogu definice buildu. Kliknutím na odkaz zobrazíte soubor šablony v Source Control Exploreru:

Dvojklik na názvu souboru otevře šablonu v návrháři integrovaném do Visual Studia. Protože jsme šablonu založili na již existující šabloně, editor už nějakou definici procesu zobrazuje.

Existující akce lze konfigurovat – stačí na danou akci kliknout a změnit její vlastnosti v okně Properties:

nebo v některých případech přímo v návrháři.


Jak je vidět, na mnoha místech je možné psát kód (VB.NET), takže nejste omezeni nějakou předdefinovanou množinou akcí a operátorů. (Na proměnné se podíváme podrobněji někdy příště.)

Nové akce prostě přetáhněte z Toolboxu – buď do vyhrazeného místa v nějaké aktivitě, třeba v podmíněném bloku:

nebo na jeden z trojúhelníčků, které jsou ve výchozí šabloně na mnoha místech:

Tyto troujúhelníčky jsou ve skutečnost část aktivity Sequence. Ta je důležitá, protože třeba v předchozím příkladu můžete do If přiřadit pouze jednu akci – pokud potřebujete více akcí, třeba vytvořit adresář a stáhnout soubory, musíte do If nejdřív přetáhnout Sequence a teprve do ní další aktivity.

Dostupných aktivit a řídících strkutur je celá řada (a je možné definovat i vlastní), jen pro představu pár příkladů:

  • ForEach – projde zadanou kolekci a pro každý prvek spustí nějakou akci
  • TryCatch – zpracování výjimek pomocí klasické konstrukce Try/Catch/Finally
  • LabelWorkspace – vytvoří label ve správě verzí
  • InvokeProcess – spustí příkaz v příkazové řádce

Popis předdefinovaných aktivit je k dispozici na MSDN.

Lehký úvod do Team Foundation Build 2010

Team Foundation Build je součástí Team Foundation Server a slouží k automatizovanému sestavování aplikací. Typicky takový build získá nejnovější verze zdrojového kódu a odkazovaných komponent, zkompiluje aplikaci, spustí testy, vytvoří instalátor a tak dále. Dokáže také logovat a oznamovat výsledky buildu, přiřadit pracovním položkám číslo buildu, v kterém jsou zahrnuty, a mnoho dalšího. Buildy mohou být spouštěny ručně, jako noční buildy, po každém změně a tak podobně.

Smysl buildu je zřejmý – ověří, že do sebe všechny části zapadají, a připraví aplikaci do spustitelné podoby, takže ji lze testovat či distribuovat. A protože se aplikace skládají z mnoha komponent (na nich často pracují různé týmy nebo dokonce různé společnosti), není sestavení všech částí ve správných verzích triviální úkol. Často to vyžaduje to mnoho kroků i hodně času.

Zde je smysl automatizace – jakmile je proces zautomatizován (a otestován), odpadá složitá a k chybám náchylná manuální práce. Časová náročnost buildu také není zásadní problém, protože po zautomatizování vás build stojí jen výpočetní čas, ale lidé se mohou věnovat práci skutečně vyžadující lidské úsilí. A bez automatizace byste nikdy nedosáhli dostatečně častých buildů, takže problémy byste nemuseli identifikovat včas.

Protože je na tomto blogu řeč hlavně o Dynamics AX, představte si, že chcete připravit testovací prostředí Dynamics AX. Musíte získat aktuální verze X++ kódu a jakýchkoli dalších komponent (např. externích DLL knihoven), nasadit kód (v závislosti na verzi AX řešit také labely, indexy apod.), zkompilovat aplikaci, synchronizovat databázi, aktualizovat reporty atd. atd. Spousta kroků, spousta prostoru k chybám, trvání – minimálně hodina. Automatizace toto celé řeší a Team Foundation Build poskytuje ideální infrastrukturu, ačkoli není připraven přímo pro Dynamics AX.

Nasazení Team Foundation Build 2010

Zjednodušeně řečeno, Team Foundation Build se skládá z build controlleru, který řídí postup sestavení, a jednoho nebo více build agentů, které provádějí jednotlivé úkony. Minimálně potřebujete jeden build controller a jeden build agent.

Team Foundation Build lze nainstalovat z instalačního média Team Foundation Serveru. Pokud vám vyhovuje výchozí nastavení (jeden agent, vše na jednom stroji atd.), stačí jen přiřadit kolekci projektů TFS. V reálném nasazení asi budete chtít více plánovat s ohledem na výkon, zabezpečení, software dostupný jednotlivým build agentům atd.

Definice buildu se skládá z šablony procesu a konkrétních parameterů. Šablona je založena na Windows Workflow Foundation 4.0 – díky tomu je možné snadno definovat tok procesu včetně podmíněných bloků, cyklů, paralelního zpracování a podobně. Navíc je k dispozici grafický editor (viz obrázek níže) a několik způsobů, jak vytvářet vlastní stavební bloky.

Build Template Editor

Výchozí šablony jsou pochopitelně nachystané pro klasické .NET aplikace, pro Dynamics AX nebo jiné atypické scénáře je třeba šablonu modifikovat. To je naštěstí něco, s čím se v Team Foundation Buildu počítá a možnosti konfigurace a rozšiřování jsou velmi široké.

Použití

Funkce týkající se buildů jsou přístupné přímo z Team Exploreru:

Team Explorer - Build context menu

V definici buildu můžete nastavit základní parametry, například automatické spouštění buildu.

Build definition - Triggers

Build Explorer zobrazuje naplánovaná a uskutečněná sestavení:

Build Explorer

O každém buildu si můžete zobrazit také podrobnější informace:

Failed build details

Výsledky si můžete nechat zasílat e-mailem, můžete využít připravené reporty, build může v případě selhání automaticky vytvořit pracovní položku pro opravu atd. Integrace s pracovními položkami vám také umožní sledovat v jakém buildu byla objevena nebo opravena nějaká chyba, analyzovat jaké testy by měly být pro daný build spuštěny, dokumentovat obsah buildu (pracovní položky, soubory) a tak dále a tak dále.

Závěr

Díky Team Foundation Buildu může být sestavovací proces mnohem jednodušší, jednotnější a škálovatelnější než pomocí ad hoc skriptů. Šablona procesu postavená na Windows Workflow Foundation umožňuje snadno celý proces modifikovat a integrace s ostatními částmi TFS nabízí celou řadu unikátních možností.

Je pochopitelné, že se při zavádění Team Foundation Serveru klade důraz na správu pracovních položek a zdrojového kódu. I tak mi ale Team Foundation Build připadá neprávem trochu opomíjený. Přitom vývojové týmy plýtvají časem na ruční sestavování, sepisování změněných objektů, hledání „kde byla ta chyba opravena“ a podobně, často v dost tristní kvalitě. Team Foundation Build je jednou z cest, jak zvýšit kvalitu a zároveň i ušetřit čas (a peníze).