niedziela, 26 marca 2017

Jak narysować coś w Xamarin.Forms

Hej,
dzisiaj kolejny problem jaki musiałem rozwiązać w formsach. Chciałem narysować ul. W zależności od jego typu, elementów i danych wprowadzonych przez użytkownika rysunek miał się różnić tak aby oddawać jego rzeczywisty stan więc wstawienie gotowego rastra odpadało. Schemat ula musi być więc wygenerowany w kodzie - niestety ograniczenia formsów znowu okazały się zbyt duże. Tak na prawdę musimy poprzestać na samym prostokącie, który możemy zrealizować np poprzez kontrolkę BoxView lub każdą inną, która ma właściwość BackgroundColor. W Adobe AIR sprawa byłaby prosta - większość kontrolek wystawia nam obiekt klasy Graphics, który pozwala na rysowanie wektorowe przeróżnych kształtów. Po, krótkim researchu odnalazłem jej odpowiednik dla Xamarina - po pierwsze NGraphics, które pozwala na obsługe wektorowej grafiki na każdej z platform korzystając z natywnych konrolek, a po drugie - NControl, która to biblioteka opakowuje pierwszą w kontrolkę, dziedziczącą po Xamarin.Forms.View. Sprawa jest o tyle przyjemna, że mając naszą biblioteke rysującą w postaci View - możemy nie naruszając architektury MVVM rysować i bindować do widoku nasz rysunek np poprzez:
<ContentView Content="{Binding BeeHiveDrawing}" />
całkowicie w naszym modelu widoku:
public Xamarin.Forms.View BeeHiveDrawing
{
    get
    {
        return _beeHiveDrawing;
    }
    set
    {
        _beeHiveDrawing = value;
        OnPropertyChanged(nameof(BeeHiveDrawing));
    }
}
Ok, przejdźmy więc dalej. Sama inicjalizacja naszej kontrolki jest standardowa  i opisana pod linkiem podanym powyżej. Jej przykładowe użycie wygląda następująco:
var nc = new NControlView((NGraphics.ICanvas canvas, NGraphics.Rect rect) =>
{
    canvas.DrawPath(new PathOp[] 
    { 
        new MoveTo(0, 0),
        new LineTo(rect.Width, rect.Height) 
    }, 
    new Pen("#ff0000", 3));
});

W konstruktorze NControlView podajemy Action<ICanvas, Rect> gdzie otrzymujemy nasz element - płachtę, po której rysujemy oraz jej finalne wymiary w postaci obiektu NGraphics.Rect. Mamy więc od czego zacząć - w następnym poście zaprezentuje jak poradziłem sobie z zadaniem, opisanym na początku - rysowaniem ula.

środa, 22 marca 2017

Mapy i geolokalizacja w Xamarin.Forms

Hej,
w moim projekcie, zaraz na początkowym etapie miałem potrzebę użycia map. Dokładnie potrzebowałem poprosić użytkownika o wskazanie lokalizacji pasieki - w przyszłości mam zamiar wykorzystać te dane do pokazywania użytkownikowi z jednego z dostępnych API  pogody na pasiece i w pewnych przypadkach pokazywać mu notyfikacje (moja główna wartość biznesowa). Na start trafiłem na przygotowany przez ekipę Xamarina bibliotekę Xamarin.Forms.Maps, której użycie i konfiguracja opisana jest tutaj.
Niestety szybko okazało się, że funkcjonalność oferowana przez ten pakiet jest uboga i ogranicza się głównie do kontrolki mapy, na której możemy pokazać nieinteraktywnego pina. Szybko trafiłem na bibliotekę, która bazując na w/w bardzo rozszerza jej funkcjonalność, a którą bardzo polecam: TK.CustomMap - z jej pomocą możemy dodawać customowe, przeciągalne piny, rysować na mapie kształty, wyznaczać trasy itp. Instalacja z nugeta jest bezproblemowa i po konfiguracji opisanej np na githubie możemy zacząć dodawać mapy.

Na potrzeby testów i ewentualnej zmiany używanej biblioteki dodałem sobie interfejs:
public interface IMap
{
    void showMap();
    Task showMapForLocation(Action<string> onUserSelectPoint, double currentLat = 0, double currentLng = 0);
}

Pierwsza metoda służy mi do pokazania mapy - zapewne będę ją rozbudowywał o dodatkową funkcjonalność, jednak do zabawy z mapami po prostu pokazuje okienko z mapą. W samej aplikacji korzystam na razie tylko z drugiej funkcji i na jej przykładzie omówię moje użycie biblioteki - pełna implementacja jest wrzucona na githubie.
Na początek tworzę obiekt mojej mapy z początkową lokalizacją gdzieś w Polsce (52,20):
var map = new TK.CustomMap.TKCustomMap(MapSpan.FromCenterAndRadius(new Position(52, 20), Distance.FromKilometers(100)));

Utworzony obiekt dodaję do wcześniej przygotowanego widoku, a następnie tworzę obiekt pina,  dodaje go do mapy, a po kliknięciu w mapę, przesuwam pina w te miejsce.
var pin = new TK.CustomMap.TKCustomMapPin { Position = map.MapCenter, IsDraggable = true, ShowCallout = true, Title = "Lokalizacja pasieki" };
map.CustomPins = new List<TK.CustomMap.TKCustomMapPin> { pin };
map.MapClicked += (object sender, TK.CustomMap.TKGenericEventArgs<Position> e) =>
{
    pin.Position = e.Value;
};

W ten sposób, użytkownik może zarówno klikając w mapę jak i przeciągając pina po niej łatwo wskazać lokalizację. Teraz pozostaje nam temat drugi - czyli geolokalizacja. Na początek w celu ułatwienia użytkownikowi odnalezienia swojej pasieki staram się wycentrować mapę na jego bieżacej lokalizacji - użyłem do tego biblioteki Xam.Plugin.Geolocator - polecam
ją bo korzysta z async/await oraz sama pyta o przyznanie uprawnień w Androidzie 5.0+. Jej użycie sprowadza się tak naprawdę do paru linijek kodu:

if (Plugin.Geolocator.CrossGeolocator.Current.IsGeolocationAvailable && Plugin.Geolocator.CrossGeolocator.Current.IsGeolocationEnabled)
{
    userLocation = await Plugin.Geolocator.CrossGeolocator.Current.GetPositionAsync(10000);
    var userPosition = new Position(userLocation.Latitude, userLocation.Longitude);
    map.MoveToRegion(MapSpan.FromCenterAndRadius(userPosition, Distance.FromKilometers(3)));
    pin.Position = userPosition;
}

Jeżeli api geolokalizacji jest dostępne - pobieramy lokalizację z timeoutem 10s, zmieniamy strukturę lat/lng na tą z TK.CustomMaps i centrujemy mapę i pina. Na początek na moje potrzeby taka funkcjonalność jest wystarczająca chociaż będę musiał dodać jeszcze pole tekstowe do wyszukiwania adresu.  Dodanie obsługi map w aplikacji - korzystając z tutoriali i opisów z zamieszczonych tu linków nie nastręczyło mi większych trudności, a mam nadzieję że korzyść z tego płynąca będzie spora. Pozdrawiam i do następnego razu!


niedziela, 19 marca 2017

XAMARIN i SQlite

Hej,
dzisiaj trochę o bazie danych w naszej konkursowej aplikacji - cóż to by była za aplikacja bez bazy, prawda? Przeglądając wszystkie xamarinowe tutoriale wybór wydaje się oczywisty: SQLite. Z takiego też rozwiązania korzystam, aczkolwiek gwoli ścisłości sposobów na przechowywania danych w xamarinie jest sporo - możemy równie dobrze dane parsować do jsona i korzystając z PCL Storage trzymać je w plikach. Ale ponieważ SQLite jest natywnie wspierany pod wszystkimi mobilnymi platformami, oraz korzystałem z niego wcześniej pisząc pod Adobe AIR nie zastanawiałem się długo nad wyborem. W internecie jest więcej niż jedna biblioteka tego bazodanowego silnika, ale najbardziej wypromowana i polecana jest sqlite-net-pcl wywodząca się z .netowej implementacji o nazwie sqlite.net.

Sam proces tworzenia naszej bazy jest dobrze opisany w internecie np tu : link.
Korzystanie jest proste i przyjemne jednak mamy pewne ograniczenia wynikające ze stosowania ORMa oraz z trochę okrojonego silnika. Po tych kilku latach używania SQLite nazbierało mi się kilka uwag i upierdliwych problemów:

  • Pod każdy interesujący nas wynik zapytania musimy zrobić osobną encje - nie można po prostu zrobić joina dwóch tabelek i dostać wyniku w postaci List<object>. Bardzo brakuje mi możliwości zwrócenia wyniku w postaci Hashtable, ExpandoObject lub w inny podobny sposób, który nie angażowałby mnie tak bardzo w pisanie encji. Korzystając z bazy w Adobe Air, w taki właśnie sposób działało zwracanie wyników - dostawaliśmy tablicę AS3.0 obiektów które zachowują się tak jak ExpandoObject w C#. 
  • Brak kluczy zewnętrznych - aczkolwiek znalazłem bibliotekę, która taką funkcjonalność oferuje (link) ale pewnie daje to spory narzut bo sam silnik takiej funkcjonalności nie posiada. 
  • ALTER TABLE obsługuje tylko ADD COLUMN - żadnej zmiany na istniejącej kolumnie nie wykonamy - nawet jej nie usuniemy. Trzeba się bawić w przenoszenie danych do tabelki tymczasowej, drop, nowy create itd.
  • Plik bazy ma tendencje do puchnięcia - polecam co jakiś czas wykonać VACUUM.
  • Error: database disk image is malformed - kto nigdy tego błędu nie widział ten może się uznać za szczęśliwca. Niestety mając kilka tysięcy urządzeń na produkcji raz na jakiś czas zdarza się, że w środowisku wystąpi błąd który plik bazy nieodwracalnie uszkadza (link). Dostęp do którejś z tabel jest wtedy niemożliwy, a resztę zwykle staram się odzyskać i w locie przerzucić do nowo utworzonej bazy. Bardzo irytujący błąd, którego nie potrafię wyeliminować.


Podsumowując - wybór silnika bazy danych był dla mnie prosty. Nie zawiodłem się na nim i polecam SQLite każdemu, kto potrzebuje skorzystać z relacyjnej bazy danych na urządzeniu mobilnym.



niedziela, 12 marca 2017

Widok, Model Widoku - proste połączenie

Hej,
w tym poście chciałbym zaprezentować prosty i skuteczny sposób na kojarzenie dwóch tytułowych elementów w jeden. Słowem wstępu - najpierw kilka informacji o działaniu widoków w Xamarin.Forms:
Widoki możemy tworzyć na dwa sposoby: używając XAMLa oraz całkowicie w kodzie c#. Osoby korzystające z Xamarina lub WPFa pewnie z XAMLem miały już do czynienia, a jak nie to krótko ujmując jest to język oparty na XMLu służący właśnie do tworzenia warstwy interfejsu użytkownika. Ma on tą przewagę nad tworzeniem widoków w kodzie, że po krótkiej praktyce, jestem w stanie z wizualizować sobie interfejs w głowie - przydatna umiejętność.
Pisząc XAMLowy widok w Xamarin.Forms jest on podzielony na dwa elementy - plik *.xaml, w którym jest xml z widokiem i plik tzw "codebehing" - *.xaml.cs który jest podpięty pod plik *.xaml i wygląda on mniej więcej tak:
public partial class WidokTest : ContentPage
{
    public WidokTest ()
    {
        InitializeComponent();
    }
}
Jak widać dla naszego testowego widoku mamy klasę, w której określamy bazowy widok po którym dziedziczymy - ContentPage lub ContentView najczęściej, a w konstruktorze wywołujemy inicjalizację komponentu.
Na pytanie skąd ten partial odpowiedź jest prosta - drugą częścią naszej klasy jest właśnie nasz plik xamlowy, który kompilator przerabia na jedną, wspólną klasę.

Dobrą praktyką jest aby w codebehind robić jak najmniej, a wszystkie operacje na widoku działy się poprzez bindingi. No ale w naszej klasie testowej brakuje co najmniej jednego elementu: ustalenia właściwości BindingContext, który powinien być instancją jednego z naszych modeli widoków.

Stworzyłem więc taką klasę bazową:

public abstract class ViewPage<T> : ContentPage where T : IViewModel
{
    readonly T _viewModel;
    public T ViewModel
    {
        get { return _viewModel; }
    }

    public ViewPage()
    {
        using (var scope = IoC.container.BeginLifetimeScope())
        {
            _viewModel = scope.Resolve<T>(new TypedParameter(this.GetType(), this));
        }
        BindingContext = _viewModel;
    }

    public ViewPage(object viewModelContext)
    {
        using (var scope = IoC.container.BeginLifetimeScope())
        {
            _viewModel = scope.Resolve<T>(new TypedParameter(this.GetType(), this), new NamedParameter("context", viewModelContext));
        }
        BindingContext = _viewModel;
    }
}
Stosując taką generyczną klasę bazową dla widoków mogę łatwo i szybko wyciągnąć z kontenera odpowiedni model i podpiąć go pod widok. Korzystając z niej, nasza testowa klasa widoku wyglądałaby następująco:

public partial class WidokTest : ViewPage<WidokTestModel>
{
    public WidokTest ()
    {
        InitializeComponent();
    }
}

public partial class WidokTest : ViewPage<WidokTestModel>
{
    public WidokTest (object kontekstWidoku) : base(kontekstWidoku)
    {
        InitializeComponent();
    }
}

Jak widać ilość kodu w codebehind wzrosła bardzo nieznacznie, a mamy załatwiony najważniejszy element. Niestety wszystko ma swoją cenę - trzeba przypomnieć sobie o tym nieszczęsnym partialu - nasz klasa dziedziczy teraz po nowym elemencie  i musimy analogicznie zmienić to w XAMLu:

<test:ViewPage xmlns:test="clr-namespace:MyApp.Test" x:TypeArguments="test:WidokTestModel"

Wystarczy dodać namespace w którym jest nasza klasa bazowa, zmienić nazwę głównego elementu oraz dodać generyczny parametr. W ten sposób wszystko nam się ładnie kompiluje, a my w prosty sposób spinany ze sobą widoki i modele widoków.

czwartek, 9 marca 2017

CQRS część 4, ostania - Validator

Hej,
ostatnią integralną częścią mojego CQRSa są walidatory. Są to klasy które dostając komendę, sprawdzają jej poprawność i możliwość wykonania. Gdy dana komenda w aktualnym stanie systemu jest niemożliwa do wykonania np nie można usunąć ula, w którym jest jakaś pszczela rodzina lub dane komendy są niepoprawne, spoza zakresu itp - to wtedy zwracamy taką informację do szyny komend, a ona za pomocą wyjątku zwraca ją dalej - użycie zostało pokazane w implementacji szyny komend w poście numer 1 dotyczącym CQRSa.
Dla walidatorów tak jak i dla komend mam ścieżkę synchroniczną i asynchroniczną. Zacznijmy od interfejsów:

public interface IValidator<TCommand> where TCommand : ICommand 
{
    ValidationResult Validate(TCommand command);
}

public interface IValidatorAsync<TCommand> where TCommand : ICommandAsync
{
    Task<ValidationResult> Validate(TCommand command); 
}

Jedyną nową rzeczą w tym kawałku kodu to klasa ValidationResult, a przedstawia się ona następująco:

public class ValidationResult
{
    public bool Result { get; set; } = true;

    public List<string> Messages { get; private set; } = new List<string>();
}

Mamy tutaj wynik walidacji określający czy wykonać komende, czy też nie, oraz listę komunikatów o problemach - jest to lista, ponieważ w ten sposób nie muszę się martwić o prawidłową konkatenację moich błędów i mam też informację o ich ilości, a gdy chcę pokazać informację o błędach robię po prostu String.join("\n", validationResult.Messages). Ostatnim elementem moich walidatorów jest customowy wyjątek, który wygląda następująco:

public class ValidationException : Exception
{
    public ValidationResult Result { get; protected set; }

    public ValidationException(ValidationResult result)
    {
        this.Result = result;
    }
}
Te kilka klas i interfejsów zamyka mi temat sprawdzania poprawności komend - oczywiście za pomocą kontenera i automatycznej rejestracji z assembly wszystko działa bardzo wygodnie. Ten post kończy serię o CQRS, po kilku tygodniach używania tego wzorca jestem bardzo zadowolony, wprawdzie ilość klas wzrosła i odczuwam pewien większy narzut czasowy pisania logiki jednak jest to niska cena za efekt, który uzyskałem. Kod jest czytelny, prosty, łatwo testowalny i dużo trudniej jest złamać zasadę jednej odpowiedzialności. Będę korzystał z CQRSa na pewno częściej w swojej pracy zawodowej.

środa, 8 marca 2017

Pszczoły na wiosnę i przywitanie

Hej,
blog wprawdzie miał być programistyczny, ale skoro aplikacja jeszcze nie gotowa chciałbym publikować tutaj również trochę informacji o moich pszczółkach i różnych ciekawych pszczelarskich perypetiach. Przy okazji pogłębię też moją wiedzę domenową, na której opieram cały projekt aplikacji. Na początku słowem wstępu o samej pasiece.
Pszczołami interesuje się od lat ok. 4, a pszczoły pierwsze zakupiłem lat temu 3. Mam 4 ule wielkopolskie - klasyczne stojaki w tym 3 wykonane przeze mnie, a jeden zakupiony u bartnicy.pl. Pasieka jest zlokalizowana koło mojego domu, na przedmieściach Tychów - dzielnica Czułów. Każdy ul jest zasiedlony, w tym jeden zeszłoroczny odkład Elgon, jeden odkład Krainka i dwie średnio silne rodzinki: Krainka i jakiś mieszaniec Krainki z Buckfastem. Tak prezentowała się moja pasieka w roku 2016 po zrobieniu ostatniego odkładu, zwracam uwagę, że w ulu na pierwszym planie są dwie rodziny oddzielone sprytną dennico-powałką.


No i jest dzień 08.03.2017, zima powoli za nami, a sytuacja w pasiece wygląda tak:
- straty po zimie (na razie): 0%
- pierwszy oblot : 27.02.2017,
- pyłek noszą - matki czerwią,
- wkładki dennicowe włożone,
- krokusy zaczynają kwitnąć,
- dzisiaj pierwsza porcja fondantu z diamanta ok 0,5 kg na rodzinke podane - nie bardzo miałem jak sprawdzić ilość pokarmu więc asekuracyjnie dostały co nieco - mam nadzieje, ze nie spracuje tym zimowej pszczoły.

Zastrzegam, że to moja pierwsza zimowla i pierwszy rok pszczelarzenia całkowicie od początku do końca na swoim - wcześniej pszczoły miałem na spółke z teściem, a pasieka była zlokalizowana w beskidzie żywieckim. Mam nadzieję, że ta gałąź bloga będzie również uzupełniana o nowe treści w miarę postępowania sezonu - do następnego razu!

CQRS część 3 - Event, Consumer

Hej,
wracamy do naszego CQRSa. Omówiłem już jego dwa podstawowe elementy czyli Komendy i Zapytania i na tym teoretycznie sam wzorzec poprzestaje, jednak oglądając na githubie jego różne implementacje zdecydowałem dołożyć do niego te dwa przydatne elementy. Zdarzenia są oczywiście wywoływane tylko przez handlery komend bo tylko tam zmieniamy stan systemu. Przenosząc się z Flexa do Xamarina, naturalne były dla mnie Eventy - we Flexie są bardzo dobrze zaimplementowane, a ich propagacja w systemie, propagacja po drzewie renderingu w górę do korzenia, anulowanie itp były out of the box. Zmieniłem z tego powodu trochę konwencję nazewnictwa samych zdarzeń co zobaczycie za chwilkę w snippetach. Konsument - jest to interfejs, który implementują klasy nasłuchujące na zdarzenia - zazwyczaj modele widoków. Wstęp za nami, lecimy z kodem, marker interface dla zdarzeń:

public interface IEvent
{
}

I od razu nasz interfejs szyny zdarzeń, dla mojej wygody po przesiadce z Flexa zamiast IEventBus interfejs po którym odnajdujemy w kontenerze naszą szynę nazywa się tak:

public interface IEventPublisher
{
    Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent;
    void RegisterAsyncConsumer<TEvent>(IConsumerAsync<TEvent> consumer) where TEvent : IEvent;
    void UnregisterAsyncConsumer<TEvent>(IConsumerAsync<TEvent> consumer) where TEvent : IEvent;
}

Musicie mi wybaczyć tą zmianę konwencji nazewnictwa, sama klasa implementująca nazywa się już poprawnie :) Jak widać z samego interfejsu - wszystkie eventy obsługiwane są asynchronicznie - z początku chciałem zrobić dwie ścieżki tak jak dla komend jednak uznałem to za zbędną komplikacje i zostało mi to co widać. Nie będę zamieszczał tym razem implementacji gdyż jest trywialna (jest na githubie) i opiera się na jednym polu zawierającym typ eventa i listę instancji klas nasłuchujących, które niestety musiałem zapakować w object:

private static Dictionary<Type, List<object>> _consumersAsync = new Dictionary<Type, List<object>>();

Dla konsumenta przygotowałem interfejs:

public interface IConsumerAsync<TEvent> where TEvent : IEvent
{
    Task HandleAsync(TEvent eventMessage);
}

Ponieważ spora część moich zdarzeń do będą CRUDowe informacje na temat dodania, aktualizacji lub usunięcia elementu dodałem bardzo przydatną uniwersalną klasę generyczną:

public class Event<TData> : IEvent where TData : DataModelBase
{
    public TData Item { get; protected set; }

    public EventAction Action { get; protected set; }

    public Event(TData item, EventAction action)
    {
        this.Action = action;
        this.Item = item;
    }
}

public enum EventAction
{
    CREATE = 1,
    UPDATE= 2,
    DELETE = 3
}

Kolejna porcja mojego CQRSa za nami, jeszcze trochę do omówienia zostało ale to w kolejnych postach, do przeczytania jutro!

wtorek, 7 marca 2017

CQRS, część 2 - Query

Hej,
dzisiaj omówię kolejny element mojego CQRSa czyli Zapytania - Query. Nadmienię jeszcze, że sam CQRS to wzorzec projektowy, a nie architektura (dzięki Norek, za uwagę żeby to podkreślić!). Xamarin jest oczywiście stworzony do MVVM - Model, Model widoku, Widok i z takiej architektury korzystam, a CQRS jest tutaj stosowany pomocniczo.

Ok, a więc Query - czyli zapytanie, które z definicji nie zmienia stanu systemu, mam dla niego przygotowany interfejs :
public interface IQuery<TResult>
{
}
Mimo że również jest to marker interface mamy już tutaj typ generyczny, który określa jaki typ danych zapytanie zwraca. Obsługujący zapytanie handler implementuje interfejs:

public interface IQueryHandler<in TQuery,  TResult> where TQuery : IQuery<TResult>
{
    TResult Execute(TQuery query);
}

Nasz handler ma określony typ zapytania oraz typ zwracanych danych, zwracam również uwagę, że dla ścieżki zapytań nie stosuję metod async. Wynika to ze specyfiki bazy danych - SQLite mimo że ma bibliotekę w nugecie obsługującą asynchroniczne zapytania - niestety nie spełniła moich oczekiwań i pozostałem przy wersji synchronicznej. Nie ma to w moim projekcie większego znaczenia i nie widzę potencjalnych benefitów z korzystania z bazy w sposób asynchroniczny. Upraszcza to również naszą szynę zapytań, która implementuje następujący interfejs:

public interface IQueryBus
{
    TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>;
}

Tak jak dla komend tak i tutaj pozwolę sobie zamieścić implementację mojej szyny zapytań - kiedy pisałem swojego CQRSa brakowało mi w internetowych artykułach  implementacji - wszyscy skupiali się tylko na interfejsach, a szukać przykładu musiałem na githubie przeglądając projekty.

public class QueryBus:IQueryBus
{

    private readonly ILifetimeScope _resolver;

    public QueryBus(ILifetimeScope resolver)
    {
        _resolver = resolver;
    }

    public TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>
    {
        var queryHandler = _resolver.ResolveOptional<IQueryHandler<TQuery,TResult>>();
        if (queryHandler == null)
        {
             throw new Exception(string.Format("No handler found for query '{0}'", query.GetType().FullName));
        }
        return queryHandler.Execute(query);
    }
}

Implementacja szyny jest bardzo prosta - szukamy zarejestrowanego handlera i jeśli go znajdujemy to wykonujemy zapytanie i zwracamy dalej jego wynik. Żeby powielić schemat posta o komendach, tutaj również pozwolę sobie zamieścić przykładowe zapytanie i handler zapytania.

public class GetBeeHivesOnApiary :IQuery<List<BeeHive>>
{
    public int ApiaryId { get; protected set; }

    public GetBeeHivesOnApiary(int apiaryId)
    {
        this.ApiaryId = apiaryId;
    }
}

public class GetBeeHivesOnApiaryHandler :IQueryHandler<GetBeeHivesOnApiary, List<BeeHive>>
{
    protected SQLiteConnection _database;

    public GetBeeHivesOnApiaryHandler(SQLiteConnection database)
    {
        _database = database;
    }

    public List<BeeHive> Execute(GetBeeHivesOnApiary query)
    {
        return _database.Query<BeeHive>("SELECT * FROM tb_beehive WHERE bh_ap_id = " + query.ApiaryId.ToString());
    }
}

OK, mamy więc omówioną z przykładem ścieżkę jaką wędrują w systemie zapytania, do przeczytania jutro - omówimy kolejny element czyli zdarzenia - Events.