GenericRepository implementacja (dłuższy kod)

Odpowiedz Nowy wątek
2017-10-19 09:20
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;
        //}
    }
}

Pozostało 580 znaków

2017-10-19 09:29
2017-10-19 09:47
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?

edytowany 1x, ostatnio: jacek.placek, 2017-10-19 09:49
po prostu nie przeczytał pierwszego zdania z Twojego posta ;) - ne0 2017-10-19 19:33
Taki odruch kompulsywny. - MrBean Bean 2017-10-19 21:48

Pozostało 580 znaków

2017-10-19 21:40
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?[...]sy-ab..0.0.0....0.j65hLsGaV_M


▒▒▒▒▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
▒▒▒▒▒▒▒█░▒▒▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒░█
▒▒▒▒▒▒▒█░▒▒▓▒▒▒▒▒▒▒▒▒▄▄▒▓▒▒░█░▄▄
▒▒▄▀▀▄▄█░▒▒▒▒▒▒▓▒▒▒▒█░░▀▄▄▄▄▄▀░░█

Pozostało 580 znaków

2017-10-19 22:52
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ę.


▒▒▒▒▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
▒▒▒▒▒▒▒█░▒▒▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒░█
▒▒▒▒▒▒▒█░▒▒▓▒▒▒▒▒▒▒▒▒▄▄▒▓▒▒░█░▄▄
▒▒▄▀▀▄▄█░▒▒▒▒▒▒▓▒▒▒▒█░░▀▄▄▄▄▄▀░░█
A możesz napisać dlaczego? W czym komendy i fabryka jest lepsza? - jacek.placek 2017-10-20 10:21

Pozostało 580 znaków

2017-10-19 23:50
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.

Pozostało 580 znaków

2017-10-20 15:46
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.


▒▒▒▒▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
▒▒▒▒▒▒▒█░▒▒▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒░█
▒▒▒▒▒▒▒█░▒▒▓▒▒▒▒▒▒▒▒▒▄▄▒▓▒▒░█░▄▄
▒▒▄▀▀▄▄█░▒▒▒▒▒▒▓▒▒▒▒█░░▀▄▄▄▄▄▀░░█

Pozostało 580 znaków

2017-10-21 12:16
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.

Pozostało 580 znaków

2017-10-21 14:13
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?


▒▒▒▒▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
▒▒▒▒▒▒▒█░▒▒▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒░█
▒▒▒▒▒▒▒█░▒▒▓▒▒▒▒▒▒▒▒▒▄▄▒▓▒▒░█░▄▄
▒▒▄▀▀▄▄█░▒▒▒▒▒▒▓▒▒▒▒█░░▀▄▄▄▄▄▀░░█
edytowany 1x, ostatnio: MrBean Bean, 2017-10-21 14:16

Pozostało 580 znaków

2017-10-21 14:31
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ń?

Pozostało 580 znaków

2017-10-21 16:04
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.


▒▒▒▒▒▒▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
▒▒▒▒▒▒▒█░▒▒▒▒▒▒▒▓▒▒▓▒▒▒▒▒▒▒░█
▒▒▒▒▒▒▒█░▒▒▓▒▒▒▒▒▒▒▒▒▄▄▒▓▒▒░█░▄▄
▒▒▄▀▀▄▄█░▒▒▒▒▒▒▓▒▒▒▒█░░▀▄▄▄▄▄▀░░█

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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