10

I followed a tutorial on Generic Repository Pattern with ASP.NET core with EF CORE, here for example

   public class Repository<T> : IRepository<T> where T : class
   {
    protected readonly DbContext _dbContext;
    protected readonly DbSet<T> _dbSet;

    public Repository(DbContext context)
    {
        _dbContext = context ?? throw new 
         ArgumentException(nameof(context));
        _dbSet = _dbContext.Set<T>();
    }

    public void Add(T entity)
    {
       _dbSet.Add(entity);
    }
   }

since this is using EF Core we can just use it pre defined methods for insert data through Add method, but when it comes to dapper it needs sql query, then how can i create this generic interface suitable for Dapper ?

Lakshan
  • 297
  • 1
  • 3
  • 14
  • Did you take a look at [Dapper.Contrib](https://github.com/StackExchange/Dapper/tree/master/Dapper.Contrib)? It provides extension methods for connections similar to a repository pattern. – Dirk Sep 20 '18 at 06:11
  • @Dirk, it is helpful, but what would be the way to query table joining – Lakshan Sep 20 '18 at 06:21
  • It is Dapper, you write SQL by yourself and have fun. You can add a bunch of helpful generic method for managing connection, transactions, etc in your base repository, however you have to queries for every entity. – KozhevnikovDmitry Sep 20 '18 at 06:29
  • https://stackoverflow.com/a/45460483/5779732 – Amit Joshi Sep 20 '18 at 12:44
  • Unit of Work implementation with Dapper: https://stackoverflow.com/a/45029588/5779732 – Amit Joshi Nov 12 '21 at 07:45

2 Answers2

6

We had a project which we had dapper generic repository but after project evolved we have ditched the generic repository to use the real power of dapper.

I would recommend to use Dapper as direct without generic CRUD operations.

To demonstrate what we had i will provide a sample code which is not production ready will give you an idea to implement your own generic repo.

public abstract class ConnectionBase : IDbConnection
{

    protected ConnectionBase(IDbConnection connection)
    {
        Connection = connection;
    }

    protected IDbConnection Connection { get; private set; }

    // Verbose but necessary implementation of IDbConnection:
    #region "IDbConnection implementation"

    public string ConnectionString
    {
        get
        {
            return Connection.ConnectionString;
        }

        set
        {
            Connection.ConnectionString = value;
        }
    }

    public int ConnectionTimeout
    {
        get
        {
            return Connection.ConnectionTimeout;
        }
    }

    public string Database
    {
        get
        {
            return Connection.Database;
        }
    }

    public ConnectionState State
    {
        get
        {
            return Connection.State;
        }
    }

    public IDbTransaction BeginTransaction()
    {
        return Connection.BeginTransaction();
    }



    public void Close()
    {
        Connection.Close();
    }

    public IDbCommand CreateCommand()
    {
        return Connection.CreateCommand();
    }

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

    public void Open()
    {
        Connection.Open();
    }

    #endregion
}

Generic Repository

public abstract class GenericRepository<T> : IRepository<T> where T : class //EntityBase, IAggregateRoot
    {

        private readonly string _tableName;


        internal IDbConnection Connection
        {
            get
            {
                return new SqlConnection(ConfigurationManager.ConnectionStrings["SmsQuizConnection"].ConnectionString);
            }
        }

        public GenericRepository(string tableName)
        {
            _tableName = tableName;
        }

        internal virtual dynamic Mapping(T item)
        {
            return item;
        }

        public virtual void Add(T item)
        {
            using (IDbConnection cn = Connection)
            {
                var parameters = (object)Mapping(item);
                cn.Open();
                item.ID = cn.Insert<Guid>(_tableName, parameters);
            }
        }

        public virtual void Update(T item)
        {
            using (IDbConnection cn = Connection)
            {
                var parameters = (object)Mapping(item);
                cn.Open();
                cn.Update(_tableName, parameters);
            }
        }

        public virtual void Remove(T item)
        {
            using (IDbConnection cn = Connection)
            {
                cn.Open();
                cn.Execute("DELETE FROM " + _tableName + " WHERE ID=@ID", new { ID = item.ID });
            }
        }

        public virtual T FindByID(Guid id)
        {
            T item = default(T);

            using (IDbConnection cn = Connection)
            {
                cn.Open();
                item = cn.Query<T>("SELECT * FROM " + _tableName + " WHERE ID=@ID", new { ID = id }).SingleOrDefault();
            }

            return item;
        }



        public virtual IEnumerable<T> FindAll()
        {
            IEnumerable<T> items = null;

            using (IDbConnection cn = Connection)
            {
                cn.Open();
                items = cn.Query<T>("SELECT * FROM " + _tableName);
            }

            return items;
        }



    }
Eldho
  • 7,795
  • 5
  • 40
  • 77
  • How would you pass _tableName from controller? How would you declare GenericRepository in startup class? – Rajon Tanducar Nov 07 '19 at 13:28
  • We inject the name via constructor when creating a repo. Like this EmploeeRepository(employee) – Eldho Nov 07 '19 at 14:33
  • In .net core we pass DI through IOC container in startup. So how would you pass table name from startup? – Rajon Tanducar Nov 10 '19 at 06:37
  • You should have concrete class. Like `EmpoyeeRepo` you should only pass connection string to the Repo from the startup. Passing the table information should be part of the `EmployeeRepo` – Eldho Nov 10 '19 at 06:47
5

The examples by @PathumLakshan request from comments. Provided examples are written in asynchronous manner, but of source can be implemented synchronous. Anyway it is just an illustration of how you can manage instrastructure with Dapper. Class Db provides some generic methods for getting data and executing SQL queries. For instance you can use overload Get<T>(string, object) for basic queries, or take Get<T>(Func<SqlConnection, SqlTransaction, int, Task<T>> to use let say QueryMultiple. Class Repository<Entity> shows, how can look basic repository for entity Entity.

Db class:

public class Db : IDb
{
    private readonly Func<SqlConnection> _dbConnectionFactory;

    public Db(Func<SqlConnection> dbConnectionFactory)
    {
        _dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory));
    }

    public async Task<T> CommandAsync<T>(Func<SqlConnection, SqlTransaction, int, Task<T>> command)
    {
        using (var connection = _dbConnectionFactory.Invoke())
        {
            await connection.OpenAsync();

            using (var transaction = connection.BeginTransaction())
            {
                try
                {
                    var result = await command(connection, transaction, Constants.CommandTimeout);

                    transaction.Commit();

                    return result;
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    Logger.Instance.Error(ex);
                    throw;
                }
            }
        }
    }

    public async Task<T> GetAsync<T>(Func<SqlConnection, SqlTransaction, int, Task<T>> command)
    {
        return await CommandAsync(command);
    }

    public async Task<IList<T>> SelectAsync<T>(Func<SqlConnection, SqlTransaction, int, Task<IList<T>>> command)
    {
        return await CommandAsync(command);
    }

    public async Task ExecuteAsync(string sql, object parameters)
    {
        await CommandAsync(async (conn, trn, timeout) =>
        {
            await conn.ExecuteAsync(sql, parameters, trn, timeout);
                return 1;
        });

    public async Task<T> GetAsync<T>(string sql, object parameters)
    {
        return await CommandAsync(async (conn, trn, timeout) =>
        {
            T result = await conn.QuerySingleAsync<T>(sql, parameters, trn, timeout);
            return result;
        });
    }

    public async Task<IList<T>> SelectAsync<T>(string sql, object parameters)
    {
        return await CommandAsync<IList<T>>(async (conn, trn, timeout) =>
        {
            var result = (await conn.QueryAsync<T>(sql, parameters, trn, timeout)).ToList();
            return result;
        });
    }
}

Repository class:

public class Repository<Entity> : IRepository<Entity>
{
    protected readonly IDb _db;

    public Repository(IDb db)
    {
        _db = db ?? throw new
            ArgumentException(nameof(db));
    }

    public async Task Add(Entity entity)
    {
        await _db.ExecuteAsync("INSERT INTO ... VALUES...", entity);
    }

    public async Task Update(Entity entity)
    {
        await _db.ExecuteAsync("UPDATE ... SET ...", entity);
    }

    public async Task Remove(Entity entity)
    {
        await _db.ExecuteAsync("DELETE FROM ... WHERE ...", entity);
    }

    public async Task<Entity> FindByID(int id)
    {
        return await _db.GetAsync<Entity>("SELECT ... FROM ... WHERE Id = @id", new { id });
    }

    public async Task<IEnumerable<Entity>> FindAll()
    {
        return await _db.SelectAsync<Entity>("SELECT ... FROM ... ", new { });
    }
}

Db can be extended with other generic method, for example, ExecuteScalar, which you would need in your repositories. Hope it helps.

KozhevnikovDmitry
  • 1,660
  • 12
  • 27