GenericRepository implementacja (dłuższy kod)

0

Siemka
Pomijając to czy generic repo to zło czy nie to chciałbym się was zapytać o jakość takiej implementacji. Znalazłem taki przykład kiedyś i nawet kilka razy użyłem w kilku projektach (trochę rozbudowane CRUD-y). Niby działa i co całkiem sprawnie, ale jakie mogą być wpadki i ograniczenia takiej implementacji?
Jak widać obiekty są odłączane od kontekstu przy odczycie i podłączane przy zapisie i aktualizacji) ale po zmianach trzeba dostarczyć do obiektu info o EntityState (co nie jest jakimś problemem).

Dodatkowo umożliwia zapisanie czy aktualizację całego drzewa obiektów (o ile każdy obiekt na każdej gałęzi będzie miał określony EntityState).

Czyli czy Wam się podoba taki twór i dlaczego nie? :)

public interface IEntity
{
    [NotMapped]
    EntityState EntityState { get; set; }
}

public enum EntityState
{
    Unchanged,
    Added,
    Modified,
    Deleted
}

public class BaseEntity<T> : IEntity
{
    public T Id { get; set; }
    public DateTime Created { get; set; }

    public BaseEntity()
    {
        Created = DateTime.UtcNow;
    }
    [NotMapped]
    public EntityState EntityState { get; set; }
}


public interface IGenericDataRepository<T> : IDisposable  where T : class, BetKom.Model.Common.IEntity
{
    IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties);
    IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
    T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
    void Add(params T[] items);
    void Update(params T[] items);
    void Remove(params T[] items);
}

public class GenericDataRepository<T> : IGenericDataRepository<T> where T : class, BetKom.Model.Common.IEntity
{
    public GenericDataRepository()
    {
        
    }
    public virtual IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties)
    {
        List<T> list;
        using (var context = new DBContext())
        {
            IQueryable<T> dbQuery = context.Set<T>();

            //Apply eager loading
            foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                dbQuery = dbQuery.Include<T, object>(navigationProperty);

            list = dbQuery
                .AsNoTracking()
                .ToList<T>();
        }
        return list;
    }

    public virtual IList<T> GetList(Func<T, bool> where,
         params Expression<Func<T, object>>[] navigationProperties)
    {
        List<T> list;
        using (var context = new DBContext())
        {
            IQueryable<T> dbQuery = context.Set<T>();

            //Apply eager loading
            foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                dbQuery = dbQuery.Include<T, object>(navigationProperty);

            list = dbQuery
                .AsNoTracking()
                .Where(where)
                .ToList<T>();
        }
        return list;
    }

    public virtual T GetSingle(Func<T, bool> where,
         params Expression<Func<T, object>>[] navigationProperties)
    {
        T item = null;
        using (var context = new DBContext())
        {
            IQueryable<T> dbQuery = context.Set<T>();

            //Apply eager loading
            foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                dbQuery = dbQuery.Include<T, object>(navigationProperty);

            item = dbQuery
                .AsNoTracking() //Don't track any changes for the selected item
                .FirstOrDefault(where); //Apply where clause
        }
        if (item != null)
            item.EntityState = BetKom.Model.Common.EntityState.Unchanged;
        return item;
    }

    /* rest of code omitted */

    public virtual void Add(params T[] items)
    {

        PUpdate(items);
    }

    public virtual void Update(params T[] items)
    {

        PUpdate(items);
    }

    public void PUpdate(params T[] items)
    {
        using (var context = new DBContext())
        {
            DbSet<T> dbSet = context.Set<T>();
            foreach (T item in items)
            {
                dbSet.Attach(item);
                var dd = context.ChangeTracker.Entries<IEntity>();
                foreach (DbEntityEntry<IEntity> entry in dd)
                {
                    IEntity entity = entry.Entity;
                    entry.State = GetEntityState(entity.EntityState);
                }
            }
            context.SaveChanges();
    }

    public virtual void Remove(params T[] items)
    {
        PUpdate(items);
    }

    protected static System.Data.Entity.EntityState GetEntityState(BetKom.Model.Common.EntityState entityState)
    {
        switch (entityState)
        {
            case BetKom.Model.Common.EntityState.Unchanged:
                return System.Data.Entity.EntityState.Unchanged;
            case BetKom.Model.Common.EntityState.Added:
                return System.Data.Entity.EntityState.Added;
            case BetKom.Model.Common.EntityState.Modified:
                return System.Data.Entity.EntityState.Modified;
            case BetKom.Model.Common.EntityState.Deleted:
                return System.Data.Entity.EntityState.Deleted;
            default:
                return System.Data.Entity.EntityState.Detached;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            //// free managed resources  
            //if (managedResource != null)
            //{
            //    managedResource.Dispose();
            //    managedResource = null;
            //}
        }
        //// free native resources if there are any.  
        //if (nativeResource != IntPtr.Zero)
        //{
        //    Marshal.FreeHGlobal(nativeResource);
        //    nativeResource = IntPtr.Zero;
        //}
    }
}
0

Ale co chciałeś powiedzieć tym linkiem bo większość punktów z linka nie ma tu zastosowania. Szczególnie jak zamiast generycznego repozytorium (GR) zrobisz podobną, konkretną implementację dla AggregateRoot-a (co jest możliwe bez problemu w podanej implementacji i nawet tak wykorzystywane przeze mnie).
Zresztą pisałem, żeby nie oceniać Samego faktu czy GR ma sens czy nie.

Inna sprawa, ze jak mam 10 tabel czysto słownikowych to GR też jest złe? No to co zamiast? DBContext w okienkach WinForms?

0

Nie rozumiem co w tym "repozytorium" robi GetEntityState. Nie lepiej ci użyć ObjectStateManager ?

Ja tam nic nie umiem wszystkie moje aplikacje to Table Module :).

Ale może mój kolega coś więcej powie https://www.google.pl/search?biw=1920&bih=974&q=Change+Tracking+of+POCO+snapshot+and+proxy&oq=Change+Tracking+of+POCO+snapshot+and+proxy&gs_l=psy-ab.3...11278.14726.0.14934.5.4.1.0.0.0.139.437.2j2.4.0....0...1.1.64.psy-ab..0.0.0....0.j65hLsGaV_M

0

Sorry ale pociągnę temat zagadką jest dla mnie te repo. Dlaczego są tam metody virtualne ?

To bardziej wygląda jak fasada

Expression<Func<T, object>>[] navigationProperties

Ty to wstrzykujesz do warstwy widoku?

Inna sprawa, ze jak mam 10 tabel czysto słownikowych to GR też jest złe? No to co zamiast? DBContext w okienkach WinForms?

Ja bym zrobił komendy i do tego fabrykę.

0

To nie moje, jak pisałem na początku. Znalazłem i się pytam o opinie. Było to jakiś czas temu i teraz nie mogę odnaleźć nawet na jakiej to było stronie.
Wirtualne pewnie są po to, żeby można przesłonić.

Nie wstrzykuję nic nigdzie. navigationProperties to są "dzieci" do pobrania razem z rodzicem. Można sobie wybrać, że chcesz pobrać rodzica z jakimiś dziećmi albo jakieś inne rekordy w relacjach.

0

A możesz napisać dlaczego? W czym komendy i fabryka jest lepsza?

Będziesz mógł nimi zastąpić grube serwisy. W tedy każdy select czy transakcja do bazy ma swoją klasę. Możesz też pójść dalej i zrobić cqrs.
Repo jest częścią pakietu, który powinien być zależny od domeny. Zwykle wypluwa to co potrzebuje serwis domenowy:). Jeśli nie robisz DDD to prawdopodobnie w ogóle go nie potrzebujesz.

0

Dzięki za odpowiedź.

MrBean Bean napisał(a):

A możesz napisać dlaczego? W czym komendy i fabryka jest lepsza?

Będziesz mógł nimi zastąpić grube serwisy. W tedy każdy select czy transakcja do bazy ma swoją klasę. Możesz też pójść dalej i zrobić cqrs.

CQRS to OK ale raczej przy większych potrzebach iż moje. Np. mam 15 słownikowych tabel. W każdej maks. 100 rekordów. CQRS ma sens? Same Commandy pewnie tak. Albo jakiś WinForms-owy manager aplikacji ASP.NET MVC w którym są importy (i ręczna edycja) produktów, opcji itp głównie z plików Excela.

Repo jest częścią pakietu, który powinien być zależny od domeny. Zwykle wypluwa to co potrzebuje serwis domenowy:).

To jest jasne. Ja nie mówię, ze generic repo jest sensowne dla DDD bo w DDD rządzi domena a ta powinna gadać swoim (domenowym) językiem.

Jeśli nie robisz DDD to prawdopodobnie w ogóle go nie potrzebujesz.

To nie jest tak. IMHO DDD ma znacznie wyższy techniczny próg wejścia. Ja "miększe" tematy DDD to raczej ogarniam ale mam wrażenie, że cała architektura aplikacji się mocno komplikuje a to trzeba potem utrzymać. Trzeba mieć zasoby a ja jestem mikro MiSiem. Może niedługo będę aktualizował jeden większy system to spróbuję jakoś mocniej do niego podejść pod katem DDD. Tam jest kilka modułów (zamówienia, sprzedaż, produkcja, logistyka, pecety, terminale mobilne, maszyny przemysłowe z komunikacją bezpośrednią itp) i wydaje się, ze DDD byłoby idealne ale musiałbym się sporo douczyć a nie wiem czy będe miał na to czas i budżet.

0

Jeśli masz problem z wydajnością to może zrób sobie fizyczne widoki, to znaczy skompilowane selecty. Od strony transakcja patrząc przez pryzmat CQRS robisz sobie handler AddFakturaHandlerCommand do niego komendę z 10 kluczami do słowników, no i chyba git?

0

Ale ja nie mam problemów z wydajnością.
Chciałem się tylko zapytać o tę implementację GR do np. słownikowych tabel. Mi się ona ładnie spina z WinForms-ami i Gridami z DevExpress szczególnie jak chcę zapisać od razy całe drzewo do db. Albo niektóre dzieci w drzewie, tylko trzeba ustawić odpowiedni EntityState. Np Opcje i WartośćOpcji dla jakiegoś Produktu.
Pobieram sobie Opcję z Wartościami z takiego repo. Obiekty są odłączone od kontekstu.
Mogę sobie w gridach coś pozmieniać i wykonać repo.Update(Opcja op) i wszystko się ładnie zapisuje (zmiany w Opcji i zmiany w wartościach opcji).
No ale może są inne, lepsze metody do takich zadań?

0

Repo nie może być stabilne to to co korzysta z repo ma być stabilne a repo niestabilne. Projektowanie to nie przebieranie w wzorcach i wybieranie tych "najlepszych" a raczej zarządzanie zależnościami, tworzenie powiązań logicznych.

0

Hm... Nawet się zgadzam (przy założeniu, ze zrozumiałem poprawnie). Tylko mam wrażenie, ze nie piszemy o tym samym problemie bo to co napisałeś to też dotyczy bardziej DDD niż CRUD. A mi chodzi o ocenę rozwiązania dla CRUD+.
Np m u siebie słowniki Marka -> Model -> Typ. Podobnie jak np. w samochodach. I tu nie ma żadnej domeny. nic się z tymi obiektami nie dzieje, nie ma żadnego przetwarzania. wszystko mocno zamknięte bo to jest aplikacja UI dla maszyny (a maszyna raczej się nie zmieni przez całe swoje życie). I mam jedno repo MarkaRepository w którym jest obsługa tych 3 klas. I wydaje mi się, że podawane grzechy GR nie maja tu zastosowania.

0

No niestety EF potrzebuje jakiejś nakładki typu pseudo repo jeśli przepuszczasz to jeszcze przez warstwę serwisową to powinno być ok (nie wiem zgaduje). Poza tym jak my mamy mówić na tym samym poziomie abstrakcji. Ja nie widzę całości kodu, czy diagramu, dokumentacji ja muszę zgadywać. To że ty coś mi tam tłumaczysz po swojemu to nie znaczy że ja to sobie tak samo narysuję jak ty. Nie rozumiem czy nie można tego wrzucić na githuba?

0

Wyglada jako tako (strasznie dlugie) - ale brakuje mi obslugi stronicowania/sortowania - co jak tabele bedzie miala 100000000 elelmentow? EntityState tez nie ma sensu (zadnego) - jesli chcesz aby grid pozwalal Ci updatetowac wiele elementow to zamiast robic to ustawianiem state dodaj sobie metode ktora wywola konkretne metody update/delete/create.

GR ma czesto sens - ma tylko jeden problem z DDD a raczej osobami stosujacymi DDD widzacymi cos innego niz DDD ;)

Btw zapytaj osoby od DDD jak dojsc na jakas ulice to odpowie Ci ze zle ze tam idziesz i czy na pewno masz parasol bo moze padac ;)

2
tamtamtu napisał(a):

GR ma czesto sens - ma tylko jeden problem z DDD a raczej osobami stosujacymi DDD widzacymi cos innego niz DDD ;)

Btw zapytaj osoby od DDD jak dojsc na jakas ulice to odpowie Ci ze zle ze tam idziesz i czy na pewno masz parasol bo moze padac ;)

Problem wynika z nazewnictwa, ponieważ niestety doszło do sytuacji że dwa różne wzorce w obiegowej opinii mają tą samą nazwę: Repository.
Repository oryginalnie pochodzi z DDD, i występujące pod tą samą nazwą w książce Fowlera o wzorcach klasy enterpise.
Natomiast to co tutaj mamy to jest to DAO - Data Access Object, występujące pod nazwą Table Data Gateway u Fowlera, a potocznie nazywane repozytorium.
I przez to jest masa chaosu i nieporozumień, bo to są dwa różne wzorce znajdujące się podobnym miejscu w architekturze, a mające jednak trochę różne odpowiedzialności.

0
tamtamtu napisał(a):

Wyglada jako tako (strasznie dlugie) - ale brakuje mi obslugi stronicowania/sortowania - co jak tabele bedzie miala 100000000 elelmentow?

To jest dla aplikacji WinForms i jak widać metoda GetList ma jako parametr "Func<T, bool> where" i zawsze (a nawet ZAWSZE) tam gdzie może być więcej rekordów idzie ograniczenie po datach a ja używam tego jako dostęp do słowników i w tych słownikach jest tyle danych ile jest (np województw w PL jest tyle ile jest i więcej nie będzie). Podobnie z np. kolorami.

EntityState tez nie ma sensu (zadnego) - jesli chcesz aby grid pozwalal Ci updatetowac wiele elementow to zamiast robic to ustawianiem state dodaj sobie metode ktora wywola konkretne metody update/delete/create.

Tylko, że taka postać umożliwia mi wykonanie Update na obiekcie root ze zmienionymi niektórymi obiektami w jakichś kolekcjach w obiekcie root.

Np Opcja ma listę Wartości. Pobieram rekord Opcja razem z listą wartości. Zmieniam wartości (edycja, dodanie...) i robię Update na obiekcie Opcja i wartości, które są zmienione, dodane, usunięte aktualizują się wg EntityState.

0
neves napisał(a):
tamtamtu napisał(a):

GR ma czesto sens - ma tylko jeden problem z DDD a raczej osobami stosujacymi DDD widzacymi cos innego niz DDD ;)

Btw zapytaj osoby od DDD jak dojsc na jakas ulice to odpowie Ci ze zle ze tam idziesz i czy na pewno masz parasol bo moze padac ;)

Problem wynika z nazewnictwa, ponieważ niestety doszło do sytuacji że dwa różne wzorce w obiegowej opinii mają tą samą nazwę: Repository.
Repository oryginalnie pochodzi z DDD, i występujące pod tą samą nazwą w książce Fowlera o wzorcach klasy enterpise.
Natomiast to co tutaj mamy to jest to DAO - Data Access Object, występujące pod nazwą Table Data Gateway u Fowlera, a potocznie nazywane repozytorium.
I przez to jest masa chaosu i nieporozumień, bo to są dwa różne wzorce znajdujące się podobnym miejscu w architekturze, a mające jednak trochę różne odpowiedzialności.

W książce Fowlera repo to mieszanka Query Object ora Metadata Maping.
Evans za to przestrzega przed tego typu elastycznymi repo opartych o specyfikacje.

Table Data Gateway to tylko przykrywka dla stringów sqla, czym w jego przypadku powinie się zająć normalny ORM.

To nie tak że repo nazywane jest potocznie DAO. Ludzie na siłę wpychają intefejs DAO w implementacje repo.

Tutaj mamy doczynienia z czymś więcej niż repo czy DAO. hmmm...

0

Poza tym, że nazwa jest bez sensu, bo to zwykłe DAO, które z repozytorium to nie ma nic wspólnego, to kod też jest bez sensu, bo to tylko taki wrapper, który wycina funkcjonalność znajdującego się pod spodem kontekstu ORMa, a poza tym niczego nie daje. Kawał dobrej, nikomu niepotrzebnej roboty.

A u Fowlera z repozytorium wszystko jest ok. Repozytorium jest abstrakcją nad warstwą dostępu do danych, nie jej częścią. Repozytorium daje dostęp do obiektów domenowych w formie przypominającej kolekcję i tyle.

1 użytkowników online, w tym zalogowanych: 0, gości: 1