web-dev-qa-db-fra.com

Modèle de référentiel générique + EF et unité d'oeuvre

Je suis nouveau dans ce modèle de référentiel et j'ai vu beaucoup de modèles de référentiel + implémentations UoW partout sur Internet et je ne suis pas en mesure de parvenir à une conclusion quant à laquelle est correcte. Après avoir parcouru de nombreux liens, j'ai pu en implémenter un.

Compte tenu des points suivants à l'esprit

  • Il doit satisfaire aux principes SOLID
  • Soyez testable
  • Soyez indépendant du cadre
  • Être indépendant de DB

Voici le code de l'implémentation

IRepository générique

 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);
        }


    }

EXEMPLE D'UTILISATION

Contrôleur

 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'injection de dépendance se fait à l'aide d'AutoFac
  • Le code de journalisation est omis

Je demande si cela semble être une bonne mise en œuvre ou si je manque quelque chose?

J'apprécierais tout commentaire sur ma mise en œuvre que vous pourriez offrir concernant l'exactitude, l'efficacité et toute suggestion. Donc, voici mes questions

  • Est-ce vaguement couplé?
  • Y a-t-il une abstraction qui fuit et pourquoi?
  • Que faut-il faire pour passer d'EF à MySQL db et combien d'efforts faudrait-il pour implémenter les changements?
  • Ce modèle brise-t-il l'un des principes SOLID SOLID, Law ou Demeter ou toute loi orientée objet?
  • Ce modèle a-t-il des redondances de code qui ne sont pas nécessaires?
  • Comment cette architecture utilisant EF évoluerait avec un projet contenant plus de 100+ entités de domaine et avec chaque entité ayant au moins plus de 10 champs. Est-ce que ça va être un cauchemar de maintenance plus tard?

- Toutes les critiques très appréciées !!

7
Codebadger

Il doit satisfaire aux principes SOLID

Un objectif louable, mais cela vous aide-t-il à satisfaire les exigences fonctionnelles et non fonctionnelles de votre logiciel?

Le but des principes SOLID est de vous aider à écrire un meilleur code; non à être un objectif, une exigence ou une métrique à part entière. Si vous passez du temps, vous mesurez votre projet progresser en observant et en catégorisant la stricte adhésion de votre code aux principes SOLID, vous vous trompez.

Soyez testable

Également un objectif louable. Mais franchement, la plupart de ce code n'est que de la plomberie, et ce n'est pas particulièrement intéressant du point de vue des tests unitaires. La simplification du code faciliterait les tests, et la plupart des tests seraient de toute façon des tests d'intégration.

Soyez indépendant du framework/db

La valeur de cette exigence est discutable, sauf si vous savez que vous allez modifier les cadres ultérieurement. Comme vous l'avez déjà remarqué, avoir ce type de flexibilité nécessite beaucoup de discipline pour découpler vos implémentations, et parfois ce niveau de rigueur n'en vaut pas le coût.

La grande majorité des projets logiciels ne modifient jamais leur cadre ou leurs décisions de base de données. Bien que pour être juste, lors de mon dernier travail, l'ORM a été modifié deux ou trois fois (la base de données n'a jamais été modifiée; elle est restée SQL Server pendant toute la durée de vie du projet).

L'injection de dépendances utilise AutoFac

Avez-vous inclus un Interface pour votre conteneur DI afin que vous puissiez le changer plus tard, si vous le souhaitez? Notez que cette possibilité est plus probable que de changer le cadre ou la base de données.

Le code de journalisation est omis

Avez-vous inclus un Interface pour votre implémentation de la journalisation afin de pouvoir le modifier plus tard, si vous le souhaitez? Notez que cette possibilité est plus probable que de changer le cadre ou la base de données.

Avez-vous considéré les implications transversales de l'exploitation forestière? Chaque classe prend-elle une référence à votre interface ILogger ou avez-vous adopté une stratégie de gestion des exceptions?

Est-ce vaguement couplé?

Oui et non. Partout où vous avez utilisé une interface? Oui. Partout où vous avez hérité d'une classe de base ou pris une référence concrète? Non. Je suppose que RepositoryBase est l'endroit où vous implémentez votre référentiel générique spécifique au fournisseur; c'est une technique parfaitement valable, tant que cela ne vous dérange pas de la réécrire lorsque vous changez votre DB/ORM.

Y a-t-il une abstraction qui fuit et pourquoi?

EF est lui-même une abstraction qui fuit. Les entités que vous passez vers et depuis EF dans votre implémentation BaseRepository contiennent toutes sortes de fuites, mais la seule façon d'éviter cela est de la découpler en fournissant votre propre ensemble d'entités qui mappent vers les entités EF. La gestion des problèmes de suivi des modifications qui en résultera sera compliquée; ça ne vaut probablement pas la peine.

Que faut-il faire pour passer d'EF à MySQL db et combien d'efforts faudrait-il pour implémenter les changements?

EF et MySQL ne sont pas comparables. EF est un mappeur objet-relationnel et une implémentation d'unité de travail; MySQL est un système de base de données relationnelle. MySQL est un remplacement pour SQL Server, pas EF. EF est normalement utilisé en conjonction avec SQL Server, bien que les fournisseurs soient disponibles pour d'autres bases de données, donc l'option est là.

Cependant, pour obtenir une véritable indépendance vis-à-vis de la base de données, vous devrez vous conformer à ANSI SQL, ce qui signifie que vous sacrifierez toutes les fonctionnalités spécifiques au fournisseur de votre base de données choisie, telles que les améliorations de performances spécifiques à SQL. Vous voudrez probablement éviter complètement les procédures stockées.

Ce modèle brise-t-il l'un des principes SOLID, Loi ou Déméter ou toute loi orientée objet?

Je n'ai pas évalué votre code sur cette base. Il suffit de dire que, selon vos objectifs de conception, votre approche de conception (et votre code) semble raisonnablement solide.

Ce modèle a-t-il des redondances de code qui ne sont pas nécessaires?

Pas que je puisse voir, en fonction de vos exigences de conception.

Comment cette architecture utilisant EF évoluerait avec un projet contenant plus de 100+ entités de domaine et avec chaque entité ayant au moins plus de 10 champs. Est-ce que ça va être un cauchemar de maintenance plus tard?

J'ai utilisé EF avec succès avec autant d'entités. EF offre de l'aide pour créer les classes d'entités nécessaires à partir de la base de données, en supposant que vous effectuez la conception avant tout de la base de données. Garder les choses dans un environnement avec plusieurs développeurs de logiciels (et plusieurs environnements: développement, mise en scène, production) peut être délicat; un glissement dans la nullité d'un champ peut détruire tout un système de production si vous n'y faites pas attention, et le compilateur ne le rattrapera pas car il est parfaitement valide en C #.

Quelques autres réflexions

Le code que vous avez publié prend uniquement en charge les opérations CRUD. Quatre-vingt pour cent de votre application peuvent fonctionner correctement en utilisant simplement les méthodes Créer, Lire, Mettre à jour et Supprimer, mais les 20 pour cent restants nécessiteront un SQL personnalisé si vous vous attendez à ce qu'il fonctionne correctement et soit adapté à vos cas d'utilisation. Avez-vous pris en compte cette possibilité?

Pensez à utiliser un micro-ORM comme Dapper au lieu d'un des poids lourds comme EF ou nHibernate. Dapper vous donnera toutes les opérations CRUD que vous souhaitez, propose une méthode Execute SQL qui est pratiquement identique à la méthode EF Execute SQL pour les occasions où le SQL personnalisé est indiqué, et a des performances bien meilleures que EF hors de la boîte.

8
Robert Harvey