Preparing for Dapper (Part 4)

So far, I have the POCOs and the database context. If I let the application access the database context directly, it binds the application to the database context. This breaks the goal of making sure that the application is data store agnostic. What's needed is a mediator between the database context and the application itself.

I created, well for the lack of a better name, the "repository". Note that it's not an implementation of the repository pattern per se.

DbRepostoryBase.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace Mendz.Data
{
    public abstract class DbRepositoryBase<D> : IDisposable
        where D : IDbDataContext, new()
    {
        protected IDbDataContext DbDataContext { get; set; }

        protected bool DbDataContextOwner { get; set; } = false;

        protected DbRepositoryBase()
        {
            CreateDbDataContext();
        }

        protected DbRepositoryBase(IDbDataContext dbDataContext)
        {
            DbDataContext = dbDataContext;
        }

        protected void CreateDbDataContext()
        {
            if (DbDataContext == null)
            {
                DbDataContext = new D();
                DbDataContextOwner = true;
            }
        }

        #region IDisposable Support
        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (DbDataContextOwner)
                    {
                        DbDataContext.Dispose();
                        disposed = true;
                    }
                }
                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        #endregion
    }
}
This code defines an IDisposable base repository that mediates between the application and the data source. Note that the DbRepositoryBase class focuses on managing the database context. It deliberately allows for the database context to be injected via generics. To enable repositories to share the database context, implementers can create an internal constructor that accepts an IDbDataContext parameter. The InternalsVisibleToAttribute can be used to expose the sharing capability between assemblies of repositories.

[assembly: InternalsVisibleTo("TestRepositories2")]
namespace TestRepositories1
{
    public class POCONameRepository 
        : DbRepositoryBase<TestDbDataContext>
    {
        public POCONameRepository()
            : base()
        {

        }

        internal POCONameRepository(IDbDataContext dbDataContext)
            : base(dbDataContext)
        {

        }
    }
}
This code illustrates an implementation of DbRepositoryBase with an internal constructor that accepts an IDbDataContext parameter. As an internal constructor, it is only available to the TestRepositories1 assembly and to friend assembly TestRepositories2. This allows repositories to call other repositories and share the same database context. Applications will not have access to the repository's internal constructor.

In order to guide the developers on how create, read, update, delete and search (CRUDS) methods will be defined, I then created interfaces for each CRUDS operation (IDbDataCreatable, IDbDataReadable, IDbDataUpdatable, IDbDataDeletable and IDbDataSearchable). Developers have the option to implement only the operations needed for the repository. If the interface has been implemented but a variation of the same operation needs to be created, then the developer is allowed to do so with a different method name (ex. IDbDataDeletable.Delete and DeleteByParentID). If the method signature suggested by the interface is not good enough for their needs, they can drop the interface inheritance and define their own method instead. Although it is suggested that they try to implement using the built-in CRUDS interfaces first, there is no real point trying to enforce it and to restrict the team.

The CRUDS interfaces are paired with stored procedure templates, which are also provided to the team. The stored procedure templates align with the parameters of the CRUDS interfaces. Again, these are provided as guides. They are not given to be enforced or to restrict the team.

Comments