Entity Framework Generic Repository Pattern

  • pensiero 1



    public interface IRepository : IDisposable where T : class
    {
    IQueryable Fetch();
    IEnumerable GetAll();
    IEnumerable Find(Func predicate);
    T Single(Func predicate);
    T First(Func predicate);
    void Add(T entity);
    void Delete(T entity);
    void SaveChanges();
    }


    con l'approccio di cui sopra andrò come Repository e quindi avrò un Repository
    con l'implementazione dei metodi scritti sopra.



    Penso che questo liberi i miei test dalla dipendenza da DbContext.



    pensiero 2



     public class RepositoryBase : IDisposable where TContext : DbContext, new()
    {
    private TContext _DataContext;

    protected virtual TContext DataContext
    {
    get
    {
    if (_DataContext == null)
    {
    _DataContext = new TContext();
    }
    return _DataContext;
    }
    }

    public virtual IQueryable GetAll() where T : class
    {
    using (DataContext)
    {
    return DataContext.Set();
    }
    }

    public virtual T FindSingleBy(Expression> predicate) where T : class
    {
    if (predicate != null)
    {
    using (DataContext)
    {
    return DataContext.Set().Where(predicate).SingleOrDefault();
    }
    }
    else
    {
    throw new ArgumentNullException("Predicate value must be passed to FindSingleBy.");
    }
    }

    public virtual IQueryable FindAllBy(Expression> predicate) where T : class
    {
    if (predicate != null)
    {
    using (DataContext)
    {
    return DataContext.Set().Where(predicate);
    }
    }
    else
    {
    throw new ArgumentNullException("Predicate value must be passed to FindAllBy.");
    }
    }

    public virtual IQueryable FindBy(Expression> predicate,Expression> orderBy) where T : class
    {
    if (predicate != null)
    {
    if (orderBy != null)
    {
    using (DataContext)
    {
    return FindAllBy(predicate).OrderBy(orderBy).AsQueryable(); ;
    }
    }
    else
    {
    throw new ArgumentNullException("OrderBy value must be passed to FindBy.");
    }
    }
    else
    {
    throw new ArgumentNullException("Predicate value must be passed to FindBy.");
    }
    }

    public virtual int Save(T Entity) where T : class
    {
    return DataContext.SaveChanges();
    }
    public virtual int Update(T Entity) where T : class
    {
    return DataContext.SaveChanges();
    }
    public virtual int Delete(T entity) where T : class
    {
    DataContext.Set().Remove(entity);
    return DataContext.SaveChanges();
    }
    public void Dispose()
    {
    if (DataContext != null) DataContext.Dispose();
    }
    }


    Ora avrei potuto CustomerRepository : RepositoryBase,ICustomerRepository
    ICustomerRepositorydarmi la possibilità di definire cose come GetCustomerWithOrdersecc...



    Sono confuso su quale approccio adottare. Avrebbe bisogno di aiuto attraverso una revisione sull'implementazione, quindi commenti su quale sarà l'approccio più semplice quando si tratta di test ed estensibilità.


    Perché non avere entrambi e fare in modo che RepositoryBase implementi l'interfaccia IRepository?

    @dreza quindi, IRepository dovrebbe avere metodi come add , save , delete o dovrebbe avere i metodi find , get , fetch ? Immagino che entrambi stiano affrontando problemi diversi, un insieme di metodi è la query e l'altro è la logica transazionale, e la domanda successiva è quali sono i vantaggi/svantaggi di fare in modo che RepositoryBase implementi l'interfaccia IRepository?

    Farei in modo che il repository abbia metodi come SingleOrDefault(), GetAll(), FirstOrDefault() piuttosto che dover fare .Fetch().SingleOrDefault() ecc

    Informazioni sulla modifica 2: i test di RepositoryBase devono essere "integrati" poiché DbContext non è astratto o non ha una buona interfaccia, ma un bel trucco è usare Database.SetInitializer (nuovo DropCreateDatabaseAlways ()) in quelle prove. Ciò significa che hai sempre un nuovo DB per i tuoi test integrati. È anche utile spostare le classi e i test Repository e UnitOfWork concreti in assembly separati, in modo da non avere tali dipendenze nei test e nelle classi di business logic. Altrimenti potresti sempre andare a comprare TypeMock che può deridere più o meno qualsiasi cosa.

    @Lars-Erik So RepositoryBase verrà testato tramite un test di integrazione con un database. Dovrei comunque essere in grado di testare la mia logica aziendale attraverso i test unitari. Le mie classi POCO sono in un assembly separato e il repository si trova in un assembly separato. Il mio progetto di unit test ha solo un riferimento al repository. È la struttura corretta?

    Mantieni due progetti di unit test. Uno che fa riferimento all'assembly poco (che non ha dipendenza da concrete uow/repo) e uno che fa riferimento a entrambi per i test integrati.

    Cosa posso testare nel progetto che fa riferimento all'assembly POCO? Mi aspettavo di inviare un SMS alla logica nel mio repository in un progetto e di avere un'integrazione supportata da db in un altro.

    Il repository conrete è strettamente accoppiato al tuo ORM (Entity Framework), quindi è per definizione un'integrazione di backend DB. L'interfaccia del repository, tuttavia, non lo è, quindi può essere derisa e utilizzata nei test di logica aziendale.

  • Lars-Erik

    Lars-Erik Risposta corretta

    9 anni fa

    Dovresti usare Expression>come predicati sulla tua interfaccia e avere l' RepositoryBaseimplementazione di quell'interfaccia.
    Usando solo Func, non otterrai la traduzione in L2E ecc., Ma dovrai enumerare l'intera tabella DB prima di poter valutare il Func.
    L'interfaccia può essere derisa, quindi testata senza un db fisico e utilizzata anche con altri ORM.



    In genere si preferisce mantenere SaveChangese DbContextin IUnitOfWorkun'implementazione separata . Puoi passare UnitOfWorkal costruttore del repository, che può accedere a una proprietà interna sull'UOW esponendo il DbContext.



    In questo modo puoi condividere lo stesso DbContexttra repository e aggiornamenti batch su diverse radici di entità di tipo diverso. Mantieni anche meno connessioni aperte, che sono la risorsa più costosa che hai quando si tratta di DB.



    Per non dimenticare che possono condividere le transazioni. :)



    Quindi non dovresti eliminare il DbContext nel Repository, il Repository non ha davvero bisogno di essere usa e getta. Ma UnitOfWork / DbContext deve essere eliminato da qualcosa.



    Inoltre, elimina il OrderBypredicato in FindBy. Poiché restituisci un IQueryable, e usi un Expressionper il predicato, puoi continuare a creare Queryable dopo la chiamata. Per esempio repo.FindBy(it => it.Something == something).OrderBy(it => it.OrderProperty). Verrà comunque tradotto in "seleziona [campi] da [tabella] dove [predicato] ordina per [proprietà ordine]" quando enumerato.



    Altrimenti sembra buono.



    Ecco un paio di buoni esempi:



    http://blog.swink.com.au/index.php/c-sharp/generic-repository-for-entity-framework-4-3-dbcontext-with-code-first/



    http://www.martinwilley.com/net/code/data/genericrepository.html



    http://www.mattdurrant.com/ef-code-first-with-the-repository-and-unit-of-work-patterns/



    Ed ecco come lo faccio:



    Modello / Comune / Assemblaggio aziendale



    Nessun riferimento ma BCL e altri possibili modelli (interfacce/dtos)



    public interface IUnitOfWork : IDisposable
    {
    void Commit();
    }

    public interface IRepository
    {
    void Add(T item);
    void Remove(T item);
    IQueryable Query();
    }

    // entity classes


    Assemblaggio prove aziendali



    Solo riferimenti Modello / Comune / Assemblaggio aziendale



    [TestFixture]
    public class BusinessTests
    {
    private IRepository repo;
    private ConcreteService service;

    [SetUp]
    public void SetUp()
    {
    repo = MockRepository.GenerateStub>();
    service = new ConcreteService(repo);
    }

    [Test]
    public void Service_DoSomething_DoesSomething()
    {
    var expectedName = "after";
    var entity = new Entity { Name = "before" };
    var list = new List { entity };
    repo.Stub(r => r.Query()).Return(list.AsQueryable());
    service.DoStuff();
    Assert.AreEqual(expectedName, entity.Name);
    }

    }


    Assembly di implementazione di Entity Framework



    Modello di riferimento e assembly Entity Framework / System.Data.Entity



    public class EFUnitOfWork : IUnitOfWork
    {
    private readonly DbContext context;

    public EFUnitOfWork(DbContext context)
    {
    this.context = context;
    }

    internal DbSet GetDbSet()
    where T : class
    {
    return context.Set();
    }

    public void Commit()
    {
    context.SaveChanges();
    }

    public void Dispose()
    {
    context.Dispose();
    }
    }

    public class EFRepository : IRepository
    where T : class
    {
    private readonly DbSet dbSet;

    public EFRepository(IUnitOfWork unitOfWork)
    {
    var efUnitOfWork = unitOfWork as EFUnitOfWork;
    if (efUnitOfWork == null) throw new Exception("Must be EFUnitOfWork"); // TODO: Typed exception
    dbSet = efUnitOfWork.GetDbSet();
    }

    public void Add(T item)
    {
    dbSet.Add(item);
    }

    public void Remove(T item)
    {
    dbSet.Remove(item);
    }

    public IQueryable Query()
    {
    return dbSet;
    }
    }


    Assemblaggio prove integrato



    Riferimenti a tutto



    [TestFixture]
    [Category("Integrated")]
    public class IntegratedTest
    {
    private EFUnitOfWork uow;
    private EFRepository repo;

    [SetUp]
    public void SetUp()
    {
    Database.SetInitializer(new DropCreateDatabaseAlways());
    uow = new EFUnitOfWork(new YourContext());
    repo = new EFRepository(uow);
    }

    [TearDown]
    public void TearDown()
    {
    uow.Dispose();
    }

    [Test]
    public void Repository_Add_AddsItem()
    {
    var expected = new Entity { Name = "Test" };
    repo.Add(expected);
    uow.Commit();
    var actual = repo.Query().FirstOrDefault(e => e.Name == "Test");
    Assert.IsNotNull(actual);
    }

    [Test]
    public void Repository_Remove_RemovesItem()
    {
    var expected = new Entity { Name = "Test" };
    repo.Add(expected);
    uow.Commit();
    repo.Remove(expected);
    uow.Commit();
    Assert.AreEqual(0, repo.Query().Count());
    }
    }

    Expression v/s Func ..nice catch.. RepositoryBase ha il predicato corretto. Anche OrderBy mi è sembrato inutile. Lo rimuoverò. Il collegamento fornito ha due diverse implementazioni in termini di passaggio di contesto o di passaggio di contesto e tipo di entità come parametri di tipo. Quale è il migliore o sono entrambi uguali e solo una questione di stile. In qualche modo non riesco a capirlo.

    DbContext è di per sé un UoW, quindi quali sarebbero i vantaggi di avere un UoW separato?

    @ashutoshraina possibilmente per astrarre l'UoW stesso. Le persone lo fanno pensando che alla fine dovranno cambiare il loro ORM o l'archiviazione dei dati. Nella mia esperienza, non succede mai e quindi meno astratti e meglio è.

    In genere è meglio prendere in giro un'interfaccia che non ha dipendenze da librerie diverse dal BCL stesso. Anche se non cambierai il tuo ORM, i tuoi test possono essere eseguiti senza dipendere da nient'altro che dal BCL. Da qui la necessità di avvolgere DbContext. Non sarà molto comunque. Per fare un punto, lo abbiamo fatto per EF 1 combinato con il progetto EF Poco Adapter di codeplex. Durante la migrazione a EF 4, abbiamo dovuto sostituire i vecchi contesti e le implementazioni dbset con quelli nuovi e abbiamo tratto vantaggio dall'avere i nostri UOW e Repos.

    @Lars-Erik Vedi la mia modifica 2.

Licenza sotto CC-BY-SA con attribuzione


Contenuto datato prima del 24/07/2021 11:53