Шаблон дизайна репозитория с Dapper

Возможно, это больше вопрос для проверки кода, чем для переполнения стека.

Я использую Dapper для MicroORM для извлечения и сохранения данных в SQL Server 2014. У меня есть DTO-классы в DTO Proj, которые представляют Данные, извлеченные из БД или сохраненные в БД.

Я использую шаблон репозитория, поэтому на моем уровне службы, если требуется репозиторий, я использую конструктор DI для внедрения этой зависимости, а затем вызываю метод в репозитории для выполнения работы.

скажем так, у меня есть 2 сервиса под названием CustomerService и CarService.

Затем у меня есть 2 репозитория: репозиторий CustomerRepository и CarRepository.

У меня есть интерфейс, который определяет все методы в каждом репозитории, а затем конкретные реализации.

Пример метода показан ниже (вызов Stored Proc для выполнения INSERT БД (обратите внимание, что фактическая строковая переменная для хранимого proc определяется как закрытая строка в верхней части класса):

    public void SaveCustomer(CustomerDTO custDTO)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            db.Execute(saveCustSp, custDTO, commandType: CommandType.StoredProcedure);
        }
    }

Все это прекрасно работает, но я обнаружил, что повторяю блок using в каждом методе в каждом репозитории. У меня есть два реальных вопроса, изложенных ниже.

Есть ли лучший подход, который я мог бы использовать, возможно, каким-то образом, используя класс BaseRepository, от которого наследуется каждый другой репозиторий, и Base будет реализовывать создание экземпляра соединения с БД?

Будет ли это работать нормально для нескольких одновременно работающих пользователей в системе?

****ОБНОВИТЬ****

На основании ответа Сайласа я создал следующее

public interface IBaseRepository
{
    void Execute(Action<IDbConnection> query);
}

public class BaseRepository: IBaseRepository
{
        public void Execute(Action<IDbConnection> query)
        {
            using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
            {
                query.Invoke(db);
            }
        }
}

Тем не менее, в моих репозиториях у меня есть другие методы, такие как ниже:

    public bool IsOnlyCarInStock(int carId, int year)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            var car = db.ExecuteScalar<int>(anotherStoredSp, new { CarID = carId, Year = year },
                                commandType: CommandType.StoredProcedure);

            return car > 0 ? true : false;
        }
    }

а также

    public IEnumerable<EmployeeDTO> GetEmployeeDetails(int employeeId)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            return db.Query<EmployeeDTO>(anotherSp, new { EmployeeID = employeeId },
                                commandType: CommandType.StoredProcedure);
        }
    }

Как правильно добавить их в мой базовый репозиторий, используя Generic Type T, чтобы я мог вернуть любой тип DTO или любой родной тип C#

2 ответа

Решение

Конечно, функция для создания и удаления вашего соединения будет работать отлично.

protected void Execute(Action<IDbConnection> query)
{
    using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
    {
        query.Invoke(db);
    }
}

И ваш упрощенный сайт звонков:

public void SaveCustomer(CustomerDTO custDTO)
{
    Execute(db => db.Execute(saveCustSp, custDTO, CommandType.StoredProcedure));
}

С возвращаемыми значениями:

public T Get<T>(Func<IDbConnection, T> query)
{
    using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
    {
        return query.Invoke(db); 
    }
}

На вашем сайте звонков просто напишите логику, которую вы хотите использовать.

public IEnumerable<EmployeeDTO> GetEmployeeDetails(int employeeId)
{
    return Get<IEnumerable<EmployeeDTO>(db => 
        db.Query<EmployeeDTO>(anotherSp, new { EmployeeID = employeeId }, CommandType.StoredProcedure));
}

Это не имеет прямого отношения к вашему вопросу. Но я предлагаю вам рассмотреть возможность использования DapperExtensions.

Изначально я реализовал шаблон репозитория с помощью Dapper. Недостатком было то, что я должен писать запросы повсюду; это было очень тяжело. Из-за жестко закодированных запросов было почти невозможно написать общий репозиторий.

Недавно я обновил свой код для использования DapperExtensions. Это исправляет много проблем.

Ниже приводится общий репозиторий:

public abstract class BaseRepository<T> where T : BasePoco
{
    internal BaseRepository(IUnitOfWork unitOfWork)
    {
        dapperExtensionsProxy = new DapperExtensionsProxy(unitOfWork);
    }

    DapperExtensionsProxy dapperExtensionsProxy = null;

    protected bool Exists()
    {
        return (GetCount() == 0) ? false : true;
    }

    protected int GetCount()
    {
        var result = dapperExtensionsProxy.Count<T>(null);
        return result;
    }

    protected T GetById(Guid id)
    {
        var result = dapperExtensionsProxy.Get<T>(id);
        return result;
    }
    protected T GetById(string id)
    {
        var result = dapperExtensionsProxy.Get<T>(id);
        return result;
    }

    protected List<T> GetList()
    {
        var result = dapperExtensionsProxy.GetList<T>(null);
        return result.ToList();
    }

    protected void Insert(T poco)
    {
        var result = dapperExtensionsProxy.Insert(poco);
    }

    protected void Update(T poco)
    {
        var result = dapperExtensionsProxy.Update(poco);
    }

    protected void Delete(T poco)
    {
        var result = dapperExtensionsProxy.Delete(poco);
    }

    protected void DeleteById(Guid id)
    {
        T poco = (T)Activator.CreateInstance(typeof(T));
        poco.SetDbId(id);
        var result = dapperExtensionsProxy.Delete(poco);
    }
    protected void DeleteById(string id)
    {
        T poco = (T)Activator.CreateInstance(typeof(T));
        poco.SetDbId(id);
        var result = dapperExtensionsProxy.Delete(poco);
    }

    protected void DeleteAll()
    {
        var predicateGroup = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
        var result = dapperExtensionsProxy.Delete<T>(predicateGroup);//Send empty predicateGroup to delete all records.
    }

Как видно из приведенного выше кода, большинство методов являются просто оберткой над DapperExtensionsProxy учебный класс. DapperExtensionsProxy внутренне также управляет UnitOfWork, который вы можете увидеть ниже. Эти два класса могут быть объединены без каких-либо проблем. Я лично предпочитаю держать их отдельно.

Вы также можете заметить, что дополнительные методы Exists, DeleteById, а также DeleteAll реализуются те, которые не являются частью DapperExtensionsProxy,

метод poco.SetDbId определяется в каждом классе POCO, чтобы установить его свойство Identifier. В моем случае идентификаторы POCO могут иметь разные типы данных и имена.

Следующий DapperExtensionsProxy:

internal sealed class DapperExtensionsProxy
{
    internal DapperExtensionsProxy(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
    }

    IUnitOfWork unitOfWork = null;

    internal int Count<T>(object predicate) where T : BasePoco
    {
        var result = unitOfWork.Connection.Count<T>(predicate, unitOfWork.Transaction);
        return result;
    }

    internal T Get<T>(object id) where T : BasePoco
    {
        var result = unitOfWork.Connection.Get<T>(id, unitOfWork.Transaction);
        return result;
    }

    internal IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort = null, bool buffered = false) where T : BasePoco
    {
        var result = unitOfWork.Connection.GetList<T>(predicate, sort, unitOfWork.Transaction, null, buffered);
        return result;
    }

    internal IEnumerable<T> GetPage<T>(object predicate, int page, int resultsPerPage, IList<ISort> sort = null, bool buffered = false) where T : BasePoco
    {
        var result = unitOfWork.Connection.GetPage<T>(predicate, sort, page, resultsPerPage, unitOfWork.Transaction, null, buffered);
        return result;
    }

    internal dynamic Insert<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Insert<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal void Insert<T>(IEnumerable<T> listPoco) where T : BasePoco
    {
        unitOfWork.Connection.Insert<T>(listPoco, unitOfWork.Transaction);
    }

    internal bool Update<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Update<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal bool Delete<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Delete<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal bool Delete<T>(object predicate) where T : BasePoco
    {
        var result = unitOfWork.Connection.Delete<T>(predicate, unitOfWork.Transaction);
        return result;
    }
}

Ниже приводится BasePoco используется выше:

public abstract class BasePoco
{
    Guid pocoId = Guid.NewGuid();

    public Guid PocoId { get { return pocoId; } }

    public virtual void SetDbId(object id)
    {//Each POCO should override this method for specific implementation.
        throw new NotImplementedException("This method is not implemented by Poco.");
    }

    public override string ToString()
    {
        return PocoId + Environment.NewLine + base.ToString();
    }
}

Это также использует UnitOfWork, который объясняется здесь.

Я знаю, что это очень старый вопрос, но я все же хотел сделать предложение.

https://www.nuget.org/packages/Dapper.SimpleRepository/ - это пакет NuGet, который уже проделал за вас всю работу по созданию репозитория, построенного на основе Dapper. Он предоставляет вам основные методы CRUD, а также возможность использовать фильтры, полные запросы, сохраненные процедуры и т. Д. Он поддерживает как асинхронный, так и неасинхронный режим. И он будет работать с Framework, Standard и Core.

Это дает вам два варианта. ПредполагаяFoo - это класс C#, который отражает таблицу базы данных...

Вариант 1. Создайте репозиторий, введя строку подключения и определив тип.

Dapper.SimpleRepository.Repository<Foo> fooRepo = new Dapper.SimpleRepository.Repository<Foo>("your connection string");

Тогда базовый CRUD так же прост, как:

fooRepo.Insert(foo);    // Add a record to the database
fooRepo.Get(55);             // Get a sinlge item from the database by Id
fooRepo.Update(foo);    // Update a record in the database
fooRepo.Delete(55);          // Delete a single object from the database by Id

Вариант 2: Создайте репозиторий, введя строку подключения, но НЕ определяйте тип.

Dapper.SimpleRepository.Repository repo = new Dapper.SimpleRepository.Repository("your connection string");

Тогда ваши методы CRUD выглядят так:

repo.Insert<Foo>(foo);    // Add a record to the database
repo.Get<Foo>(55);        // Get a sinlge item from the database by Id
repo.Update<Foo>(foo);    // Update a record in the database
repo.Delete<Foo>(55);     // Delete a single object from the database by Id

Чтобы узнать обо всех методах, выходящих за рамки базового алгоритма (а их много), см. Страницу GitHub.

(Полное раскрытие... Я создал пакет NuGet.)

Другие вопросы по тегам