NON usando il pattern del repository, usa l'ORM così com'è (EF)


95

Ho sempre usato il pattern Repository ma per il mio ultimo progetto volevo vedere se potevo perfezionarne l'uso e la mia implementazione di “Unit Of Work”. Più ho iniziato a scavare ho iniziato a chiedermi: "Ne ho davvero bisogno?"

Ora tutto inizia con un paio di commenti su Stackoverflow con una traccia al post di Ayende Rahien sul suo blog, con 2 specifici,

Probabilmente se ne potrebbe parlare per sempre e dipende da diverse applicazioni. Cosa mi piace sapere

  1. questo approccio sarebbe adatto per un progetto Entity Framework?
  2. utilizzando questo approccio la logica aziendale va ancora in un livello di servizio o metodi di estensione (come spiegato di seguito, lo so, il metodo di estensione utilizza la sessione NHib)?

È facilmente realizzabile utilizzando metodi di estensione. Pulito, semplice e riutilizzabile.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Usando questo approccio e Ninjectcome DI, devo creare l' Contextinterfaccia a e iniettarla nei miei controller?

Risposte:


103

Ho seguito molti percorsi e creato molte implementazioni di repository su diversi progetti e ... ho gettato la spugna e ci ho rinunciato, ecco perché.

Codifica per l'eccezione

Codifichi per l'1% di possibilità che il tuo database cambi da una tecnologia all'altra? Se stai pensando allo stato futuro della tua attività e dici di sì, è una possibilità, allora a) devono avere molti soldi per permettersi di fare una migrazione a un'altra tecnologia DB ob) stai scegliendo una tecnologia DB per divertimento o c ) qualcosa è andato storto con la prima tecnologia che hai deciso di utilizzare.

Perché buttare via la ricca sintassi LINQ?

LINQ ed EF sono stati sviluppati in modo da poter fare cose pulite con esso per leggere e attraversare i grafici a oggetti. Creare e mantenere un repository che possa darti la stessa flessibilità per farlo è un compito mostruoso. Nella mia esperienza ogni volta che ho creato un repository l'ho SEMPRE perdita di logica aziendale nel livello del repository per rendere le query più performanti e / o ridurre il numero di accessi al database.

Non voglio creare un metodo per ogni singola permutazione di una query che devo scrivere. Potrei anche scrivere stored procedure. Non voglio GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity,GetOrderByUserId , e così via ... voglio solo per ottenere l'entità principale e traverse e includere l'oggetto grafico come ho quindi per favore.

La maggior parte degli esempi di archivi sono cazzate

A meno che tu non stia sviluppando qualcosa di VERAMENTE scarno come un blog o qualcosa, le tue domande non saranno mai semplici come il 90% degli esempi che trovi su Internet che circondano il modello del repository. Non lo sottolineerò mai abbastanza! Questo è qualcosa che bisogna strisciare nel fango per capirlo. Ci sarà sempre quella query che rompe il tuo repository / soluzione perfettamente pensata che hai creato, e non è fino a quel punto in cui indovini te stesso e inizia il debito / erosione tecnica.

Non mettermi alla prova, fratello

Ma per quanto riguarda i test di unità se non ho un repository? Come mi deriderò? Semplice, non lo fai. Vediamolo da entrambi gli angoli:

Nessun repository: puoi deridere l' DbContextuso di uno IDbContexto altri trucchi, ma poi stai davvero testando l'unità LINQ to Objects e non LINQ to Entities perché la query è determinata in fase di esecuzione ... OK, quindi non va bene! Quindi ora tocca al test di integrazione coprirlo.

Con il repository: ora puoi simulare i tuoi repository e testare unitamente i layer intermedi. Ottimo vero? Beh, non proprio ... Nei casi sopra in cui devi trapelare la logica nel livello del repository per rendere le query più performanti e / o meno hit al database, come possono coprirlo i tuoi test unitari? Ora è nel livello del repository e non vuoi testarlo, IQueryable<T>giusto? Inoltre, siamo onesti, i tuoi unit test non copriranno le query che hanno una .Where()clausola di 20 righe e.Include()È un mucchio di relazioni e colpisce di nuovo il database per fare tutte queste altre cose, blah, blah, blah comunque perché la query viene generata in fase di esecuzione. Inoltre, poiché hai creato un repository per mantenere ignorante la persistenza dei livelli superiori, se ora vuoi cambiare la tecnologia del tuo database, mi dispiace che i tuoi test unitari non garantiscano gli stessi risultati in fase di esecuzione, tornando ai test di integrazione. Quindi l'intero punto del repository sembra strano ..

2 centesimi

Perdiamo già molte funzionalità e sintassi quando si utilizza EF su procedure memorizzate semplici (inserimenti in blocco, eliminazioni in blocco, CTE, ecc.) Ma codice anche in C # quindi non devo digitare binario. Usiamo EF in modo da avere la possibilità di utilizzare diversi provider e di lavorare con gli oggetti grafici in un bel modo correlato tra molte cose. Alcune astrazioni sono utili e altre no.


16
Non si creano repository per poterli unit test. Si creano repository per poter eseguire un test unitario della logica di business . Per quanto riguarda la sicurezza del funzionamento delle query: è molto più semplice scrivere test di integrazione per i repository poiché contengono solo logica e non attività commerciali.
jgauffin

16
Coding for the exception: L'uso dei repository non significa poter cambiare motore di database. Si tratta di separare il business dalla tenacia.
jgauffin

2
Questi sono tutti punti molto validi con una grande quantità di verità dietro di loro. Ciò che manca, tuttavia, è la consapevolezza che LINQ disseminata di un'applicazione anziché vincolata a una posizione coerente crea l'equivalente EF delle chiamate SQL nelle pagine codebehind. Ogni query LINQ è un potenziale punto di manutenzione in un'applicazione e più ce ne sono (e più sono diffuse), maggiori sono i costi e i rischi di manutenzione. Immagina di aggiungere un flag "cancellato" a un'entità e di dover individuare ogni singola posizione in un'applicazione di grandi dimensioni a cui l'entità viene interrogata, dovendo modificare ogni ...
DVK

2
Penso che questo sia miope e stanco. Perché trapelare la logica nel repo? E se lo facessi, perché sarebbe importante? È un'implementazione dei dati. Tutto ciò che stiamo facendo è isolare LINQ dal resto del codice nascondendolo dietro il repository. Dici di non provarlo, ma poi usi il non essere in grado di testarlo come argomento contro farlo. Quindi crea il repository, non esporre IQueryable e non testarlo. Almeno puoi testare tutto il resto separatamente dall'implementazione dei dati. E quell'1% di possibilità di una modifica del database è ancora enorme in termini di $.
Sinaesthetic

5
+1 per questa risposta. Trovo che NON abbiamo davvero bisogno di repository con Entity Framework Core. Il DbSetè il deposito ed DbContextè l' Unità di Lavoro . Perché implementare il pattern del repository quando ORM lo fa già per noi! Per i test, cambia semplicemente il provider in InMemory. E fai i tuoi test! È ben documentato in MSDN.
Mohammed Noureldin

49

Il pattern del repository è un'astrazione . Il suo scopo è ridurre la complessità e rendere il resto del codice persistente ignorante. Come bonus ti permette di scrivere test unitari invece di test di integrazione .

Il problema è che molti sviluppatori non riescono a comprendere lo scopo dei modelli e creano archivi che perdono informazioni specifiche sulla persistenza fino al chiamante (tipicamente esponendo IQueryable<T>). In questo modo non ottengono alcun vantaggio rispetto all'utilizzo diretto di OR / M.

Aggiorna per affrontare un'altra risposta

Codifica per l'eccezione

Usare i repository non significa essere in grado di cambiare la tecnologia di persistenza (cioè cambiare database o utilizzare un servizio web, ecc.). Si tratta di separare la logica aziendale dalla persistenza per ridurre la complessità e l'accoppiamento.

Test unitari vs test di integrazione

Non scrivi unit test per i repository. periodo.

Ma introducendo repository (o qualsiasi altro livello di astrazione tra persistenza e business) si è in grado di scrivere unit test per la logica di business. cioè non devi preoccuparti che i tuoi test falliscano a causa di un database configurato in modo errato.

Per quanto riguarda le domande. Se usi LINQ devi anche assicurarti che le tue query funzionino, proprio come hai a che fare con i repository. e questo viene fatto utilizzando i test di integrazione.

La differenza è che se non hai mischiato la tua attività con le istruzioni LINQ puoi essere sicuro al 100% che è il tuo codice di persistenza a non riuscire e non qualcos'altro.

Se analizzi i tuoi test vedrai anche che sono molto più puliti se non hai preoccupazioni contrastanti (es. LINQ + logica aziendale)

Esempi di repository

La maggior parte degli esempi sono stronzate. questo è molto vero. Tuttavia, se cerchi su Google qualsiasi modello di progettazione, troverai molti esempi schifosi. Questo non è un motivo per evitare di usare un pattern.

Costruire una corretta implementazione del repository è molto semplice. In effetti, devi solo seguire una singola regola:

Non aggiungere nulla nella classe del repository fino al momento in cui ne hai bisogno

Molti programmatori sono pigri e cercano di creare un repository generico e utilizzare una classe base con molti metodi di cui potrebbero aver bisogno. YAGNI. Si scrive la classe del repository una volta e la si conserva finché l'applicazione è attiva (possono essere anni). Perché rovinare tutto essendo pigro. Mantenerlo pulito senza alcuna ereditarietà della classe base. Sarà molto più facile leggere e mantenere.

(La dichiarazione di cui sopra è una linea guida e non una legge. Una classe base può benissimo essere motivata. Pensa solo prima di aggiungerla, in modo da aggiungerla per le giuste ragioni)

Roba vecchia

Conclusione:

Se non ti dispiace avere istruzioni LINQ nel tuo codice aziendale né ti interessano gli unit test, non vedo alcun motivo per non utilizzare direttamente Entity Framework.

Aggiornare

Ho scritto sul blog sia del pattern del repository che di cosa significhi veramente "astrazione": http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Aggiorna 2

Per il tipo di entità singola con più di 20 campi, come progetterai il metodo di query per supportare qualsiasi combinazione di permutazione? Non vuoi limitare la ricerca solo per nome, per quanto riguarda la ricerca con proprietà di navigazione, elencare tutti gli ordini con articolo con codice prezzo specifico, 3 livelli di ricerca proprietà di navigazione. L'intera ragione è IQueryablestata inventata per poter comporre qualsiasi combinazione di ricerca su database. Tutto sembra fantastico in teoria, ma la necessità dell'utente vince sulla teoria.

Di nuovo: un'entità con più di 20 campi è modellata in modo errato. È un'entità DIO. Abbattilo.

Non sto sostenendo che IQueryablenon sia stato fatto per interrogare. Sto dicendo che non è giusto per un livello di astrazione come il modello di repository poiché perde. Non esiste un provider LINQ To Sql completo al 100% (come EF).

Tutti hanno cose specifiche di implementazione come come usare il caricamento ansioso / pigro o come eseguire le istruzioni SQL "IN". EsporreIQueryable nel repository costringe l'utente a conoscere tutte queste cose. Quindi l'intero tentativo di astrarre la fonte dei dati è un completo fallimento. Devi solo aggiungere complessità senza ottenere alcun vantaggio rispetto all'utilizzo diretto di OR / M.

O implementa correttamente il pattern Repository o semplicemente non usarlo affatto.

(Se vuoi davvero gestire entità di grandi dimensioni, puoi combinare il modello Repository con il modello Specification . Questo ti dà un'astrazione completa che è anche testabile.)


6
Non esporre IQueryable porta a una ricerca limitata e le persone finiscono per creare più metodi Get per diversi tipi di query e alla fine rende il repository più complesso.
Akash Kava

3
non hai affrontato affatto il problema principale: esporre IQueryable attraverso un repository non è un'astrazione completa.
jgauffin

1
Avere un oggetto query che contiene tutta l'infrastruttura necessaria da eseguire in sé e per sé è la strada da percorrere imo. Gli dai i campi che sono i termini di ricerca e restituisce un elenco di risultati. All'interno del QO puoi fare quello che vuoi. Ed è un'interfaccia, così facilmente testabile. Vedi il mio post sopra. È la migliore.
h.alex

2
Personalmente, penso che abbia anche senso implementare l'interfaccia IQueryable <T> su una classe Repository, piuttosto che esporre il set sottostante in uno dei suoi membri.
dark_perfect

3
@yat: un repository per radice aggregata. Ma imho non è la radice aggregata e l'aggregazione di tabelle, ma solo la radice aggregata e gli aggregati . La memoria effettiva potrebbe utilizzare solo una tabella o molte di esse, ovvero potrebbe non essere una mappatura uno-uno tra ogni aggregato e una tabella. Uso i repository per ridurre la complessità e rimuovere eventuali dipendenze dello storage sottostante.
jgauffin

27

IMO sia l' Repositoryastrazione che l' UnitOfWorkastrazione hanno un posto molto prezioso in ogni sviluppo significativo. Le persone discuteranno sui dettagli di implementazione, ma proprio come ci sono molti modi per scuoiare un gatto, ci sono molti modi per implementare un'astrazione.

La tua domanda è specificamente da usare o non usare e perché.

Come senza dubbio hai capito che hai già entrambi questi modelli integrati in Entity Framework, DbContextè il UnitOfWorked DbSetè il Repository. In genere non è necessario eseguire un test unitario di UnitOfWorko Repositoryse stessi poiché stanno semplicemente facilitando tra le classi e le implementazioni di accesso ai dati sottostanti. Quello che ti ritroverai a dover fare, ancora e ancora, è deridere queste due astrazioni durante il test unitario della logica dei tuoi servizi.

Puoi deridere, falsificare o qualsiasi altra cosa con le librerie esterne aggiungendo strati di dipendenze di codice (che non controlli) tra la logica che esegue il test e la logica che viene testata.

Quindi un punto minore è che avere la tua astrazione per UnitOfWorke Repositoryti dà il massimo controllo e flessibilità quando deridi i tuoi test unitari.

Tutto molto bene, ma per me, il vero potere di queste astrazioni è che forniscono un modo semplice per applicare le tecniche di programmazione orientata agli aspetti e aderire ai principi SOLID .

Quindi hai il tuo IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

E la sua implementazione:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Finora niente di straordinario, ma ora vogliamo aggiungere un po 'di registrazione - facile con un decoratore di registrazione .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Tutto fatto e senza modifiche al nostro codice esistente . Ci sono numerose altre preoccupazioni trasversali che possiamo aggiungere, come la gestione delle eccezioni, la memorizzazione nella cache dei dati, la convalida dei dati o qualsiasi altra cosa e durante il nostro processo di progettazione e costruzione la cosa più preziosa che abbiamo che ci consente di aggiungere semplici funzionalità senza modificare il nostro codice esistente è la nostra IRepositoryastrazione .

Ora, molte volte ho visto questa domanda su StackOverflow: "come si fa a far funzionare Entity Framework in un ambiente multi tenant?".

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Se hai Repositoryun'astrazione, la risposta è "è facile aggiungere un decoratore"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO dovresti sempre posizionare una semplice astrazione su qualsiasi componente di terze parti a cui verrà fatto riferimento in più di una manciata di posti. Da questo punto di vista, un ORM è il candidato perfetto in quanto è referenziato in gran parte del nostro codice.

La risposta che normalmente viene in mente quando qualcuno dice "perché dovrei avere un'astrazione (ad es. Repository) Su questa o quella libreria di terze parti" è "perché non dovresti?"

I decoratori PS sono estremamente semplici da applicare utilizzando un contenitore IoC, come SimpleInjector .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

11

Innanzitutto, come suggerito da qualche risposta, EF stesso è un pattern di repository, non c'è bisogno di creare ulteriore astrazione solo per nominarlo come repository.

Repository mockable per test unitari, ne abbiamo davvero bisogno?

Consentiamo a EF di comunicare per testare il DB in unit test per testare la nostra logica di business direttamente rispetto al DB di test SQL. Non vedo alcun vantaggio nell'avere una derisione di qualsiasi modello di repository. Cosa c'è di veramente sbagliato nel fare unit test sul database di test? Poiché si tratta di operazioni di massa non sono possibili e finiamo per scrivere SQL grezzo. SQLite in memoria è il candidato perfetto per eseguire test unitari su database reali.

Astrazione inutile

Vuoi creare un repository solo in modo che in futuro tu possa facilmente sostituire EF con NHbibernate ecc. O qualsiasi altra cosa? Sembra un ottimo piano, ma è davvero conveniente?

Linq uccide i test unitari?

Mi piacerebbe vedere alcuni esempi su come può uccidere.

Iniezione di dipendenze, IoC

Wow, queste sono grandi parole, certo che in teoria sembrano grandiose, ma a volte devi scegliere un compromesso tra un ottimo design e un'ottima soluzione. Abbiamo usato tutto questo e abbiamo finito per buttare tutto nella spazzatura e scegliere un approccio diverso. Dimensioni e velocità (dimensioni del codice e velocità di sviluppo) sono importanti nella vita reale. Gli utenti hanno bisogno di flessibilità, a loro non importa se il tuo codice ha un design eccezionale in termini di DI o IoC.

A meno che non si stia creando Visual Studio

Tutti questi ottimi design sono necessari se stai creando un programma complesso come Visual Studio o Eclipse che sarà sviluppato da molte persone e deve essere altamente personalizzabile. Tutti i grandi schemi di sviluppo sono entrati in scena dopo anni di sviluppo che questi IDE hanno attraversato e si sono evoluti in un punto in cui tutti questi fantastici modelli di progettazione contano così tanto. Ma se stai facendo un semplice libro paga basato sul Web o una semplice app aziendale, è meglio che tu evolva nel tuo sviluppo nel tempo, invece di spendere tempo per costruirlo per milioni di utenti dove verrà distribuito solo per centinaia di utenti.

Repository come visualizzazione filtrata - ISecureRepository

Dall'altro lato, il repository dovrebbe essere una visualizzazione filtrata di EF che protegge l'accesso ai dati applicando il riempimento necessario in base all'utente / ruolo corrente.

Ma farlo complica ancora di più il repository in quanto finisce in un'enorme base di codice da mantenere. Le persone finiscono per creare diversi repository per diversi tipi di utenti o combinazioni di tipi di entità. Non solo questo, finiamo anche con un sacco di DTO.

La risposta seguente è un'implementazione di esempio di Filtered Repository senza creare un intero set di classi e metodi. Potrebbe non rispondere direttamente alla domanda, ma può essere utile per derivarne una.

Dichiarazione di non responsabilità: sono l'autore di Entity REST SDK.

http://entityrestsdk.codeplex.com

Tenendo presente quanto sopra, abbiamo sviluppato un SDK che crea un repository di viste filtrate basate su SecurityContext che contiene filtri per le operazioni CRUD. E solo due tipi di regole semplificano qualsiasi operazione complessa. Il primo è l'accesso all'entità e l'altro è la regola di lettura / scrittura per la proprietà.

Il vantaggio è che non si riscrive la logica aziendale o i repository per diversi tipi di utenti, ma si limita semplicemente a bloccare o concedere loro l'accesso.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Queste regole LINQ vengono valutate rispetto al database nel metodo SaveChanges per ogni operazione e queste regole fungono da firewall davanti al database.


3
Test di unità rispetto a un DB significa che hai requisiti esterni aggiuntivi per i tuoi test. Se quel DB è inattivo, i dati vengono cancellati o succede qualcosa a quel DB, i test falliranno. Questo non è desiderato. I repository che espongono IQueryable richiedono circa 2 minuti per l'installazione. Nessun tempo perso qui. Perché ti ci è voluto molto tempo? Tutto questo richiede minuti. Dirò che tutto ha funzionato alla grande per testare le mie query complesse nel mio livello di servizio. È stato così bello non aver bisogno di un database a cui connettersi. Ottenere un framework beffardo da nuget ha richiesto circa un minuto. Questa roba non richiede tempo.
user441521

@ user441521 Repository con IQueryable 2 minuti da configurare? in quale mondo vivi, ogni richiesta asp.net sul nostro sito live viene servita in pochi millisecondi. Deridere e simulare ecc. Aggiunge più complessità al codice, la sua totale perdita di tempo. I test unitari sono inutili quando l'unità non è definita come unità di logica aziendale.
Akash Kava

7

Si discute molto su quale metodo sia corretto, quindi lo considero entrambi accettabili, quindi uso quello che mi piace di più (che non è un repository, UoW).

In EF UoW viene implementato tramite DbContext ei DbSet sono repository.

Per quanto riguarda il modo in cui lavorare con il livello dati, lavoro direttamente sull'oggetto DbContext, per query complesse creerò metodi di estensione per la query che possono essere riutilizzati.

Credo che Ayende abbia anche alcuni post su come astrarre le operazioni CUD sia negativo.

Creo sempre un'interfaccia e il mio contesto eredita da essa in modo da poter utilizzare un contenitore IoC per DI.


Quindi i metodi di estensione, quanto sono estesi? Diciamo che devo ottenere lo stato di un'altra entità nella mia estensione? Questa è la mia più grande preoccupazione in questo momento. Ti dispiace mostrare alcuni esempi di metodi di estensione?
Dejan.S

ayende.com/blog/153473/… e ayende.com/blog/153569/… . (Queste sono recensioni di un'architettura (Framework?) Chiamata s # arp lite. Perlopiù buona ma non è d'accordo con i repository e le astrazioni CUD).
Josh

È basato su NHibernate. Non hai esempi di utilizzo di EF? E ancora, quando ho bisogno di chiamare un'altra entità, come è possibile ottenerla al meglio nel metodo di estensione statica?
Dejan.S

3
Questo va bene fino a quando una proprietà del tuo oggetto di dominio deve essere idratata da dati che non sono memorizzati nel tuo database; o è necessario passare a una tecnologia più performante del tuo ORM gonfio. OOPS! Un ORM NON è semplicemente un sostituto per un repository, è un dettaglio di implementazione di uno.
cdaq

2

Ciò che più si applica a EF non è un modello di repository. Si tratta di un modello Facade (che riassume le chiamate ai metodi EF in versioni più semplici e facili da usare).

EF è quello che applica il modello di repository (e anche il modello di unità di lavoro). Cioè, EF è quello che astrae il livello di accesso ai dati in modo che l'utente non abbia idea di avere a che fare con SQLServer.

E a questo punto, la maggior parte dei "repository" su EF non sono nemmeno buone Facades poiché si limitano a mappare, in modo abbastanza diretto, a singoli metodi in EF, fino al punto di avere le stesse firme.

Le due ragioni, quindi, per applicare questo cosiddetto pattern "Repository" su EF sono per consentire test più facili e per stabilire un sottoinsieme di chiamate "predefinite" ad esso. Non male di per sé, ma chiaramente non un repository.


1

Linq è oggi un 'Repository'.

ISession + Linq è già il repository e non sono necessari né GetXByYmetodi né QueryData(Query q)generalizzazioni. Essendo un po 'paranoico all'uso di DAL, preferisco ancora l'interfaccia del repository. (Dal punto di vista della manutenibilità dobbiamo anche avere qualche facciata su specifiche interfacce di accesso ai dati).

Ecco il repository che usiamo: ci separa dall'uso diretto di nhibernate, ma fornisce l'interfaccia linq (come l'accesso a ISession in casi eccezionali, che sono soggetti a refactoring).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

Cosa fai per il livello di servizio?
Dejan.S

I controller interrogano il repository per dati di sola lettura, perché aggiungere un ulteriore livello? L'altra possibilità è utilizzare "ContentService" che tende sempre più ad essere un repository a livello di servizio: GetXByY, etc.etc. Per le operazioni di modifica - i servizi applicativi sono solo astrazioni su casi d'uso - usano BL e repo liberamente ..
mikalai

Sono abituato a fare il livello di servizio per la logica aziendale. Non sono proprio sicuro di seguire quello che fai con ContentService, per favore approfondisci. Sarebbe una cattiva pratica fare classi di supporto come "livello di servizio"?
Dejan.S

Con "livello di servizio" intendevo "servizi applicativi". Possono utilizzare il repository e qualsiasi altra parte pubblica del livello di dominio. "Livello di servizio" non è una cattiva pratica, ma eviterei di creare la classe XService solo per fornire il risultato List <X>. Il campo del commento sembra essere troppo breve per descrivere i servizi in dettaglio, mi dispiace.
mikalai

E se, diciamo un calcolo del carrello e hai bisogno di ottenere i parametri delle impostazioni dell'applicazione e parametri specifici del cliente per eseguire un calcolo, e questo viene riutilizzato in diversi punti dell'applicazione. Come gestisci questa situazione? classe di supporto o servizio applicativo?
Dejan.S

1

Il repository (o comunque si scelga di chiamarlo) in questo momento per me riguarda principalmente l'astrazione dello strato di persistenza.

Lo uso insieme agli oggetti query, quindi non ho un accoppiamento con nessuna tecnologia particolare nelle mie applicazioni. Inoltre facilita molto i test.

Quindi, tendo ad averlo

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Eventualmente aggiungere metodi asincroni con callback come delegati. Il repository è facile da implementare genericamente , quindi sono in grado di non toccare una riga dell'implementazione da app a app. Beh, questo è vero almeno quando si usa NH, l'ho fatto anche con EF, ma mi ha fatto odiare EF. 4. La conversazione è l'inizio di una transazione. Molto interessante se alcune classi condividono l'istanza del repository. Inoltre, per NH, un repo nella mia implementazione equivale a una sessione che viene aperta alla prima richiesta.

Quindi gli oggetti query

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Per la configurazione utilizzo in NH solo per passare in ISession. In EF non ha più o meno senso.

Una query di esempio potrebbe essere .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Per eseguire una query EF dovresti avere il contesto nella base Abstract, non la sessione. Ma ovviamente l'ifc sarebbe lo stesso.

In questo modo le query sono esse stesse incapsulate e facilmente testabili. Soprattutto, il mio codice si basa solo sulle interfacce. Tutto è molto pulito. Gli oggetti di dominio (aziendali) sono proprio questo, ad esempio, non vi è alcuna combinazione di responsabilità come quando si utilizza il modello di record attivo che è difficilmente verificabile e mescola il codice di accesso ai dati (query) nell'oggetto di dominio e nel farlo mescola le preoccupazioni (oggetto che recupera si??). Tutti sono ancora liberi di creare POCO per il trasferimento dei dati.

Tutto sommato, con questo approccio viene fornito molto riutilizzo e semplicità del codice senza nulla che io possa immaginare. Qualche idea?

E grazie mille ad Ayende per i suoi fantastici post e la continua dedizione. Sono le sue idee qui (oggetto query), non le mie.


1
Le entità di persistenza (i tuoi POCO) NON sono entità aziendali / di dominio. E lo scopo del repository è separare il livello aziendale (qualsiasi) dalla persistenza.
MikeSW

Non riesco a vedere l'accoppiamento. Un po 'd'accordo sulla parte POCO, ma non importa. Niente per impedirti di avere POCO "autentici" e continuare a utilizzare questo approccio.
h.alex

1
Le entità non devono essere affatto stupide POCO. In effetti, modellare la logica di business in Entities è ciò che il pubblico DDD fa sempre. Questo stile di sviluppo si fonde molto bene con NH o EF.
chris

1

Per me è una decisione semplice, con relativamente pochi fattori. I fattori sono:

  1. I repository sono per le classi di dominio.
  2. In alcune delle mie app, le classi di dominio sono le stesse delle mie classi di persistenza (DAL), in altre no.
  3. Quando sono uguali, EF mi fornisce già i repository.
  4. EF fornisce caricamento lento e IQueryable. Mi piacciono questi.
  5. Astrazione / 'facading' / reimplementazione del repository su EF di solito significa perdita di pigro e IQueryable

Quindi, se la mia app non può giustificare il n. 2, separare i modelli di dominio e dati, di solito non mi preoccuperò del n. 5.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.