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可以毫无问题地结合起来。我个人更喜欢将它们分开。
您还可以注意到其他方法 Exists
、DeleteById
和 DeleteAll
已实现,它们不属于 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 包。)
这可能更像是一个代码审查问题,而不是堆栈溢出问题。
我正在为 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可以毫无问题地结合起来。我个人更喜欢将它们分开。
您还可以注意到其他方法 Exists
、DeleteById
和 DeleteAll
已实现,它们不属于 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 包。)