Pattern generico di repository + EF e unità di lavoro

4

Sono nuovo in questo modello di repository e ho visto molte configurazioni di repository + implementazioni UoW su Internet e non sono in grado di giungere a una conclusione su quale di esse sia corretta. Dopo aver esaminato molti link, sono riuscito a implementarne uno.

Considerando i seguenti punti in testa

  • Dovrebbe soddisfare i principi SOLID
  • Sii testabile
  • Sii indipendente del quadro
  • Essere indipendente dal DB

Ecco il codice dell'implementazione

IRepository generico

 public interface IRepository<T> where T : class
    {

        void Add(T entity);

        void Update(T entity);

        void Delete(T entity);

        T GetByKey(object id);

    }

RepositoryBase

public abstract class RepositoryBase<D, T> : IRepository<T> where T : class where D : BaseDbContext
{


    private D dataContext;
    private readonly IDbSet<T> dbSet;

    protected IDbFactory<D> DbFactory
    {
        get;
        private set;
    }

    protected D DbContext
    {
        get { return dataContext ?? (dataContext = DbFactory.Init()); }
    }


    protected RepositoryBase(IDbFactory<D> dbFactory)
    {

        DbFactory = dbFactory;
        dbSet = DbContext.Set<T>();

    }


    #region Implementation
    public virtual void Add(T entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Update(T entity)
    {
        dbSet.Attach(entity);
        DbContext.Entry(entity).State = EntityState.Modified;
    }

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


    public T GetByKey(object id)
    {
        return dbSet.Find(id);
    }


    #endregion

}

IUnitofWork, UnitOfWork

public interface IUnitOfWork<D> where D : BaseDbContext
    {
        void Commit();
    }



public class UnitOfWork<D> : IUnitOfWork<D> where D : BaseDbContext, new()
{
    private readonly IDbFactory<D> dbFactory;
    private D dbContext;

    public UnitOfWork(IDbFactory<D> dbFactory)
    {
        this.dbFactory = dbFactory;
    }

    public D DbContext
    {
        get { return dbContext ?? (dbContext = dbFactory.Init()); }
    }

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

IDBFactory, DBFactory

public interface IDbFactory<D> where D : BaseDbContext
{
    D Init();
}




public class DbFactory<D> : Disposable, IDbFactory<D> where D : BaseDbContext, new()
        {
            D dbContext;
            public D Init()
            {
                return dbContext ?? (dbContext = new D());
            }
            protected override void DisposeCore()
            {
                if (dbContext != null)
                    dbContext.Dispose();
            }
        }

BaseDbContext

public abstract class BaseDbContext : DbContext
{
public BaseDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {

        }

}

ProjectDbContext

 public partial class ProjectDbContext : BaseDbContext
    {
        public ProjectDbContext()
            : base("name=ProjectDbContext")
        {
            Database.SetInitializer<ProjectDbContext>(null);
        }


    }

ESEMPIO USO

Regolatore

 public class StudentsController : BaseController
    {

        private IStudentBusiness objstudentbusiness;
        public StudentsController(IStudentBusiness rstudentbusiness)
        {
            objstudentbusiness = rstudentbusiness;
        }


        public JsonResult LoadStudents()
        {

                var data = objstudentbusiness.ListStudents();
                var jsonResult = Json(data, JsonRequestBehavior.AllowGet);
                return jsonResult;

        }

    }

IStudentBAL, StudentBAL

 public interface IStudentBAL
    {
        void SaveStudent(StudentDto student);
        List<StudentDto> ListStudents();
    }


public class StudentBAL : BusinessBase, IStudentBAL
{

    private readonly IStudentRepository objStudentRepository;
    private readonly IUnitOfWork<ProjectDbContext> objIUnitOfWork;

    public StudentBAL(IStudentRepository rIStudentRepository, IUnitOfWork<ProjectDbContext> rIUnitOfWork)
    {
        try
        {
            objStudentRepository = rIStudentRepository;
            objIUnitOfWork = rIUnitOfWork;
        }
        catch (Exception ex)
        {

            Log.Error(ex);
        }

    }

    public List<StudentDto> ListStudents()
    {
        try
        {
            var tusrs = objStudentRepository.ListStudents() ?? new List<StudentDto>();
            return tusrs;
        }
        catch (Exception ex)
        {
            Log.Error(ex);

        }
        return new List<StudentDto>();
    }
}

IStudentRepository, StudentRepository

 public interface IStudentRepository
    {
        void SaveStudent(Student Student);
        StudentDto GetStudentByName(StudentDto Studentname);
        Student GetStudentByID(int Studentid);
        List<StudentDto> ListStudents();
    }

public class StudentRepository : RepositoryBase<ProjectDbContext, Student>, IStudentRepository
{
    public StudentRepository(IDbFactory<ProjectDbContext> dbFactory) : base(dbFactory)
    {
    }
    public List<StudentDto> ListStudents()
    {

            var students = (from t in DbContext.Students

                        select new StudentDto
                        {
                           // all the required properties
                        }).ToList();


            return students;

    }
}
  • L'iniezione delle dipendenze viene eseguita utilizzando AutoFac
  • Il codice per la registrazione è omesso

Ti sto chiedendo se questa sembra una buona implementazione o mi sto perdendo qualcosa?

Apprezzerei qualsiasi feedback sulla mia implementazione che puoi offrire in merito a correttezza, efficienza e suggerimenti. Quindi, ecco le mie domande

  • Questo è liberamente accoppiato?
  • Ha qualche astrazione che perde e perché?
  • Che cosa è necessario fare per passare da EF a MySQL db e quanto impegno richiederebbe per implementare le modifiche?
  • Questo schema rompe i principi SOLID, Law o Demeter o qualsiasi legge orientata agli oggetti?
  • Questo modello ha delle ridondanze di codice che non sono richieste?
  • In che modo questa architettura che utilizza EF si ridimensionerà con un progetto contenente più di 100 entità di dominio e con ciascuna entità con almeno più di 10 campi. Sarà in seguito incubo di manutenzione?

-Tutte le critiche sono molto apprezzate !!

    
posta Codebadger 14.02.2018 - 14:10
fonte

1 risposta

6

It should satisfy SOLID principles

Un obiettivo lodevole, ma ti aiuta a soddisfare i requisiti funzionali e non funzionali del tuo software?

Lo scopo dei principi SOLID è quello di aiutarti a scrivere codice migliore; non essere un obiettivo, un requisito o una metrica a sé stante. Se stai spendendo tempo per misurare i progressi del tuo progetto osservando e categorizzando la rigorosa aderenza del codice ai principi SOLID, stai sbagliando.

Be testable

Anche un obiettivo lodevole. Ma francamente, la maggior parte di questo codice è solo plumbing, e non è particolarmente interessante da una prospettiva di testing unitario. Semplificare il codice renderebbe più semplice il test e la maggior parte dei test sarebbero comunque test di integrazione.

Be independent of the framework/db

Il valore di questo requisito è discutibile a meno che tu non sappia che cambierai i quadri più tardi. Come hai già notato, avere questo tipo di flessibilità richiede molta disciplina per disaccoppiare le tue implementazioni, e talvolta questo livello di rigore non vale il costo.

La stragrande maggioranza dei progetti software non modifica mai le loro decisioni di framework o DB. Sebbene sia giusto, al mio ultimo lavoro, l'ORM è stato modificato due o tre volte (il database non è mai stato modificato, è rimasto SQL Server per tutta la durata del progetto).

Dependency Injection uses AutoFac

Hai incluso un Interface per il tuo contenitore DI in modo che tu possa cambiarlo più tardi, se lo desideri? Nota che questa possibilità è più probabile che modificare il framework o il DB.

Logging code is omitted

Hai incluso un Interface per la tua implementazione di registrazione in modo che tu possa cambiarlo più tardi, se lo desideri? Nota che questa possibilità è più probabile che cambiare il framework o il DB.

Hai considerato le implicazioni trasversali del logging? Ogni classe prende come riferimento la tua interfaccia ILogger o hai adottato una strategia di gestione delle eccezioni?

Is this loosely coupled?

Sì e no. Ovunque tu abbia usato un'interfaccia? Sì. Ovunque hai ereditato una classe base o preso un riferimento concreto? No. Suppongo che RepositoryBase sia il luogo in cui si implementa il repository generico specifico del provider; è una tecnica perfettamente valida, purché non ti preoccupi di riscriverlo quando cambi il tuo DB / ORM.

Is it having any leaky abstraction and why?

EF è di per sé un'astrazione che perde. Le Entità che stai passando a EF dall'implementazione BaseRepository contengono tutti i tipi di perdite, ma l'unico modo per evitarlo è quello di separarle fornendo il tuo insieme di entità che si associano alle Entità EF. La gestione dei problemi di rilevamento delle modifiche risultanti sarà complicata; probabilmente non ne vale la pena.

What needs to be done to switch from EF to MySQL db and how much effort would it require to implement the changes?

EF e MySQL non sono paragonabili. EF è una mappatura oggetto-relazionale e unità di lavoro; MySQL è un sistema di database relazionale. MySQL è un sostituto per SQL Server, non EF. L'EF viene normalmente utilizzato insieme al server SQL, sebbene i provider siano disponibili per altri database, quindi l'opzione è disponibile.

Tuttavia, per ottenere un'autentica indipendenza dal database, è necessario conformarsi a ANSI SQL, il che significa che sacrificherete le funzionalità specifiche del fornitore del database scelto, come i miglioramenti delle prestazioni specifici di SQL. Probabilmente vorrai evitare del tutto le stored procedure.

Is this pattern breaking any of the SOLID principles, Law or Demeter or any Object Oriented laws?

Non ho valutato il tuo codice su questa base. Basti dire che, in base ai tuoi obiettivi di progettazione, il tuo approccio al design (e il tuo codice) sembra abbastanza ragionevole.

Does this pattern have any code redundancies which are not required?

Non che io possa vedere, in base ai tuoi requisiti di progettazione.

How this architecture using EF would scale with a project containing more than 100+ domain entities and with each entity having at least more than 10 fields. Is it going to be maintenance nightmare later on?

Ho usato con successo EF con queste molte entità. EF offre aiuto nella creazione delle classi di entità necessarie dal database, presupponendo che si esegua la progettazione in base al database. Mantenere le cose dritte in un ambiente con più sviluppatori di software (e più ambienti: sviluppo, gestione temporanea, produzione) può essere complicato; una scivolata nel nullability di un campo può abbattere un intero sistema di produzione se non stai attento, e il compilatore non lo catturerà perché è perfettamente C #.

Altri pensieri

Il codice che hai postato supporta solo le operazioni CRUD. L'80% della tua applicazione potrebbe funzionare correttamente usando solo metodi di creazione, lettura, aggiornamento e cancellazione, ma l'altro 20 percento richiederà l'SQL personalizzato se ti aspetti che funzioni in modo adeguato e si adatti bene ai tuoi casi d'uso. Hai tenuto conto di questa possibilità?

Considera l'uso di un micro-ORM come Dapper invece di uno dei pesi massimi come EF o ibernato. Dapper ti offre tutte le operazioni CRUD che desideri, offre un metodo SQL Execute che è praticamente identico al metodo SQL Execute di EF per le occasioni in cui è indicato l'SQL personalizzato e offre prestazioni decisamente migliori rispetto a EF out of the box.

    
risposta data 14.02.2018 - 21:05
fonte