Se il modello di deposito è eccessivo per gli ORM moderni (EF, nHibernate), qual è un'astrazione migliore?


12

Di recente ho letto molti argomenti contro l'uso del modello di repository con Entity Framework potente come ORM in quanto incorpora funzionalità simili a repository, insieme alla funzionalità Unit of Work.

Un altro argomento contro l'utilizzo del modello per una situazione come il test unitario è che il modello del repository è un'astrazione che perde perché le implementazioni più generiche sfruttano IQueryable.

Le argomentazioni contro l'uso del modello di repository hanno senso per me, ma i metodi alternativi di astrazione suggeriti sono spesso più confusi e appaiono altrettanto esagerati del problema.

La soluzione di Jimmy Bogards sembra essere un mix di soffiare via le astrazioni, ma anche introdurre la propria architettura. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Un altro esempio di repository inutilmente ... ma usa la mia architettura! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Un altro ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Non ho trovato una chiara sostituzione o alternativa all'approccio "eccessivamente complesso" al modello di repository che non sia più progettato da solo.


4
Cosa specificamente stai cercando di ottenere? Le astrazioni dovrebbero avere uno scopo. Se stai scrivendo un'app CRUD, un ORM è probabilmente abbastanza astratto.
JacquesB,

@JacquesB Sto cercando di evitare problemi di impedenza relazionale degli oggetti con un robusto modello di dominio, ma anche quello astratto lontano dai miei modelli di visualizzazione in un'implementazione di mvc.
AnotherDeveloper,

Reed Cospey ha un sacco di cose positive da dire su IQuerable qui: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Ciò implica che è meglio a livello di trasporto. Per quanto riguarda l'astrazione, ho trovato che l'uso del modello di repository generico funziona bene quando ho bisogno di iniettare l'EntityType ma volevo comunque mantenere metodi comuni. D'altra parte, io stesso ho discusso sul forum MSDN LINQ che EF è un modello di repository perché è tutto in memoria. Un progetto ha utilizzato numerose clausole Where come chiamate di metodo che hanno funzionato bene.
John Peters,

Ma un'area che ho esaminato ma respinto era il business di Expression Tree, ad alcuni piace molto, ma .... devi studiarlo prima di trovarlo utile.
John Peters,

2
dovremmo tornare a chiamare SQL dai controller
bobek

Risposte:


12

Penso che tu stia combinando repository e repository generici.

Un repository di base si interfaccia semplicemente con l'archivio dati e fornisce metodi per restituire i dati

IRepository {
   List<Data> GetDataById(string id);
}

Non perde il livello dati nel codice tramite un IQueryable o altri modi di passare query casuali e fornisce una superficie di metodi testabile e iniettabile ben definita.

Un repository generico consente di passare la query in modo molto simile a un ORM

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

Sono d'accordo che non ha molto senso utilizzare un repository generico in cima a un ORM che è fondamentalmente solo un altro repository generico.

La risposta è utilizzare il modello di repository di base per nascondere il tuo ORM


1
Un ORM di un ORM? Stavo cercando di essere divertente. Perché è necessario sottrarre l'ORM?
johnny,

1
la stessa ragione per cui astrarre qualcosa. per evitare di inquinare il tuo codice con classi proprietarie
Ewan,

6

La maggior parte degli argomenti citati erroneamente attribuiscono alle funzionalità del modello di repository che non ha.

Concettualmente, un repository come originariamente definito in DDD è solo una raccolta di oggetti che è possibile cercare o aggiungere. Il meccanismo di persistenza dietro di esso viene sottratto, quindi come consumatore si ha l'illusione che si tratti di una raccolta in memoria.

Un'implementazione del repository che presenta astrazioni che perdono (esponendo IQueryables . Ad esempio) è un'implementazione del repository scadente.

Un'implementazione del repository che espone più di semplici operazioni di raccolta (ad esempio, le funzioni di Unit of Work) è un'implementazione del repository scadente.

Esistono alternative al repository per l'accesso ai dati? Sì, ma non sono correlati ai problemi che sollevi nella tua domanda.



1

Per me, i repository, combinati con ORM o altri livelli di persistenza DB, presentano questi svantaggi:

  1. Coprire le unità di lavoro. La UoW deve essere codificata dal programmatore e raramente può essere implementata come una sorta di magia in background, in cui l'utente effettua semplicemente query e modifiche, senza definire i confini della UoW e possibilmente il punto di commit. A volte, le UoW vengono abbandonate riducendole in micro UoW (ad es. Sessioni NHibernate) in ciascun metodo di accesso al repository.
  2. Coprire o, nel peggiore dei casi, distruggere l'ignoranza della persistenza: metodi come "Load ()", "Get ()", "Save ()" o "Update ()" suggeriscono operazioni immediate a singolo oggetto, come se l'invio di singoli SQL / DML, o come se si trattasse di file. In effetti, ad esempio, i metodi NHibernate, con questi nomi fuorvianti, di solito non consentono l'accesso individuale, ma si accodano per carico pigro o inseriscono / aggiornano batch (Persistence Ignorance). A volte, i programmatori si chiedono perché non ottengano operazioni DB immediate e rompano forzatamente l'ignoranza della persistenza, uccidendo così le prestazioni e facendo grandi sforzi per peggiorare il sistema (molto!).
  3. Crescita incontrollata. Un semplice repository potrebbe accumulare sempre più metodi per soddisfare esigenze specifiche.

Ad esempio:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. Oggetto Danger of God: potresti essere tentato di creare una classe god, coprendo tutto il tuo modello o livello di accesso ai dati. La classe del repository non conterrebbe solo i metodi Car, ma i metodi per tutte le entità.

A mio avviso, è meglio offrire almeno alcune opportunità di query, per evitare l'enorme casino di molti metodi a scopo singolo. Non importa se si tratta di LINQ, un linguaggio di query proprio o anche qualcosa preso direttamente dall'ORM (OK, tipo di problema di accoppiamento ...).


4
Dove vanno tutti questi metodi se non si utilizza il modello di repository?
johnny,


1

Se lo scopo dell'interfaccia del repository è di deridere il database per unittest (= test in isolamento) la migliore astrazione è qualcosa che è facile da deridere.

È difficile deridere un'interfaccia di repository basata su un risultato IQueryable.

Dal punto di vista del test unitario

IRepository {
   List<Data> GetDataById(string id);
}

può essere deriso facilmente

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

può essere deriso facilmente solo se il mock ignora il contenuto del parametro query.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

non può essere facilmente deriso


0

Non penso che il modello di repository sia eccessivo, se si utilizzano le funzioni lambda per le query. Soprattutto quando devi astrarre l'ORM (secondo me dovresti sempre), allora non mi interesseranno i dettagli di implementazione del repository stesso.

Per esempio:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
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.