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