\


 Monday, 26 May 2014
Task Parallel Library a RStein. Async 1 - Popis základních tříd a obcházení omezení v TPL

(Obnoveno ze zálohy, omlouvám se za formátování kódu)

V následující sérií článků chci představit některé konstrukce ze své knihovny RStein.Async. Většina popisovaných tříd intenzivně využívá a někdy i s gustem zneužívá Task Parallel library.  V článcích se tedy objeví i mnoho informací o samotné knihovně TPL a klíčových slovech async a await v C#.  V článcích předpokládám jen základní znalost TPL. Pod základní znalostí si představuju, že víte, jak spustíte nový Task,  k čemu se dá Task použít a jak získáte výsledek zpracování Tasku.
Knihovna RStein.Async vznikla jako vedlejší důsledek zkoumání možností Schedulerů v TPL, kdy jsem na projektech zkoušel, co si mohu s TPL dovolit a co je mi v TPL už odepřeno, nebo jsem zjišťoval, jaké je skutečně chování tříd, které jsou v dokumentaci nedostatečně popsány. Články jsou určeny i pro čtenáře, kteří třeba neví, proč by měli používat ConcurrentExclusiveSchedulerPair, protože v jednom díle popíšu nejen to, jaké jsou výhody tohoto scheduleru oproti běžně využívaným a hlavně zneužívaným synchronizačním primitivám (lock-Monitor, Mutex, SpinLock, Condition variable atd), ale napíšeme si i vlastní ConcurrentStrandSchedulerPair a pitváním jeho vnitřností zjistíme, jak se dá napsat ekvivalent třídy ConcurrentExclusiveSchedulerPair. Také chci ukázat, jak je možné napsat jednoduché aktory (a tím skutečně nemyslím ty směšné panďuláky na diagramu případů užití Veselý obličej )  s využitím našeho speciálního strand scheduleru a porovnám je s aktory, které lze napsat pomocí samotného TPL Dataflow v .Net Frameworku.
Pro lidi, kteří vyvíjejí v C++ a znají knihovnu BOOST ASIO, může být zajímavé, že se článku objeví názvy tříd, které důvěrně znají  - io_service a strand.  A dodám, že jsem nepoužil jen názvy, ale i odpovědnosti těchto tříd se shodují  s odpovědnostmi tříd v Boostu, i když jsou mé třídy pochopitelně napsány zcela jinak.
Snad se mi vás podařilo navnadit a pro nedočkavé dodám, že si mohou již dnes celou knihovnu stáhnout z Bitbucketu.

git clone git@bitbucket.org:renestein/rstein.async.git

Forky a pull requesty od kohokoli jsou skutečně vítány. Veselý obličej


Seriál  Task Parallel Library a RStein.Async  (předběžná osnova)

Task Parallel Library a RStein. Async 1 z n –  Popis základních tříd a obcházení omezení v TPL.

Task Parallel Library a RStein. Async 2 z n –  (boost) ASIO v .Net a IoServiceScheduler.

Task Parallel Library a RStein. Async 3 z n – Ukázky použití IoServiceScheduleru. Coroutines.

Task Parallel Library a RStein. Async 4 z n  – ThreadPoolScheduler založený na IoServiceScheduleru.

Task Parallel Library a RStein. Async 6 z n – Vytvoření StrandScheduleru.

Task Parallel Library a RStein. Async 7 z n – Náhrada za některé synchronizační promitivy – ConcurrentStrandSchedulerPair.

Task Parallel Library a RStein. Async 8 z n – Jednoduchý “threadless” actor model s využitím StrandScheduleru.

Task Parallel Library a RStein. Async 9 z n – Píšeme aktory I.

Task Parallel Library a RStein. Async 10 z n – Píšeme aktory II.

Task Parallel Library a RStein. Async 11 z n – Píšeme nový synchronizační kontext  - IOServiceSynchronizationContext.

Task Parallel Library a RStein. Async 12 z n – Použití IOServiceSynchronizationContextu v konzolové aplikaci a Windows službě.

(bude upřesněno)


Poznámka: V celé sérii článků budu používat slovo Task pro třídu, task pro název proměnné / argumentu metody a ”anglicismy” tásk/tásky místo “úloha/úlohy“ nebo jiného českého patvaru při zmínce o /úlohách-táscích/ v dalším textu. Předpokládám, že pro většinu vývojářů je takový text srozumitelnější.

V průběhu celého seriálu budeme psát nové schedulery. Jak asi víte, TaskScheduler je v TPL nízkoúrovňová třída, která je odpovědná za vyřízení předaných objektů Task. Každý potomek abstraktní třídy TaskScheduler rozhoduje o tom, kolik threadů se použije k vyřízení požadavků, i o tom, v jakém  pořadí  a kdy přesně budou předané objekty Task spuštěny. V .Net Frameworku jsou dva základní schedulery, které by měly pro většinu běžných scénářů postačovat. Scheduler, který je dostupný ve vlastnosti TaskScheduler.Default, využívá výchozí .Net ThreadPool a scheduler vrácený vlastností TaskScheduler.FromCurrentSynchronizationContext se hodí pro aplikace, ve kterých musí platit, že s ovládacími prvky na formuláři manipuluje jen tzv. UI thread, který ovládací prvek vytvořil (Windows Forms, WPF, Silverlight, Metro - Modern UI), jinak dojde k výjimce.

Chcete-li napsat vlastní TaskScheduler, podědíte z třídy TaskScheduler a přepíšete následující metody:

protected internal abstract void QueueTask(
	Task task
)

Metoda QueueTask většinou uloží předaný objekt task do nějaké své interní kolekce k pozdějšímu vyřízení.

protected abstract bool TryExecuteTaskInline(
	Task task,
	bool taskWasPreviouslyQueued
)

Metoda TryExecuteTaskInline je volána, jestliže infrastruktura TPL rozhodne, že objekt task by měl být spuštěn v aktuálním vlákně. Typicky je tato metoda volána, když čekáte na výsledek zpracování tasku (task.Wait) a thread, ve kterém je metoda Wait přímo či nepřímo zavolána, není blokován, ale využit infrastrukturou TPL ke zpracování tásku. Pravidelně zabíjený nebo i jen blokovaný thread skutečně není ve vícevláknových aplikacích dobrý thread. V druhém argumentu – taskWasPreviouslyQueued – máte příznak, který sděluje, jestli task již byl nebo nebyl předán metodě QueueTask a podle toho lze upravit logiku v Scheduleru. Jak uvidíme později u StrandScheduleru, tento příznak pro nás bude vemi důležitý proto, abychom dostáli všem zárukám při zpracování tásků, které StrandScheduler svým klientům poskytuje.
Jestliže se ve vlastním Scheduleru rozhodneme, že teď je možné task vyřídit, stačí zavolat metodu TryExecuteTask z bázové třídy TaskScheduler a ta se postará o veškeré další záležitosti včetně uložení výsledku zpracování nebo výjimky do objektu task.

Další metodu používá hlavně debugger, který dovede zobrazit frontu tásků čekajících na vyřízení v našem scheduleru.

protected abstract IEnumerable<Task> GetScheduledTasks()

Každý scheduler by také měl být schopen sdělit, kolik tásků dokáže v jednom okamžiku vyřizovat paralelně. Neboli jaký je nejvyšší stupeň konkurence v Scheduleru, což je údaj, který poskytneme zájemcům ve vlastnosti MaximumConcurrencyLevel.

public virtual int MaximumConcurrencyLevel { get; }

Na kód speciálních schedulerů se můžete podívat v Parallel Extension Extras od Microsoftu.

Když začnete psát méně tradiční schedulery, narazíte na jedno zásadní omezení. Nový objekt task je po pokusu o spuštění tásku (Task.Run, TaskFactory.Run, Task.Start) asociován s právě použitým schedulerem a nikdy už nemůže být předán jinému scheduleru. Když se o něco takového pokusíte, metoda TryExecuteTask vyhodí výjimku, ve které vám sdělí, že žonglování s táskem mezi schedulery není povoleno.
To asi nevypadá jako nějaké zásadní omezení, protože proč bychom měli vůbec chtít přehodit tásk z jednoho scheduleru do druhého? Jak uvidíte v dalších dílech serálu, napíšeme si postupně pro své schedulery dekorátory, kteří například zajistí, že po určitou dobu nebudou tásky zpracovávány, ale jen schraňovány v privátní frontě a teprve po splnění dalších podmínek uvolněny k vyřízení. Tedy tásk bude aktivován v nějakém jiném scheduleru, než je ten, který tásk později vyřídí.
Bez přepsání TPL bohužel nelze toto omezení, které by se dalo parafrázovat větou  “tásk předán scheduleru, z toho nutně a nepodmíněně plyne, že ten samý scheduler tásk také vyřídí”, jednoduše potlačit.

V knihovně RStein.Async jsem musel tedy zkusit navrhnout rozhraní a třídy tak, aby se “vlčí” knihovna TPL “nažrala” a přitom můj návrh (doufám, že ne “kozí”! Mrkající veselý obličej) zůstal celý.

Nejprve tedy musíme uspokojit TPL a přitom musíme být schopni zavolat metodu TryExecuteTask z třídy TaskScheduler odkudkoli z naší knihovny. TPL proto nabídnu speciální scheduler, který je z hlediska TPL plnohodnotným schedulerem. Tento scheduler nebude dělat nic jiného, než delegovat vykonání všech metod na mé vlastní “reálné” schedulery a čekat, až “reálný” scheduler požádá o vykonání Tasku

using System.Threading.Tasks;

namespace RStein.Async.Schedulers

{

public interface IProxyScheduler

{

bool DoTryExecuteTask(Task task);

TaskScheduler AsTplScheduler();

}

}

Rozhraní IProxyScheduler umí jen dvě věci. Metoda AsTplScheduler musí vrátit scheduler, se kterým umí pracovat TPL, a implementace metody DoTryExecuteTask zavolá metodu TryExecuteTask z TPL scheduleru.

Náš konkrétní proxy scheduler vypadá takto:

using System;

using System.Collections.Generic;

using System.Threading.Tasks;

namespace RStein.Async.Schedulers

{

public class ProxyScheduler : TaskScheduler, IProxyScheduler, IDisposable

{

private readonly ITaskScheduler m_realScheduler;

public ProxyScheduler(ITaskScheduler realScheduler)

{

if (realScheduler == null)

{

throw new ArgumentNullException("realScheduler");

}

m_realScheduler = realScheduler;

m_realScheduler.ProxyScheduler = this;

}

public override int MaximumConcurrencyLevel

{

get

{

return m_realScheduler.MaximumConcurrencyLevel;

}

}

public void Dispose()

{

Dispose(true);

}

public virtual bool DoTryExecuteTask(Task task)

{

if (task == null)

{

throw new ArgumentNullException("task");

}

bool taskExecuted = TryExecuteTask(task);

if (taskExecuted)

{

task.RemoveProxyScheduler();

}

return taskExecuted;

}

public virtual TaskScheduler AsTplScheduler()

{

return this;

}

protected override void QueueTask(Task task)

{

task.SetProxyScheduler(this);

m_realScheduler.QueueTask(task);

}

protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)

{

if (!taskWasPreviouslyQueued)

{

task.SetProxyScheduler(this);

}

return m_realScheduler.TryExecuteTaskInline(task, taskWasPreviouslyQueued);

}

protected override IEnumerable<Task> GetScheduledTasks()

{

return m_realScheduler.GetScheduledTasks();

}

protected void Dispose(bool disposing)

{

if (disposing)

{

m_realScheduler.Dispose();

}

}

}

}

V kódu asi není po přečtení předchozích odstavců moc překvapivých řádků. Tento scheduler bude vydán kdykoli, kde je očekáván Tpl scheduler, a proto:

1) Dědíme z abstraktní třídy TaskScheduler z TPL a podporujeme před chvílí popisované rozhraní IProxyScheduler.
2) Metoda AsTplScheduler vrátí odkaz na samotný objekt “this” – aktuální proxy scheduler.

3) Metody QueueTask, TryExecuteTaskInline, GetScheduledTasks a MaximumConcurrenyLevel jsou implementovány tak, že delegují na nějaký “reálný” scheduler z naší knihovny.

4) Metoda DoTryExecuteTask volá metodu TryExecuteTask.

Metody QueueTask a TryExecuteTaskInline také asociují a deasociují  ProxyScheduler s předaným táskem pomocí extenzních metod SetProxyScheduler a RemoveProxyScheduler.  Teď nás tolik trápit nemusí, jak jsem tyto metody napsal. Zájemci se ale mohou na kód podívat v předstihu.

Ještě jednou k terminologii schedulerů v knihovně RStein.Async, která může být zpočátku matoucí. ProxyScheduler má ve svém názvu slovo proxy, protože z pohledu všech dalších tříd v knihovně RStein.Async  tásky vyřizují jiné (“reálné”) schedulery, kteří nejsou, jak uvidíme za chvíli, potomkem třídy TaskScheduler z TPL a kteří čekají na to, až ProxyScheduler zavolá jejich metody. Při spuštění tásku musí ale i “reálný” scheduler požádat ProxyScheduler, aby metodou TryExecuteTask upozornil infrastrukturu TPL, že je třeba task nyní vyřídit.

Takže knihovna RStein.Async používá ProxyScheduler, který ale knihovna TPL vidí jako jediný  pro ni dostupný “reálný” scheduler.
Laboroval jsem s různými názvy pro ProxyScheduler, ale všechny další varianty mi přišly ještě horší.

Rozhraní ITaskScheduler je rozhraní, které podporují všechny "reálné" schedulery v knihovně RStein.Async.

using System;

using System.Collections.Generic;

using System.Threading.Tasks;

namespace RStein.Async.Schedulers

{

public interface ITaskScheduler : IDisposable

{

int MaximumConcurrencyLevel

{

get;

}

IProxyScheduler ProxyScheduler

{

get;

set;

}

Task Complete

{

get;

}

void QueueTask(Task task);

bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued);

IEnumerable<Task> GetScheduledTasks();

}

}

Toto rozhraní obsahuje všechny metody a vlastnosti, které jsme popisoval výše u schedulerů v TPL. Na tyto metody deleguje ProxyScheduler, jehož instance je z rozhraní ITaskScheduler také dostupná. V rozhraní naleznete také vlastnost Complete, která vrací Task, jenž by měl být ve stavu “dokončen” v okamžiku, když  scheduler již skončil svou práci a nemá být dále používán.

Schedulery v knihovně RStein.Async mají mnoho společných rysů, a proto jsem základní charakteristiky vytáhl do vlastní bázové třídy TaskSchedulerBase, aby všechny Schedulery nemusely reimplementovat celé rozhraní ITaskScheduler.

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.Threading;

using System.Threading.Tasks;

namespace RStein.Async.Schedulers

{

public abstract class TaskSchedulerBase : ITaskScheduler

{

private const string PROXY_SCHEDULER_ALREADY_SET_EXCEPTION_MESSAGE = "ProxyScheduler is already set and cannot be modified!";

private readonly CancellationTokenSource m_schedulerCancellationTokenSource;

private readonly TaskCompletionSource<object> m_serviceCompleteTcs;

private readonly object m_serviceLockObject;

private bool m_disposed;

private IProxyScheduler m_proxyScheduler;

protected TaskSchedulerBase()

{

m_disposed = false;

m_serviceLockObject = new Object();

m_serviceCompleteTcs = new TaskCompletionSource<object>();

m_schedulerCancellationTokenSource = new CancellationTokenSource();

}

protected object GetServiceLockObject

{

get

{

return m_serviceLockObject;

}

}

protected virtual CancellationToken SchedulerRunCanceledToken

{

get

{

return m_schedulerCancellationTokenSource.Token;

}

}

protected virtual CancellationTokenSource SchedulerRunCancellationTokenSource

{

get

{

return m_schedulerCancellationTokenSource;

}

}

public abstract int MaximumConcurrencyLevel

{

get;

}

public virtual IProxyScheduler ProxyScheduler

{

get

{

return m_proxyScheduler;

}

set

{

lock (GetServiceLockObject)

{

checkIfDisposed();

if (value == null)

{

throw new ArgumentNullException("value");

}

if (m_proxyScheduler != null)

{

throw new InvalidOperationException(PROXY_SCHEDULER_ALREADY_SET_EXCEPTION_MESSAGE);

}

m_proxyScheduler = value;

}

}

}

public virtual Task Complete

{

get

{

return m_serviceCompleteTcs.Task;

}

}

public void Dispose()

{

lock (m_serviceLockObject)

{

if (m_disposed)

{

return;

}

try

{

Dispose(true);

m_disposed = true;

m_serviceCompleteTcs.TrySetResult(null);

SchedulerRunCancellationTokenSource.Cancel();

}

catch (Exception ex)

{

Trace.WriteLine(ex);

m_serviceCompleteTcs.TrySetException(ex);

}

}

}

public abstract void QueueTask(Task task);

public abstract bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued);

public abstract IEnumerable<Task> GetScheduledTasks();

protected abstract void Dispose(bool disposing);

protected void checkIfDisposed()

{

if (m_disposed)

{

throw new ObjectDisposedException(GetType().FullName);

}

}

}

}

TaskSchedulerBase ponechá klíčové metody a vlastnosti abstraktní, protože zpracování tásků mohou řešit jen odvozené třídy, ale sama nabídne podporu pro ukončení činnosti scheduleru v mětodě Dispose .
O metodě Dispose v Schedulerech ještě budeme mluvit, protože deterministické ukončení činnosti scheduleru je pro některé scénáře klíčové, ale zde jen shrnu.
TaskSchedulerBase garantuje, že metoda Dispose bude volána jen jednou. Metoda Dispose  - jako jedno z mála míst v knihovně – používá kritickou sekci (lock). Metoda převede Task ve vlastnosti Complete do stavu “dokončen” bez ohledu na to, jestli chráněná metoda Dispose v odvozených třídách proběhne bez problémů, nebo jestli dojde k vyvolání výjimky, takže libovolný kód v aplikaci, který závisí na informaci, že nějaký scheduler dokončil svou činnost, může pokračovat, i když došlo k výjimce. Metoda Dispose také stornuje CancellationToken, aby i další kód v odvozených třídách mohl reagovat na ukončení činnosti scheduleru.

Základní rozhraní a třídy máme, obešli jsme i některá striktní omezení v TPL a je načase začít psát specializované schedulery. Dnes si ještě ukážeme jen primitivní CurrentThreadScheduler, u kterého pojmenování naznačuje, že všechny tásky budou vždy vykonány ihned a v aktuálním threadu. Již v dalším díle nás ale čeká zajímavý a užitečný IoServiceScheduler.

CurrentThreadScheduler je třída na pár řádků, ale už alespoň  nejde o abstraktní třídu,  a my si jejím napsáním ověříme, že naše stávající infrastruktura funguje.

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

namespace RStein.Async.Schedulers

{

public class CurrentThreadScheduler : TaskSchedulerBase

{

private const int MAXIMUM_CONCURRENCY_LEVEL = 1;

public override int MaximumConcurrencyLevel

{

get

{

checkIfDisposed();

return MAXIMUM_CONCURRENCY_LEVEL;

}

}

public override void QueueTask(Task task)

{

checkIfDisposed();

ProxyScheduler.DoTryExecuteTask(task);

}

public override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)

{

checkIfDisposed();

ProxyScheduler.DoTryExecuteTask(task);

return true;

}

public override IEnumerable<Task> GetScheduledTasks()

{

checkIfDisposed();

return Enumerable.Empty<Task>();

}

protected override void Dispose(bool disposing)

{

}

}

}

Náš “reálný” scheduler s názvem CurrentThreadScheduler v metodách TryExecuteTaskInline a QueueTask spustí tásk s využitím ProxyScheduleru.
Metoda GetScheduledTasks vrátí prázdnou kolekci tásků, protože žádné tásky v metodě QueueTask neskladujeme.

I když jde o jednoduchý scheduler, měli bychom mít testy, které ověří, že se scheduler chová podle našich představ.

Nejprve CurrentThreadScheduler instanciujeme a předáme ho ProxyScheduleru.

protected override ITaskScheduler Scheduler

{

get

{

return m_scheduler;

}

}

protected override IProxyScheduler ProxyScheduler

{

get

{

return m_proxyScheduler;

}

}

public override void InitializeTest()

{

m_scheduler = new CurrentThreadScheduler();

m_proxyScheduler = new ProxyScheduler(m_scheduler);

base.InitializeTest();

}

......................

//Base tests

public TaskFactory TestTaskFactory

{

get

{

return m_testTaskFactory;

}

}

public override void InitializeTest()

{

m_testTaskFactory = new TaskFactory(ProxyScheduler.AsTplScheduler());

base.InitializeTest();

}

Metoda ProxyScheduler.GetTplScheduler() je využita k vytvoření instance TaskFactory z TPL, která nyní bude - nepřímo a aniž by si toho byla vědoma - používat ke spuštění tasků náš CurrentThreadScheduler.

A tady jsou testy:
Jestliže vytvoříme jeden tásk, tento tásk musí být vyřízen.

[TestMethod]

public async Task WithTaskFactory_When_One_Task_Is_Queued_Then_Task_is_Executed()

{

bool wasTaskExecuted = false;

await TestTaskFactory.StartNew(() => wasTaskExecuted = true);

Assert.IsTrue(wasTaskExecuted);

}

Když vytvoříme více tásků (v tomto testu jich je 8096), musí být všechny tásky vyřízeny.

[TestMethod]

public async Task WithTaskFactory_When_Tasks_Are_Queued_Then_All_Tasks_Are_Executed()

{

const int NUMBER_OF_TASKS = 8096;

int numberOfTasksExecuted = 0;

var tasks = Enumerable.Range(0, NUMBER_OF_TASKS)

.Select(_ => TestTaskFactory.StartNew(() => Interlocked.Increment(ref numberOfTasksExecuted))).ToArray();

await Task.WhenAll(tasks);

Assert.AreEqual(NUMBER_OF_TASKS, numberOfTasksExecuted);

}

Další testy ověřují charakteristiky, které by měl mít každý náš ITaskScheduler. Jedná se hlavně o ověření, že třída dodržuje doporučení  “odlehčeného” Dispose idiomu .

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

public void QueueTask_When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

var dummyTask = new Task(() => {});

Scheduler.Dispose();

Scheduler.QueueTask(dummyTask);

}

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

public void TryExecuteTaskInline_When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

var dummyTask = new Task(() => {});

Scheduler.Dispose();

Scheduler.TryExecuteTaskInline(dummyTask, false);

}

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

public void GetScheduledTasks_When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

Scheduler.Dispose();

Scheduler.GetScheduledTasks();

}

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

public void MaximumConcurrencyLevel_When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

Scheduler.Dispose();

var maximumConcurrencyLevel = Scheduler.MaximumConcurrencyLevel;

}

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

private void SetProxyScheduler__When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

Scheduler.Dispose();

Scheduler.ProxyScheduler = null;

}

[TestMethod]

[ExpectedException(typeof (ObjectDisposedException))]

private void GetProxyScheduler__When_TaskScheduler_Disposed_Then_Throws_ObjectDisposedException()

{

Scheduler.Dispose();

var proxyScheduler = Scheduler.ProxyScheduler;

}

[TestMethod]

public void Dispose_Repeated_Call_Does_Not_Throw()

{

Scheduler.Dispose();

Scheduler.Dispose();

}

[TestMethod]

public void Dispose_Does_Not_Throw()

{

Scheduler.Dispose();

}

}

A to je dnes skutečně vše.
V další části uvidíme nejen slibovaný IoServiceScheduler, ale ukážeme si, že IoServiceScheduler má speciální chování, které musí být pokryto mnohem robustnějšími testy.

Monday, 26 May 2014 09:51:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  


 Saturday, 23 March 2013
Záznam přednášky z MS Festu 2012 - Dependency injection v .NET bez pověr, iluzí a frikulínského nadšení

 

Pro ty z vás, kdo jste se mě ptali na záznam přednášky z MS Festu, mám (snad dobrou) zprávu. Záznam přednášky je od tohoto týdne dostupný na webu WUG.  O “snad dobré” zprávě píšu proto, že jsme sám nenašel odvahu se na sebe podívat.Smile

http://www.wug.cz/zaznamy/125-MS-Fest-2012-DI-v-NET-bez-pover-iluzi-a-frikulinskeho-nadseni

Materiály k přednášce.



Saturday, 23 March 2013 07:25:46 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | C# | Návrhové vzory


 Monday, 03 December 2012
Prezentace z přednášky na MS festu 2012 - DI v .NET bez pověr, iluzí a frikulínského nadšení

 

Tomáše Herceg & comp. opět po roce uspořádali další ročník konference MS Fest. A musím hned dodat, že z mého pohledu velmi povedený MS Fest, jehož organizace nikde neskřípala a na kterém jsme se cítil příjemně. Tímto organizátorům ještě jednou děkuju za skvělou organizaci konference a za veškerý servis, který poskytovali účastníkům konference i přednášejícím.

Na MS Festu jsme měl přednášku nazvanou Dependency injection v .Net Frameworku bez pověr, iluzí a frikulínského nadšení.
Sice jsem se jako každý rok po domluvě s organizátory na tématu přednášky dodatečně zděsil, že na přednášku mám jen 60 minut, a těsně před konferencí se stovkami účastníků jsme musel nahodit svůj přídavný a životní energii rychle spalující extrovertní pohon, ale samotná přednáška probíhala oproti minulému roku poklidně. Nemyslete si, já  teprve po minulém ročníku MS Festu dovedu ocenit, jaké je to blaho,  když s vámi v půlce probíhající  přednášky nezačne zuřivě diskutovat oponent z Nokie. Smile

Nabízel jsem tyto přednášky.

Tomáš Herceg mi původně v programu navrhl dvě přednášky, ale já jsme měl čas jen na přípravu jedné přednášky a vybral jsem tu, která dostala nejvíce hlasů.

Nevím, jaká je poptávka po pokročilejších/hard core přednáškách. Jak jsem psal na Twitteru, sám bych raději přednášel o “Task parallel library  pro pokročilé”, ještě raději o skrytých pokladech v RX Frameworku, ale RX si již dříve zamluvil Jarda Jírava. Bavilo by mě také přednášet o specialitkách typu dynamic, mohli bychom se pobavit o klíčových slovech async/await v netradičních kontextech, nebo bychom mohli napsat dalšího hostitele .Net Frameworku. To je alespoň malý výběr z témat, která mi jsou blízká, protože jsem podobné  vývojářské specialitky řešil pro různé firmy u nás i v zahraničí. Nevím, jaká by ale byla po těchto tématech na MS Festu poptávka, protože povídat si v potemnělé posluchárně jen pro sebe nebo prezentovat pro maximálně deset dalších  lidí má své kouzlo, ale - při vší úctě - intimní atmosféru mám raději s jinými než vývojářskými  kulisami a aktéry. Smile

Slíbená prezentace:

Doprovodný kód je na Bitbucketu:

Díky za to, že jste na mou přednášku přišli. A děkuju za hodnocení přednášky, moc jste mě potěšili. Smile

 

P .S. Ještě málá terminologická poznámka, proč používám slovo “kontajner”, a ne kontejner, na což se mě ptal už Tomáš Herceg po zaslání anotace.

Oficiálně [myšleno  - slovo kontajner žádný speciální význam] nemá – dokonce myslím ÚJČ slovo kontajner ani neuznává.

Viděl jsem, že se ale v ČR slovo kontajner docela vžilo a snažím se jeho použitím odlišit od konotací „kontejneru“ – u kterého mnoho lidí vidí spojitost s odpadky.:)

P. P. S. A ještě děkuju Alešovi Roubíčkovi za to, že si ochotně pročetl mou prezentaci a upozornil mě na místa, která by si zasloužila nějaké upřesnění.

P.P.P.S. Mrzí mě jediná věc. Na svých přednáškách většinou nezvětšuju písmo, na této přednášce jsem písmo ve Visual Studiu zvětšil přesně tak, jako to měl Tomáš Herceg na první přednášce, a při výkladu mě dost mátlo, že na obrazovce je vidět málo kódu, i když jsem měl z domova vyzkoušeno, že by kód měl být vidět bez problémů. Místo toho,  abych VS přepnul na celou obrazovku, tak jsem skroloval a v duchu se divil, proč je toho vidět tak málo. Takové momentální okno přednášejícího, který zapomněl, co udělal s fontem o přestávce. A ještě – poté, co mi spadl mikrofon a já si jej znovu nasadil, tak prý bylo slyšet hlavně v zadních řadách praskání a jiné pazvuky. Já jsem bohužel nic neslyšel a nikdo z posluchačů neprotestoval.



Monday, 03 December 2012 13:25:14 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | C# | Entity Framework | Návrhové vzory


 Wednesday, 26 September 2012
Pozvánka na mé kurzy v prosinci 2012 a lednu 2013 (Update 4. 12. 2012)

 

Aktualizace 4. 12. 2012 – kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 je zcela obsazen.

Opět bych vás rád pozval mé kurzy. Jak jste si asi všimli, tento rok “podzimní” termíny kurzů vyhlašuju kvůli různým peripetiím o něco později, takže místo podzimu se s některými z vás uvidím netradičně až v zimě. Snad to nevadí.

Také již tradičně připomenu, že je možné si objednat inhouse (ve vaší firmě uskutečněnou) variantu těchto kurzů i se domluvit na zcela jiné osnově vystavěné z témat, o kterých něco vím a jejichž výběr naleznete na mých stránkách. Všechny dotazy k veřejným i inhouse kurzům rádi zodpovíme na emailu rene@renestein.net (můj email) nebo na emailu petra@renestein.net (Petra Steinová, která rychleji a lépe než já odpoví na dotazy týkající se organizace veřejných i inhouse kurzů).

Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1  - kurz je obsazen

Datum konání kurzu: 10. - 12. 12. 2012

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé a studené nápoje. V ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu

Výběr z ohlasů na kurzy

FAQ - často kladené dotazy ke kurzům


Veřejný kurz Základy objektově orientovaného návrhu a vývoje (UML 0)

Datum konání kurzu: 14. - 16. 1. 2013

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé a studené nápoje. V ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu

Výběr z ohlasů na kurzy

FAQ  - často kladené dotazy ke kurzům

 


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 2

Datum konání kurzu:  21. - 23. 1. 2013

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé a studené nápoje. V ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu

Výběr z ohlasů na kurzy

FAQ - často kladené dotazy ke kurzům

Těším se na shledání na kurzu.



Wednesday, 26 September 2012 12:53:24 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | Ostatní


 Tuesday, 20 March 2012
Pozvánka na kurz objektových principů a návrhových vzorů – jaro 2012 a informace k dalším kurzům

Opět bych vás chtěl pozvat na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1.


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  4. 6.6. 6. 2012

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé i studené nápoje. V ceně kurzu jsou obědy v restauraci.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz

Zde jsou ještě některé ohlasy z twitteru na  kurzy, které proběhly na podzim roku 2011:
https://twitter.com/#!/AugiCZ/status/129271721512538112

https://twitter.com/#!/topascz/status/129228333991989248

https://twitter.com/#!/petrkucera/statuses/129474672575250432


FAQ - často kladené dotazy ke kurzům


Tento rok se na jaře uskuteční pouze výše popsaný kurz. Dva další kurzy, Školení Základy objektově orientovaného návrhu a vývoje (UML 0) a Pokročilé návrhové vzory a objektové principy 2, proběhnou na podzim a můžete se na ně také  předběžně hlásit. Důvodem, proč tyto kurzy neproběhnou na jaře, je moje vytížení dalšími projekty.

Pro jistotu připomenu, že všechny kurzy lze také objednávat v “inhouse” variantě, kdy kurz proběhne ve vaší firmě v termínech, na kterých se spolu domluvíme, a za podmínek, které s vámi ráda dohodne Petra Steinová. V inhouse variantě je také samozřejmě možné zcela upravit program kurzu  a věnovat se jen “specialitkám” a “špekům”, které v aplikaci řešíte.

Budu se těšit na záludné dotazy na kurzu.Smile



Tuesday, 20 March 2012 09:28:04 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Monday, 12 March 2012
Lazy loading (zpožděné nahrávání) objektů do kolekce i ve starší aplikaci s využitím dynamické proxy

 

Jestliže používáte i přes jeho nezralost Entity Framework, nebo jste si zvolili jiné ORM, které zvládá “lazy loading”, neboli zpožděné, či chcete-li dodatečné nahrání dat do typových kolekcí, možná jste přemýšleli, jak byste stejnou  službu napsali ve starší aplikaci, která žádné ORM nepoužívá, nebo v hybridní aplikaci, pod kterou si představuju aplikaci, jejíž starší moduly ORM nepoužívají, ale novější moduly již s přístupem přes ORM počítají. I bez ORM byste ale často v aplikaci rádi využívali některé vychytávky, které s sebou přináší ORM. Dnes chci ukázat, že zpožděné nahrávání kolekcí není žádná magie, která by bez ORM byla v aplikaci zapovězena. Když budete mít zájem, můžeme v dalších článcích probrat například i automatickou detekci změn vlastností na objektech a s tím související ukládání objektů i odvolávání proběhlých změn, jestliže zákazník nechce změny uložit a třeba na formuláři stiskne po deseti minutách zuřivé editace objektu a po několika masivních business transakcích, které pozmění desítky objektů najednou, tlačítko Storno.

Co si představit pod zpožděným nahráním  objektů v kolekci? Můžeme vyjít z již zlidovělé třídy Objednávka (Order), která má kolekci svých položek (kolekce Items). Místo nahrání všech položek objednávky z databáze ihned po vytvoření instance objednávky, odložíme nahrání položek až na dobu, kdy budou v aplikaci poprvé potřeba. Před klienty třídy Order ale tuto optimalizaci skrýváme tak, že kolekci Items naplníme položkami teprve při prvním přístupu ke kolekci přes veřejné rozhraní třídy Order.

Jde sice o triviální kód, kdy v get akcesoru kontrolujeme bool příznak m_itemsIsLoaded (byly položky nahrány?) a při prvním přístupu ke kolekci zavoláme metodu loadItems, ale představte si, že tento kód u starší aplikace zběsile doplňujete ke každé kolekci v každé třídě, kde teď stojíte o zpožděné nahrávání. Čitelnost kódu je citelně snížena a chrabří obhájci principu jedné odpovědnosti třídy (SRP) právě dopisují Kladivo na heretiky S.O.L.I.Dní víry pravé a připravují v zájmu lepší veřejné vývojářské morálky autodafé účtu dotyčného vývojáře na všech významných sociálních sítích.

Můžeme se rozhodnout, že nebudeme zatěžovat zpožděným nahráváním kolekcí přímo třídu Objednávka, ale že vytvoříme proxy třídu, do které odpovědnost za tuto “infrastrukturní službu” vložíme. Proxy objekt je, jak známo, objekt, který má z hlediska klienta stejné rozhraní jako původní objekt a klient si není vědom, že pracuje s instancí zástupce (surogátem) třídy Order, a ne s originální třídou Order.

Samotná třída Objednávka není nijak zatěžována znalostí, že její kolekce Items je nahrána až při prvním přístupu ke kolekci. Zpožděné nahrávání zvládne ale potomek třídy Order, třída OrderProxy, ve které je podobný kód, který se původně nacházel v objednávce. A protože platí, že potomek nějaké třídy může být v aplikaci používán na všech místech, kde je očekáván předek, můžeme například z repozitáře/identitní mapy objektů začít ihned vydávat klientům instance OrderProxy místo originální třídy Order.

Odpovědnosti už jsme rozdělili lépe, protože třída Order není zatěžována zpožděným nahráváním kolekce a jediným důvodem existence třídy OrderProxy je právě zpožděné nahrávání. Přesto stále platí, že budeme do úmoru psát další a další třídy Proxy, u nichž  jediná kreativní vývojářská činnost spočívá v pojmenování bool proměnné, která nám sděluje, jestli kolekce byla, nebo nebyla nahrána. Je možné si práci zjednodušit tím, že například vytvoříme T4 šablonu, která proxy třídy vygeneruje, ale my se dnes zaměříme na to, jak vytvořit proxy třídu, aniž bychom museli psát proxy ručně nebo spoléhat na T4 šablony.

Použijeme takzvanou dynamickou proxy, kterou si lze představit jako nástroj, kterému řekneme, co má proxy dělat, a on pro každou třídu bez ohledu na unikátní rozhraní každé třídy sám vygeneruje proxy, která pro tuto třídu implementuje námi vyžadované chování. Slovo “dynamická” u proxy vyjadřuje hlavně to, že jde o proxy generovanou automaticky za běhu aplikace! V našem konkrétním scénáři se zpožděným nahráváním objektů vytvoříme i pro aplikaci, která má v business vrstvě stovky i  tisíce tříd jen jednu další třídu, která představuje “deskriptor” pro každou proxy zajišťující  zpožděné nahrávání kolekcí, přičemž platí, že tento “deskriptor“  bude schopen obsloužit všechny kolekce ve všech třídách. Sice se pro každou třídu vytvoří unikátní proxy (potomek originální třídy), ale tato proxy se bude odkazovat na obecný scénář zpožděného nahrávání v našem “deskriptoru”. Při čtení dalších částí článku mějte na paměti, že kód dělá stále to samé, jako námi vytvořená třída OrderProxy výše, jen jsme kód zobecnili tak, abychom si dokázali vynutit zpožděné nahrávání kolekcí ve všech třídách. Vedlejším a nepříjemným důsledkem tohoto zobecnění, jak je tomu asi u každé abstrakce,  je samozřejmě snížená schopnost jen po letmém prolétnutí infrastrukturního kódu očima poznat, oč přesně usilujeme. Výhodou však bude to, že náš deskriptor oddálí zmiňované autodafé vývojáře už jen tím, že dodržuje princip DRY-Don't repeat yourself – místo psaní jen mírně obměněného kódu v každé konkrétní proxy jednorázově vyjádříme náš záměr zpožděně nahrávat kolekce v “deskriptoru” pro generování dynamické proxy.

Mějme třídy Customer, Order a OrderItem. Zajímá nás hlavně to, že třída Customer má kolekci objednávek a třída Order kolekci položek objednávky. Pro všechny kolekce chceme doplnit zpožděné nahrání kolekcí.
Všimněte si také toho, že vlastnosti s kolekcemi jsou virtuální. Stále platí, že dynamická proxy je potomkem naší třídy a musí být schopna přepsat implementaci a doplnit kód pro zpožděné nahrání kolekce stejně, jako jsme přepisovali get akcesor u “manuální” proxy výše.

I když je mnohem zábavnější napsat si podporu pro dynamicky generované proxy sám, ne vždy bychom měli vynalézat na projektu kolo, zvláště když jsme v časovém presu  a navíc pod drábovou knutou nudných projektových manažerů surově lámajících naše vývojářská křídla Smile , a alespoň v tomto článku použijeme výborný a ověřený nástroj pro generování dynamických proxy z projektu Castle. Nejjednodušší způsob přidání knihovny pro generování dynamických proxy spočívá v instalaci přes NuGet. V Powershell konzoli ve Visual studiu zadejte příkaz.

Vytvoření dynamické proxy v Castlu je kupodivu otázkou napsání  jednoho řádku kódu. My služby Castlu zapouzdříme do naší vlastní třídy ProxyEngine.

Do konstruktoru třídy ProxyEngine dostáváme instanci třídy SimpleObjectFactory, kterou si můžete prozatím zjednodušeně představit jako velmi jednoduchou generickou továrnu na výrobu business objektů, která zároveň funguje jako identitní mapa. Její kompletní výpis naleznete níže v tomto článku.

Hlavní je pro nás metoda AddDefaultProxy, která dostává jako první argument typ, pro který má být vytvořena proxy. Tedy předáte-li typový deskriptor objednávky, metoda by měla vyrobit OrderProxy. Druhým argumentem jsou argumenty, které mají být předány konstruktoru naší třídy. Jestliže objednávka vyžaduje v konstruktoru odkaz na své id, vygenerovaná proxy třída garantuje, že jí id bude do konstruktoru předáno.

Teď přichází zajímavá část – vytvoření proxy:

Proměnná m_proxyGenerator je instancí třídy ProxyEngine z Castlu, která představuje výkonné jádro pro generování proxy. Prvním argumentem metody proxyGenerator.CreateClassProxy je typ, pro který chceme proxy vytvořit. Druhý argument typu ProxyGenerationOptions jsou různé volby, které dovolují jemně řídit, jak se bude vytvořená proxy chovat. My zatím potřebujeme jen sdělit, které metody originální (ne proxy) třídy chceme v proxy “přepsat”. Proto jsou ProxyGenerationOptions inicializovány hned v konstruktoru třídy ProxyEngine a je jim předána instance třídy ProxyGenerationHook, která, jak ihned ve výpise uvidíme, vybírá kolekce, u kterých má být podporováno zpožděné nahrání objektů.

Rozhraní IProxyGenerationHook je rozhraní Castlu. Metoda ShouldInterceptMethod z tohoto rozhraní je metoda, kterou Castle používá k rozhodnutí, jaké metody a vlastnosti mají být “přepsány” v dynamické proxy. Třída ProxyGenerationHook v metodě ShouldInterceptMethod říká  - tedy vrací true - , že chceme zachytit všechny get akcesory ( methodInfo.IsSpecialName&& methodInfo.Name.StartsWith(GET_METHOD_NAME_PREFIX), jejichž návratovou hodnotou je kolekce. Přesněji řečeno každá kolekce podporující generické rozhraní ICollection<T> (methodInfo.ReturnType.GetInterface(COLLECTION_NAME) != null). Jiných metod ani vlastností si v tomto článku u tříd  nevšímáme, a proto pro ně z metody ShouldInterceptMethod vrátíme false. Zajímavou metodou v rozhraní IProxyGenerationHook je i metoda NonVirtualMemberNotification, pomocí níž nás Castle informuje, že v originální třídě je nevirtuální metoda – my metodu NonVirtualMemberNotification nevyužíváme, ale mohli bychom do ní snadno doplnit kód, který vyhodí výjimku, jestliže jste Castlem notifikováni, že existuje nevirtuální vlastnost vracející ICollection<T>, protože vaše firemní konvence vyžadují, aby všechny kolekce podporovaly zpožděné nahrávání kolekce.

Nyní jsme již Castlu sdělili, že máme zájem “přepsat” get akcesory kolekcí, ale stále Castle neví,  jakou logiku má do těchto get akcesorů doplnit. Vraťme se k metodě CreateClassProxy. Třetí argument je zřejmý, předáváme argumenty, se kterými má být zavolán konstruktor naší originální třídy. Posledním argumentem metody CreateClassProxy je objekt, který nás zajímá nejvíce – jedná se o tzv. interceptora, který bude použit vždy, když je na proxy použita  metoda/vlastnost, kterou chceme “přepsat”. Stále píšeme obecné řešení zpožděného nahrávání kolekcí, a proto metodě CreateClassProxy předáme interceptora s výmluvným názvem LazyLoadInterceptor. LazyLoadInterceptor je ten “zázračný” typ, který jsem výše popisoval jako obecný deskriptor funkcí, které musí podporovat dynamická proxy pro instanci z každé třídy v business vrstvě.

Náš LazyLoadInterceptor, stejně jako každý jiný interceptor, musí podporovat rozhraní  IInterceptor z Castlu. Rozhraní IInterceptor má jedinou metodu Intercept, kterou Castle zavolá vždy, když je volána metoda/vlastnost, kterou chceme “přepsat”.

Metodě Intercept je předán objekt Invocation, který nese základní informace o volané metodě.Kromě dalších vlastností je vhodné si zapamatovat, že v invocation.Method naleznete objekt MethodInfo  (deskriptor metody) a  v InvocationTarget zase konkrétní instanci, na které je metoda volána. Zdůrazním, že touto konkrétní instancí je v našem případě (dynamický) proxy objekt, ne instance originální třídy.

Zkusme si scénář v metodě Intercept projít. Mějme na paměti, že i když ten kód může vypadat na první pohled děsivě, neřeší nic jiného než ručně napsaná proxy výše. Zkusme se v našem popisu pro názornost zaměřit na konkrétní proxy objektu reprezentujícího zákazníka Josefa Nováka v momentě, kdy je poprvé přistoupeno k jeho kolekci Orders (seznam objednávek), i když kód v LazyLoadInterceptoru funguje analogicky ve všech dalších proxy business tříd v systému.

  • Metoda Intercept nejprve na předaném objektu invocation volá metodu Proceed.  Metoda Proceed vyvolá get akcesor originálního objektu a návratovou hodnotu (při prvním volání prázdnou typovou kolekci objednávek) nalezneme ve vlastnosti invocation.ReturnValue. Proč voláme nejprve invocation.Proceed? Protože potřebujeme v interceptoru kolekci, do které u zákazníka můžeme nahrát objednávky, a tuto kolekci stále spravuje instance originální třídy, jak si můžete ověřit ve výpisu třídy Customer.
  • Jestliže se nejedná o první volání metody, nic neděláme, protože kolekce už musí být naplněna. V proměnné m_inspectedMethods máme názvy vlastností, které jsme již u daného objektu zpracovali.
  • Jestliže invocation.ReturnValue je null, opět nic dalšího neděláme. Nemáme žádnou kolekci, do které bychom mohli nahrát objednávky.
  • Do kolekce m_inspectedMethods přidáme název aktuální vlastnosti (Orders), protože jsme ji již začali zpracovávat.
    m_inspectedMethods.Add(invocation.Method.Name);

  • Nejprve potřebujeme zjistit, z jaké třídy pocházejí objekty,  které budeme do kolekce, jejíž data nahráváme, přidávat. U objektu zákazník a kolekce Orders půjde samozřejmě o objekty z třídy Objednávka.
    Type collectionItemType = invocation.Method.ReturnType.GetInterface(COLLECTION_INTERFACE_NAME).GetGenericArguments()[0];
  • Dohledáme třídu z databázové vrstvy, která nám bude schopna vrátit seznam objednávek pro daného zákazníka.
    Object dbComponent = findDbComponent(collectionItemType);
    Pro účely článku je zvolena jednoduchá jmenná konvence -  rozhraní pro přístup k databázi se jmenují vždy I<Název třídy>DbComponent. Pro objednávku tedy hledáme typ IOrderDbComponent. Pokud db komponentu nenalezneme, nic dalšího nemůžeme dělat.
  • V nalezené db komponentě musíme najít metodu, která nám vrátí záznamy pro všechny objednávky zákazníka Josefa Nováka. 
    MethodInfo methodInfo = getDbCollectionMethodInfo(dbComponent, invocation.TargetType, collectionItemType);
    Opět je zvolená jmenná konvence, kdy metoda má tvar Get{TypeInCollection}RecordsBy{ParentType}Id" a přijímá jeden argument typu int . V našem scénáři hledáme tedy na db komponentě metodu GetOrderRecordsByCustomerId, která přijímá id “rodičovského” zákazníka. V dalších článcích bych rád ukázal, jak se bez této i dalších dále zmíněných jmenných konvence obejdeme a budeme moci nakonfigurovat zpožděné nahrávání kolekcí přes jakkoli nazvané třídy a metody.
    Stejně jako v předchozím bodě platí, že nenalezneme-li vyhovující metodu, nic dalšího nemůžeme dělat.
  • Dále u objektu zákazník získáme hodnotu vlastnosti Id, kterou potřebujeme pro vyvolání metody na db komponentě nalezené v předchozím odstavci
      int? objectId = getTargetObjectId(invocation.InvocationTarget);
  • Zavoláme metodu GetOrderRecordsByCustomerId. Návratovou hodnotou je objekt DataTable, který v našem scénáři obsahuje záznamy všech objednávek patřících Josefu Novákovi.
    DataTable retValues = methodInfo.Invoke(dbComponent, new object[] {objectId}) as DataTable;
  • Přes pomocnou třídu SimpleObjectFactory, kterou LazyLoadInterceptor vyžaduje v konstruktoru, vytvoříme proxy objekty Objednávek a přidáme je do kolekce Orders zákazníka.
    var targetCollection = invocation.ReturnValue;
    addItemsToCollection(targetCollection, collectionItemType, retValues, invocation);
    Navíc se u každé vytvořené objednávky pokusíme nastavit odkaz na  “rodičovského zákazníka”, přesněji řečeno na proxy zákazníka. Opět je zvolena jmenná konvence, kdy objekt Order musí obsahovat vlastnost nazvanou Customer, jinak k nastavení “rodiče” nejde. Jak jsem již psal výše, v dalších článcích bychom si měli ukázat, jak tyto výchozí jmenné konvence rozšíříme a dovolíme i jejich úplné nahrazení.
  • Hotovo, kolekce Orders u zákazníka Josefa Nováka je naplněna proxy objekty třídy Order a stejný scénář proběhne i při přístupu ke kolekci Items (položky objednávky) u každé objednávky.

Nyní můžeme vyzkoušet, jestli jsou proxy třídy generovány a hlavně jestli naše úsilí nebylo marné a proxy třídy podporují zpožděné nahrávání kolekce.

 

consoleProxy

Výsledkem by měl být tento výpis, ze kterého je patrné :

  • Místo originální třídy jsou používány proxy třídy.
  • Kolekce jsou naplněny, i když ve třídách Customer ani Order žádný kód pro nahrání kolekce nemáme.
  • Je naplněna kolekce Orders u zákazníka i kolekce Items u každé objednávky.

Následuje slibovaný výpis generické třídy SimpleObjectFactory. 

Třída SimpleObjectFactory podporuje rozhraní SimpleObjectFactory a při vydání objektu:

  1. Funguje jako identitní mapa, takže každý objekt je nahrán jen jednou (nyní per process, což se dá snadno změnit).
  2. Používá záměrně služby “zastaralé” db vrstvy pro nahrání dat každého objektu – vytváření objektů ale deleguje na ProxyEngine.
  3. Pokusí se nastavit hodnoty jednoduchých vlastností u vytvořených proxy objektů – jestliže se název vlastnosti shoduje s názvem sloupce v datovém zdroji, je vlastnost objektu nastavena na hodnotu sloupečku, která je uložena v řádku  vytaženém z databáze.

Co můžeme udělat dále v dalších článcích, jestliže budete mít zájem:

  1. Nebudeme spoléhat na jmenné konvence při plnění kolekci a “rodičovských” vlastností, ale dovolíme nakonfigurovat interceptora tak, abychom mohli spravovat asociace mezi třídami podle konvencí unikátních pro každý projekt, a přitom abychom nemuseli do těchto nízkoúrovňových proxy služeb moc zasahovat. Konfiguraci provedeme nejlépe pomocí fluentního API.
  2. LazyLoadInterceptor nebude používat stále dokola reflexi pro dohledání typů a metod, ale bude nalezené hodnoty cachovat.
  3. Budeme schopni podpořit i zpožděné nahrávání “rodičovských” vlastností. Prozatím je “rodičovská” vlastnost nastavena jen při nahrání kolekce – když vytáhnete z databáze jako první objednávku a sáhnete na její vlastnost Customer, vlastnost vám nyní vrátí null!
  4. Mohli bychom rozšířit proxy třídy o sledování změn vlastností a umožnit u každého objektu uložení změn nebo vrácení změn (undo).
  5. Zavedeme repozitáře (Repository), které i v hybridní aplikaci sjednotí ve vyšších vrstvách aplikace přístup k objektům, které jsou nahrány přes ORM i k objektům vytaženým z našich stávajících “old school” db/business služeb.
  6. Místo toho, abychom generovali dynamické proxy vždy po startu aplikace, umoříme jednorázově (pro většinu aplikací stejně zanedbatelnou) režii spojenou s tímto postupem vygenerováním a uložením assembly s dynamickými proxy při prvním spuštění nové verze aplikace. Při dalším spuštění aplikace se již použijí proxy ve vygenerované assembly.

Pro dnešek toho bylo ale myslím dost. Snad jen dodám, že jsem se v tomto článku chtěl vyhnout různým buzzwordům, ale fajnšmekrům potvrdím, co asi sami tuší, že jsme v tomto článku zavítali do hájemství AOP - aspektově orientovaného programování.

Celý projekt si můžete stáhnout, nejlépe přes Mercurial (hg). Součástí zdrojových kódů je i jednoduchá třída napodobující rozhraní tradičních “db komponent” pro přístup do databáze a zpřístupňující data v Datasetu.



Monday, 12 March 2012 14:06:49 (Central Europe Standard Time, UTC+01:00)       
Comments [11]  .NET Framework | Entity Framework | Návrhové vzory


 Friday, 24 February 2012
Entity Framework 4.3. Code First - (nechutný) problém s TPC mapováním?

 

Update 25. 2.2011: Tak chyba potvrzena EF týmem. Jedná se skutečně o chybu, která je částečně popsána v known issues.

Diego B Vega : @Rene Stein: Thanks for reporting this and for the repro. What you describe seems to be a bug in TPC mapping that we are already aware of and that we are planning to fix in the upcoming EF 4.3.1. Please take a look at the list of known issues above for more information.

Jedná se tedy o chybu, kterou někdo zmínil i  v komentářích. Zarážející ale je, že k chybě “chybí sloupec v databázi” se dostanete teprve tehdy, kdy vygenerujete databázi s jiným než požadovaným schématem, odchytnete výjimku při dotazování a podíváte se na popisek vnořené výjimky. V “known issues” EF by spíš podle mě mělo být  - ve verzi 4.3 se vám ani nepodaří vygenerovat databázi s TPC mapováním dědičnosti a volání metody MapInheritedProperties  při konfiguraci entit je jen zbytečná dekorace v kódu a cvičení v marnosti.

Mohl by prosím někdo ověřit, že jsem buď udělal nějakou triviální chybu při mapování, anebo potvrdit mé podezření, že je EF Code First v poslední verzi 4.3 natolik prolezlý chybami,  že v něm nefunguje ani tento triviální scénář.

Problém se snažím reprodukovat na tomto kódu.

Mám třídy Base a Derived. Jejich role asi vysvětlovat nikomu nemusím.Smile

Snažím se pro mapování třídy Derived do databáze použít v db kontextu strategii TPC – table per (concrete) class (metoda MapInheritedProperties). 

Po spuštění se aplikace vytvoří databáze se dvěma tabulkami. Struktura databáze ale odpovídá TPT strategii pro mapování dědičnosti:

Tabulka Base má sloupce Id a BaseProperty, tabulka Derived Id a Note. Volání MapInheritedProperties je tedy zcela ignorováno.

 

EFTables

Jak popisuju i v kódu, matoucí je to, že Entity Framework sice mapuje třídy do databáze podle TPT strategie, ale dotazy klade, jako kdyby v databázi byly třída Derived namapována TPC strategií.
Vygenerovaný SQL dotaz do tabulky Derived vypadá takto:

SELECT '0X0X' AS [C1], [Extent1].[id] AS [id], [Extent1].[BaseProperty] AS [BaseProperty], [Extent1].[Note] AS [Note] FROM [dbo].[Derived] AS [Extent1]

Schizofrenní Entity Framework se beze všech skrupulí snaží dohledat v tabulce Derived sloupec BaseProperty (TPC mapování), což pochopitelně skončí výjimkou při vykonávání dotazu, protože se jiná část jeho vícečetné osobnosti složené z nespolupracujících spoluautorů EF rozhodla při generování databáze, že TPT je pro každého aplikačního vývojáře vždycky jasná volba.

A protože perverzních projevů EF se při pátečním večeru nelze nabažit, tak tady je skript pro založení databáze, který jsem vytáhl z podkladového ObjectContextu a který by měl mapovat podle TPC, což se ale nestane, protože je proti databázi spuštěn skript zcela jiný.

Výsledek Trace.WriteLine(((IObjectContextAdapter) context).ObjectContext.CreateDatabaseScript());

Projekt s reprodukcí problému ke stažení.



Friday, 24 February 2012 22:06:21 (Central Europe Standard Time, UTC+01:00)       
Comments [4]  .NET Framework | C# | Entity Framework | LINQ


 Tuesday, 06 December 2011
Demo z MS Festu

Několik lidí mi psalo na email, že by chtěli demo z první přednášky na MS Festu.

Zde je:

http://jdem.cz/sxs63

Podrobné informace, na které nebyl na přednášce čas, naleznete v sérii článků Tipy pro Windows Phone 7 aplikace v sekci o WP7 aplikacích.

Asi nemusím zdůrazňovat, že jde o demo, tedy kód nemusí mít produkční kvalitu, za nic neručím a určitě ke kódu není poskytována žádná podpora.Smile



Tuesday, 06 December 2011 15:17:03 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C# | Návrhové vzory | Silverlight | WP7


 Saturday, 26 November 2011
Prezentace z MS Festu 2011 a stručný komentář

Dnes jsem měl dvě přednášky na MS Festu.

Vývoj WP 7 aplikací pro pokročilé

Strasti a slasti vývoje wp7 aplikací. I Mango chutná hořko sladce.

Prezentace z přednášek naleznete níže.

Chci poděkovat lidem, kteří MS Fest připravují (klobouk dolů, v jakém počtu to zvládnou) a kteří mě oslovili a dovolili mi dnes přednášet. MS Fest se koná ještě zítra, pokud můžete, vyražte. Třeba přednášky o WCF RIA Services, PRISMu nebo přednáška o psaní testovatelného kódu budou určitě stát za výlet do Prahy.

Mně se jen potvrdilo, že na odbornou přednášku (ta první) je jedna hodina málo a na bulvární přednášku (ta druhá) až moc.Smile

Jsem moc rád, že jsem viděl známé tváře a že jsem se seznámil s mnoha zajímavými lidmi. Omlouvám se těm, kteří se ke mně ani o poslední přestávce nedostali, nebylo v mých silách jít ke každému  a zodpovědět všechny dotazy. A děkuji i za hromadné odpolední follow na twitteru, sice nevím, co to přesně znamená, ale to neznačí, že si toho nevážím. Smile

Také chci poděkovat pánovi z Nokie (omlouvám se, neslyšel jsem v tomu hluku vaše jméno),  díky kterému jsem si na přednášce procvičil asertivní chování.Smile

Pár dalších postřehů, abych nemusel odpovídat jednotlivě na Twitteru a na G+:

  1. Názory na druhou přednášku se liší, což se dalo čekat. Třeba https://twitter.com/#!/WindMobiDown/status/140475583082663936 X https://plus.google.com/104228058858941704434/posts/RxgW6HUtsib
    Takže malý komentář: Když jsem nabízel témata pro MS Fest, bylo zřejmé, že nebudu chválit. Na webu se dá dohledat dost mých vyjádření k WP7, ze kterých plyne, že ze současné podoby WP7 nejsem nadšený. Také jsem si uvědomoval, že na MS Fest se přednáška nemusí hodit – jedná se o akci zaštiťovanou Mícrosoftem a ani předpokládaní účastníci MS Festu nemusí být nadšeni. Ještě během podzimu jsem tyto své pochyby konzultoval s jedním z pořadatelů, s Tomášem Hercegem, ale shodli jsme se, že Microsoft určitě pár nekorektních slov na svou adresu přežije. Orel much nelapá a Microsoft podle mě nikdy moc přecitlivělý na kritiku nebyl.Smile 

  2. Přednášku jsem 3x přepracoval, většinu věcí vyházel a nakonec jsem ji nechal v nevážné podobě. I jsem avizoval, jak jsou přednášky postaveny, abych lidi odradil.

    https://twitter.com/#!/renestein/status/140185759683575808

    https://twitter.com/#!/renestein/status/139983752788049920


  3. Kdybych měl mít jen tu druhou přednášku na MS Festu a nebyla mi schválena první přednáška, ani ta druhá by nezazněla. Neměla by smysl. Počítal jsem s tím, že na obou přednáškách budou stejní lidé a že po přednášce, která o WP7 pojednává s plnou vážností, přijmou i tu, u které je i doprovodná prezentace zcela jiná. Na začátku jsem také zmínil, že kromě faktů k API WP7 pronesu subjektivní postřehy, nekorektní poznámky a domněnky, které mohou účastníci ignorovat. A uváděná fakta, jestliže mají lepší informace,  opravit.  Skoro na konci prezentace jsou v kontrapozici můj sarkastický pesimistický výrok a nadšený (nadsazený?) výrok Radka Hulána – i to jsem považoval za jasný signál, že prezentace je názorově jednostranná (kdo zná Radka, pochopil) a korektivem k ní byla první přednáška.

  4. Pokud to zaniklo, tak leitomotivem druhé přednášky bylo:”Otravujme jako vývojáři Microsoft, ať nám ve WP7 nehází drobky ze stolu, ale poskytne nám servis, na který jsme zvyklí.”

A ještě. Záznam z mých přednášek nebude. Bohužel informace o natáčení byla rozesílána až tento týden, navíc jsem  před přechodem na nový notebook a nechtěl jsem riskovat, že na můj již tak zkoušený stávající notebook nainstaluju nějaký SW, o kterém moc nevím. Mám špatnou zkušenost z instalace nějakého prezentačního SW, který mi na inhouse kurzu poté nedovolil přepnout výstup na projektor. Na veřejné a jen hodinové přednášce by to mohl být ještě větší průšvih. Ani mi to ale moc nevadilo, protože během tohoto podzimu se neustále potýkám se ztrátou hlasu a dalšími radostmi, takže jsem se bál, že můj výkon dotčený soustředěním na namáhané hlasivky bude navíc ještě zvěčněn na nějakém videu.Smile

Díky, že jste přišli!:)



Saturday, 26 November 2011 20:15:12 (Central Europe Standard Time, UTC+01:00)       
Comments [3]  Silverlight | WP7


 Tuesday, 30 August 2011
Připomenutí: Kurzy – podzim 2011

Pozvánka na mé kurzy na blogu trochu zapadla, a protože tento týden dvě firmy zvolily raději inhouse variantu kurzu, a tím se uvolnila místa na veřejných kurzech, i když předtím jsme museli na začátku srpna některé zájemce o veřejné kurzy OOP 0 a OOP 1 odmítat, dávám i pro další zájemce znovu odkaz na pozvánku.



Tuesday, 30 August 2011 11:48:54 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory | UML