Dapper 的存储库设计模式

Repository Design Pattern with Dapper

这可能更像是一个代码审查问题,而不是堆栈溢出问题。

我正在为 MicroORM 使用 Dapper 来检索数据并将其保存到 SQL Server 2014。我在 DTO 项目中有 DTO classes,代表从数据库检索或保存的数据到数据库。

我正在使用存储库模式,因此如果需要存储库,我会在我的服务层使用构造函数 DI 来注入该依赖项,然后调用存储库上的方法来完成工作。

假设我有 2 个服务,分别称为 CustomerService 和 CarService。

然后我有 2 个存储库,一个 CustomerRepository 和一个 CarRepository。

我有一个接口,它定义了每个存储库中的所有方法,然后是具体实现。

示例方法如下所示(调用存储过程执行 DB INSERT(注意存储过程的实际字符串变量定义为 class 顶部的私有字符串):

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

一切正常,但我发现自己在每个存储库的每个方法中重复使用块。下面列出了两个真正的问题。

是否有更好的方法,我可以使用 BaseRepository class 以某种方式使用,每个其他 Repository 都继承自该 BaseRepository 并且 Base 将实现 DB 连接的实例化?

系统上的多个并发用户仍然可以正常工作吗?

****更新****

根据 Silas 的回答,我创建了以下内容

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

使用通用类型 T 将这些添加到我的基本存储库的正确方法是什么,这样我就可以 return 任何类型的 DTO 或任何 C# 本机类型

当然,创建和处理您的连接的功能非常有用。

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

具有 Return 个值:

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 实现了 Repository 模式。缺点是,我必须到处写查询;它非常粘稠。由于硬编码查询,几乎不可能编写通用存储库。

最近,我升级了我的代码以使用 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 class 的包装器。 DapperExtensionsProxy 在内部还管理 UnitOfWork,您可以在下面看到它。 这两个class可以毫无问题地结合起来。我个人更喜欢将它们分开。

您还可以注意到其他方法 ExistsDeleteByIdDeleteAll 已实现,它们不属于 DapperExtensionsProxy

每个 POCO class 中定义了方法 poco.SetDbId 以设置其标识符 属性。就我而言,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,解释为 here

我知道这是一个很老的问题,但我还是想提个建议。

Dapper.SimpleRepository 是一个 NuGet 包,它已经为您完成了创建基于 Dapper 的存储库的所有工作。它为您提供基本的 CRUD 方法以及使用过滤器、完整查询、存储过程等的能力。它支持异步和非异步。它将与框架、标准和核心一起工作。

它给了你两个选择。假设 Foo 是一个 C# class 镜像数据库 table...

选项 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

对于超越基本 crud 的所有方法(并且有很多),请参阅 GitHub 页面。

(完全公开...我创建了 NuGet 包。)