4

I'm new to this Repository pattern and have seen lot of repository pattern + UoW implementations all over the internet and I'm not able to reach to a conclusion as to which of them is correct . After going through many links I've been able to implement one .

Considering the following points in mind

  • It should satisfy SOLID principles
  • Be testable
  • Be independent of framework
  • Be independent of DB

Here is the code of the implementation

Generic IRepository

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


    }

EXAMPLE USAGE

Controller

 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;

    }
}
  • Dependency Injection is done using AutoFac
  • Code for logging is omitted

I am asking if this seems like a good implementation or am I missing anything?

I would appreciate any feedback on my implementation you can offer regarding correctness, efficiency, and any suggestions. So here are my questions

  • Is this loosely coupled ?
  • Is it having any leaky abstraction and why ?
  • What needs to be done to switch from EF to MySQL db and how much effort would it require to implement the changes?
  • Is this pattern breaking any of the SOLID princples ,Law or Demeter or any Object Oriented laws?
  • Does this pattern have any code redundancies which are not required ?
  • 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?

-All criticisms greatly appreciated !!

  • EF works nicely with MySQL, but you should use the Pomelo packages not the official oracle packages which still have some problems (esp. with code first I think) so you do not need to switch away from EF. – Bent Feb 14 '18 at 13:30
  • @Bent Thanks and can you please provide some general feedback on the implementation .That'd be great! – Codebadger Feb 14 '18 at 18:56
  • 1
    please don't cross-post: stackoverflow.com/questions/48794274/… "Cross-posting is frowned upon as it leads to fragmented answers splattered all over the network..." – gnat Feb 14 '18 at 21:06
  • This would be more on-topic for codereview.stackexchange.com – Mael Feb 15 '18 at 6:09
  • How did it turn out for you in practise? Any good? Changes? After thoughts? :D I used some of your code +1 – Piotr Kula Jul 16 '18 at 12:49
7

It should satisfy SOLID principles

A laudable goal, but does it help you satisfy any of your software's functional and non-functional requirements?

The purpose of the SOLID principles is to help you write better code; not to be a goal, requirement, or metric in its own right. If you're spending you're time measuring your project's progress by observing and categorizing your code's strict adherence to SOLID principles, you're doing it wrong.

Be testable

Also a laudable goal. But frankly, most of this code is just plumbing, and it's not particularly interesting from a unit testing perspective. Simplifying the code would make it easier to test, and most of the testing would be integration testing anyway.

Be independent of the framework/db

The value of this requirement is debatable unless you know you're going to change frameworks later on. As you've already noticed, having this kind of flexibility requires a lot of discipline to decouple your implementations, and sometimes this level of rigor is not worth the cost.

The vast majority of software projects never change their framework or DB decisions. Though to be fair, at my last job, the ORM was changed out two or three times (the database was never changed; it remained SQL Server throughout the project's lifetime).

Dependency Injection uses AutoFac

Have you included an Interface for your DI container so that you can change that out later, if you wish? Note that this possibility is more likely than changing the framework or DB.

Logging code is omitted

Have you included an Interface for your logging implementation so that you can change that out later, if you wish? Note that this possibility is more likely than changing the framework or DB.

Have you considered the cross-cutting implications of logging? Does every class take a reference to your ILogger interface, or have you adopted an exception-handling strategy?

Is this loosely coupled?

Yes and no. Everywhere you've used an Interface? Yes. Everywhere you've inherited a base class or taken a concrete reference? No. I assume that RepositoryBase is where you implement your provider-specific generic repository; it's a perfectly valid technique, so long as you don't mind rewriting it when you change your DB/ORM.

Is it having any leaky abstraction and why?

EF is itself a leaky abstraction. The Entities you're passing to and from EF in your BaseRepository implementation contain all sorts of leaks, but the only way to avoid that is to decouple it by providing your own set of entities that map to the EF Entities. Managing the resulting change tracking problems is going to be complicated; it's probably not worth the trouble.

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

EF and MySQL are not comparable. EF is an Object-Relational Mapper and Unit of Work implementation; MySQL is a relational database system. MySQL is a replacement for SQL Server, not EF. EF is normally used in conjunction with SQL server, though providers are available for other databases, so the option is there.

However, to achieve true database-independence, you'll need to conform to ANSI SQL, which means you'll sacrifice any vendor-specific features of your chosen database such as SQL-specific performance improvements. You'll probably want to avoid stored procedures altogether.

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

I didn't evaluate your code on this basis. Suffice it to say that, based on your design goals, your design approach (and your code) seems reasonably sound.

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

Not that I can see, based on your design requirements.

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?

I've used EF successfully with this many entities. EF offers help creating the necessary entity classes from the database, assuming you do database-first design. Keeping things straight in an environment with multiple software developers (and multiple environments: development, staging, production) can be tricky; one slip in the nullability of a field can take down an entire production system if you're not careful, and the compiler won't catch it because it's perfectly valid C#.

Some other thoughts

The code you posted only supports CRUD operations. Eighty percent of your application might work fine using merely Create, Read, Update and Delete methods, but the other 20 percent will require custom SQL if you expect it to perform adequately and be a good fit for your use cases. Have you accounted for this possibility?

Consider using a micro-ORM like Dapper instead of one of the heavyweights like EF or nHibernate. Dapper will give you all of the CRUD operations you want, offers an Execute SQL method that is virtually identical to EF's Execute SQL method for those occasions where custom SQL is indicated, and has much better performance than EF out of the box.

  • was +1 other than "Be independent of the framework/db" There is more to db independence than being able to switch databases. It is also about being able to performance optimize your persistence when needed by making drastic structural changes. It is about being able to unit test the code that is interesting to unit test. And, also, about avoiding crappy code that is just a mirror of the DB. Other than that, the rest of the answer deserves 10 upvotes – TheCatWhisperer Feb 14 '18 at 21:45
  • @TheCatWhisperer: I would think that the goal would be to avoid drastic structural changes at all costs. Otherwise, why bother with all this indirection? If you design your data access layer correctly, you can have your generic repository that's database independent, provide a way to tailor your SQL in those areas that need performance tuning (in a vendor-specific way, if necessary), and get the best of both worlds. – Robert Harvey Feb 14 '18 at 21:56
  • Sorry, my comment was not clear. You wrote, in response to, "Be independent of the framework/db"..."The value of this requirement is debatable unless you know you're going to change frameworks later on." – TheCatWhisperer Feb 14 '18 at 22:05
  • "in practice only SQL Server is used" I have to disagree with this statement as I know of a lot of work being done with EF+Postgres and EF+MySQL. And this is not only hobby projects. You are likely correct that most work is done with EF+SQL Server. – Bent Feb 14 '18 at 22:12
  • @TheCatWhisperer: Yes, I understood what you meant. I stand by my statement that large-scale structural changes are something you want to avoid. – Robert Harvey Feb 14 '18 at 23:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.