grzesiek51114

grzesiek51114
2018-11-09 22:34

Postanowiłem opisać temat odpowiedniego ustawienia opcji Inverse() w związkach wiele-do-wielu w NHibernate. Nad tym czym jest Inverse() sam w sobie nie będę się tutaj rozwodził.

Jak wiadomo jest do dość kłopotliwa sprawa, bo człowiek zawsze zadaje sobie pytanie: Po której stronie związku postawić ten nieszczęsny Inverse()? Kto ma być właścicielem związku przy HasManyToMany, bo to własnie warunkuje Inverse()?

Załóżmy więc, że mamy napisać jakąś aplikację, a w niej moduł do zarządzania użytkownikami oraz grupami użytkowników. Istnieje potrzeba zrobienia mechaniki dodawania użytkownika do zdefiniowanych w systemie grup. Tutaj pozwolę sobie dodać pewne uproszczenie. Jak wiadomo w wielu systemach grupy można również przypisywać użytkownikowi, czyli odwrotnie do koncepcji, i nie stanowi to problemu, jednakże dla jasności przypadku niech przypisanie odbywa się tylko po stronie użytkownika.

Jest to klasyczny przykład związku wiele-do-wielu, ponieważ jeden użytkownik może znajdować się w wielu grupach, jak również jedna grupa może być przypisana do wielu użytkowników.

Klasyczny przykład definicji takich encji w NH to coś w stylu:

public class User
{
    public virtual int Id { get; set; }
    public virtual IList<Group> Groups { get; set; }
}
 
public class Group
{
    public virtual int Id { get; set; }
    public virtual IList<User> Users { get; set; }
}

Jak wiadomo to nie wystarczy i należy jeszcze zrobić odpowiednie mapowanie związków dla tych encji. Zróbmy więc klasyczne mapowanie związków w NH za pomocą Fluent NHibernate:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        HasManyToMany(x => x.Groups);
    }
}
 
public class GroupMap : ClassMap<Group>
{
    public GroupMap()
    {
        Id(x => x.Id);
        HasManyToMany(x => x.Users);
    }
}

Gdzie wstawić Inverse()?
Analizując różne przypadki i to co jest opisane w Internecie doszedłem do kilku wniosków, które mogą posłużyć jako rozwiązanie tego dziwnego problemu.

Rozbijmy przedstawiony wyżej związek wiele-do-wielu na dwa związki wiele-do-jednego i zadajmy sobie pytanie w stylu: Co do czego dodajemy, wg koncepcji naszego programu?

  • Czy wielu użytkowników dodajemy w aplikacji do jednej grupy?
  • Czy może wiele grup dodajemy w aplikacji do jednego użytkownika?

Wg założeń koncepcyjnych naszej aplikacji dodajemy użytkowników do grupy (pierwszy przypadek) więc klasa User staje się automatycznie właścicielem związku. Jak będzie wyglądało teraz nasze mapowanie? Ano tak:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        HasManyToMany(x => x.Groups).Inverse(); // <= tutaj zmiana w stosunku do poprzedniego kodu.
    }
}
 
public class GroupMap : ClassMap<Group>
{
    public GroupMap()
    {
        Id(x => x.Id);
        HasManyToMany(x => x.Users);
    }
}

Dlaczego tak? Tutaj należy zadać sobie dodatkowe pytanie: Czy użytkownik może istnieć bez przypisanej do siebie grupy? Nie może, ponieważ jaki sens ma użytkownik pozbawiony uprawnień wynikających z grupy użytkowników? Dla uproszczenia załóżmy, że grupa to jedyny dostawca uprawnień w aplikacji więc user musi być do niej przypisany. Jak wiadomo w wielu systemach użytkownik może posiadać własny zestaw uprawnień plus ten, który wynika z grupy ale pomińmy to w naszym systemie, ponieważ dla uproszczenia, jedynie grupa odpowiada za uprawnienia. Zresztą jest to kolejny, dobry przykład na to, po której stronie postawić Inverse() np. w związku grupa-uprawnienie. ;)

  • Czy grupa użytkowników może istnieć bez przypisanych do niej uprawnień?
  • Oczywiście, że nie, bo pusta grupa nie ma przecież sensu dlatego klasa Grupa jest właścicielem związku, a Inverse() postawimy przy kolekcji Permissions, kiedy będziemy definiować mapowanie encji Group. Tym sposobem dodajemy grupę do istniejących uprawnień.

Uff... mam nadzieję, że jakoś czytelnie mi to wyszło, bo długo mnie ta kwestia męczyła. ;)

#inverse #nhibernate #nh

grzesiek51114
2018-08-06 23:50

Jeżeli ktoś, będąc dzieckiem, tak jak ja siedział latem przyklejony do okna kiedy drogą jechał kombajn Bizon, to ten materiał będzie w sam raz dla niego: https://m.youtube.com/watch?v=-ajhprBRvms

Wychowałem się na wsi i pamiętam, że uwielbiałem te maszyny. Kilkoma miałem nawet okazję się przejechać, naturalnie nie jako kombajnista. Cudeńko, a facet z reportażu widać, że prawdziwy pasjonat.

vpiotr

@mr_jaro: no to znaczy mam już namiary na co najmniej dwóch takich. Dobrze wiedzieć...

mr_jaro

@vpiotr: a po co ci to wiedzieć :D Zresztą bizon jest prosty jak drut, młotkiem spawarką i zestawem kluczy naprawisz większość awarii xd

grzesiek51114
2018-07-31 19:38

...a napiszę na blogu, bo mogę. :-)

W skrócie: jeśli ktoś uczy się SFML'a i chce napisać grę to proszzz:

Let's make 16 games in C++/SFML!
https://www.youtube.com/playl[...]bvUSN7mzUffhiay5g5GUHyJRO4DYr

Może kody nie są jakoś niesamowicie piękne vide np. zmienne globalne ale fajnie się ogląda. Enjoy!

PS: prócz wspomnianego materiału reszta jest w większości po rusku. :-)

#cpp #sfml #gry #kanał_po_rusku

several

Na potrzeby edukacyjnego video YT lepiej się ogląda jak jest wszystko w main i bez funkcji. Mniej wyniesiesz z video jeśli wszystko jest przykryte funkcjami, klasami czy innymi abstrakcjami. Gość mógłby tylko zmienne lepiej nazywać.

grzesiek51114

Nawet ja, który w ogóle nie interesuję się SFML'em, zostałem tymi filmikami przyciągnięty... mają coś w sobie.

grzesiek51114
2018-07-22 01:04

Ostatnio pisząc projekt w WPF miałem do zebrania dane z około piętnastu, dość dużych kontrolek typu UserControl. W celu zebrania danych, do każdej kontrolki wysyłany jest odpowiedni event za pomocą Prism'a. Kiedy viewmodel takiej kontrolki odbierze event to w odpowiedzi zaczyna propagację innego eventu Prisma, służącego do przesyłu konkretnych danych. Ot komunikacja na zasadzie Request => Response.

Gdzieś te wszystkie eventy z danymi trzeba odebrać
Gdzieś musi być jakiś punkt, w którym zbierze się wszystkie te dane i przetworzy jakoś, np. w celu zapisania ich do bazy danych.

Jest w projekcie taka klasa, która zbiera wszystko razem, subskrybując eventy przesyłające dane z każdej z piętnastu kontrolek.
Tutaj pojawia się problem. Jak wiadomo piętnaście eventów Prism to także piętnaście tokenów subskrypcji, które to trzeba użyć do desubskrybowania eventu.

Można robić to za pomocą prywatnych pól, do których przypinać będziemy odpowiednie eventy, aby później je desubskrybować:

private SubscriptionToken eventToken;
...
// Np. w komendzie bindującej zdarzenie `Loaded` etc...
// Metoda Subscribe zwraca token używany później do desubskrybowania eventu.
eventToken = eventAggregator.GetEvent<PrismEvent>().Subscribe(/* metoda */);
...
// Np. w metodzie desubskrybującej eventy.
eventAggregator.GetEvent<PrismEvent>().Unsubscribe(eventToken);

Jednakże kiedy takich tokenów nazbiera się piętnaście to takie rozwiązanie już nie jest super, bo napisać się trzeba niepotrzebnego kodu co niemiara. Jak wspomniałem, jeden event równa się z zautomatu jednemu tokenowi, za pomocą, którego należy wykonać jedno desubskrybowanie. No, masakra ogólnie.

Wymyśliłem jednak rozwiązanie, które umieściłem a klasie ViewModelBase, po której dziedziczą wszystkie viewmodele w projekcie.

Co wiemy:

  • Wiadomo, że każdy event Prism'a musi dziedziczyć po klasie PubSubEvent lub PubSubEvent<T>, a te z kolei po EventBase Mamy więc wspólnego przodka dla wszystkich typów eventów;
  • Wiadomo także, że każdy token jest typu SubscriptionToken.

A gdyby tak skojarzyć event z tokenem za pomocą słownika w klasie bazowej wszystkich viewmodeli Prism'a?
Co nam to da?

  • Nie musimy deklarować prywatnego pola typu SubscriptionToken w viewmodelu per event;
  • Nie musimy realizować ręcznie desubskrybcji eventu per każdy token;
  • Mamy w słowniku event od razu skojarzony ze swoim tokenem;
  • Dzięki takiemu słownikowi desubskrybowanie wszystkich eventów realizuje tak naprawdę jedna pętla.
    public class ViewModelBase : BindableBase
    {
        protected IDictionary<EventBase, SubscriptionToken> subscriptions;
 
        public ViewModelBase()
        {
            subscriptions = new Dictionary<EventBase, SubscriptionToken>();
        }
 
        public void Unsubscribe()
        {
            foreach (var s in subscriptions)
            {
                s.Key.Unsubscribe(s.Value);
            }
        }
    }

Jak to wygląda w klasie, która będzie dziedziczyć po ViewModelBase? Prosto, starczy dodać do niej tylko taki, przykładowy wpis subskrypcji jakiegoś zdarzenia, np. PrismEvent:

subscriptions.Add(
    eventAggregator.GetEvent<PrismEvent>(),
    eventAggregator.GetEvent<PrismEvent>().Subscribe(/* metoda */));

I już niczego więcej nie trzeba robić, bo w takim viewmodelu wszystko zostanie zrealizowane automatycznie, za pomocą słownika. Naturalnie można to jeszcze bardziej ulepszyć np. w taki sposób żeby metody GetEvent nie wywoływać dwa razy, jednakże jest to w sumie kosmetyka. Najważniejsze jest to, że teraz każdy viewmodel w projekcie Prism posiada automat spinający tokeny z eventami i realizujący ich desubskrybowanie.

Cel został osiągnięty; dopinanie zdarzeń jest szybkie, a viewmodel czysty.

#prism #wpf #pubsubevent

grzesiek51114
2018-07-11 11:07

Nie sądziłem, że dożyję tak pięknej chwili...

Saalin

@Akihito: obawiam się, że Number.POSITIVE_INFINITY - 1 === Number.POSITIVE_INFINITY

grzesiek51114

@Saalin: przecież to jest realistyczne. Popatrz na przykład z życia: mamy nieskończenie wiele wszechświatów. Każdy z tych wszechświatów będzie rozszerzał się w nieskończoność, a więc będzie nieskończony. Teraz, jeśli będziemy podróżować wystarczająco długo to w końcu spotkamy identyczne ułożenie atomów jak w naszym, obecnym wszechświecie. Czyli może się zdarzyć, że ten N - 1, nieskończony wszechświat będzie identyczny z naszym, a więc twórcy JS mówią prawdę.

grzesiek51114
2018-07-10 22:21

Czy tylko ja, pisząc w WPF'ie mam wrażenie, że obcuję z całkowicie martwą i zapomnianą technologią?
Czy tylko ja, używając w projektach NHibernate'a mam wrażenie, że obcuję z całkowicie pomijanym i zbędnym na polskim rynku pracy, dobrym ORM'em?
Czy tylko ja mam wrażenie, że ucząc się przez te wszystkie lata obu powyższych rzeczy, uczyłem się tak naprawdę nikomu niepotrzebnych technologii?

O_o

Afish

„Jeśli ktoś umie programować, to nie będzie tworzył potworków” — to też pięknie brzmi na papierze, szkoda tylko, że rzeczywistość nie jest taka kolorowa.

somekind

No rzeczywistość jest taka, że masa "seniorów" z trzyletnim stażem, którzy znają sporo technologii, ale programować (czyli efektywnie rozwiązywać problemy przy użyciu odpowiednio dobranych narzędzi z zachowaniem jakości pozwalającej na utrzymanie produktu) nie potrafią. Tak jak wyobraźnia jest ważniejsza od wiedzy, tak umiejętność programowania jest ważniejsza od znajomości technologii.

grzesiek51114
2018-07-06 13:32

A w sumie, w ramach ogólnie pojętej nudy, wpadłem na pomysł naskrobania klasy losującej N liczb bez powtórzeń. Widziałem, że taki wątek pojawia się czasami na forum. Może to i oczywistość ale niech tam:

using System;
using System.Collections.Generic;
 
namespace RandomTest
{
    static class RandomInts
    {
        private static Random random = new Random();
 
        public static HashSet<int> Shuffle(int howMany, int from, int to)
        {
            CheckRange(howMany, from, to);
 
            var result = new HashSet<int>();
            while (result.Count < howMany)
            {
                result.Add(random.Next(from, to + 1));
            }
            return result;
        }
 
        private static void CheckRange(int howMany, int from, int to)
        {
            int count = 0;
            for (int i = from; i < to + 1; i++, count++) ;
 
            if (howMany > count)
                throw new Exception("Inifinite looped range exception.");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            foreach (int i in RandomInts.Shuffle(howMany: 10, from: 1, to: 100))
            {
                Console.WriteLine(i);
            }
        }
    }
}

Losowanie oparte jest o HashSet, bo tak najprościej odsiać powtórzenia. Jest też zabezpieczenie przed pętlą nieskończoną więc jeśli ktoś będzie chciał wylosować 10 unikalnych liczb z przedziału [0, 8] to dostanie wyjątek na twarz.

Poprawny przedział: https://ideone.com/8vbZBM
Niepoprawny przedział: https://ideone.com/7F7m3s

Akihito

@grzesiek51114: niestety sam pamietam moje poczatki z programowania, jak ktos nie ogarnia :D to HasSeta tak szybko tez nie ogarnie :)

grzesiek51114

@Akihito: studia... jak ja wtedy nienawidziłem programowania. :)

grzesiek51114
2018-05-08 23:04

Napisałem tyle postów ostatnimi czasy, że non stop odświeżam forum w oczekiwaniu na odpowiedzi. Jak w ciągu alkoholowym: miesiącami może być cisza aż w końcu nałóg daje o sobie znać i jest masakra. Dzisiaj tyle razy odświeżałem stronę, że, przy próbie zliczenia tego, przekręciłbym każdego int'a, niezależnie od bitowości. ;-)

czysteskarpety

aaa to ty tak spamujesz tymi postami ostatnio, zgłaszam moderacji

Silv

@grzesiek51114: szybciej niż powiadomienia. :D