Dapper是.NET的一款轻量级ORM工具(GitHub),也可称为简单对象映射器。在速度方面拥有微型ORM之王的称号。
它是半自动的,也就是说实体类和SQL语句都要本身写,但它提供自动对象映射。是经过对IDbConnection接口的扩展来操做数据库的。git
优势:github
下面简单建立一个Web API应用并经过Dapper访问MySQL数据。sql
建立MySQL测试数据数据库
CREATE SCHEMA `ormdemo` ; CREATE TABLE `ormdemo`.`category` ( `Id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(45) NOT NULL, PRIMARY KEY (`Id`)); CREATE TABLE `ormdemo`.`product` ( `Id` INT NOT NULL AUTO_INCREMENT, `Name` VARCHAR(45) NOT NULL, `Price` DECIMAL(19,2) NULL, `Quantity` INT NULL, `CategoryId` INT NOT NULL, PRIMARY KEY (`Id`), INDEX `fk_product_category_idx` (`CategoryId` ASC), CONSTRAINT `fk_product_category` FOREIGN KEY (`CategoryId`) REFERENCES `ormdemo`.`category` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION); INSERT INTO `ormdemo`.`category` (`Name`) VALUES("Phones"); INSERT INTO `ormdemo`.`category` (`Name`) VALUES("Computers"); INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone8",4999.99,10,1); INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone7",2999.99,10,1); INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP750",6000.00,5,2); INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP5000",12000.00,10,2);
建立Web API应用并添加NuGet引用api
Install-Package MySql.Data Install-Package Dapper
新建一个Product类mvc
public class Category { public int Id { get; set; } public string Name { get; set; } } public class Product { public int Id { get; set; } public string Name { get; set; } public int Quantity { get; set; } public decimal Price { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } }
新建一个DBConfig类用于建立并返回数据库链接oracle
using MySql.Data.MySqlClient; using System.Data; using System.Configuration; public class DBConfig { //ConfigurationManager.ConnectionStrings["Connection"].ConnectionString; private static string DefaultSqlConnectionString = @"server=127.0.0.1;database=ormdemo;uid=root;pwd=Open0001;SslMode=none;"; public static IDbConnection GetSqlConnection(string sqlConnectionString = null) { if (string.IsNullOrWhiteSpace(sqlConnectionString)) { sqlConnectionString = DefaultSqlConnectionString; } IDbConnection conn = new MySqlConnection(sqlConnectionString); conn.Open(); return conn; } }
建立简单的仓储接口和类app
public interface IProductRepository { Task<bool> AddAsync(Product prod); Task<IEnumerable<Product>> GetAllAsync(); Task<Product> GetByIDAsync(int id); Task<bool> DeleteAsync(int id); Task<bool> UpdateAsync(Product prod); }
public class ProductRepository : IProductRepository { public async Task<IEnumerable<Product>> GetAllAsync() { using (IDbConnection conn = DBConfig.GetSqlConnection()) { return await conn.QueryAsync<Product>(@"SELECT Id ,Name ,Quantity ,Price ,CategoryId FROM Product"); } } public async Task<Product> GetByIDAsync(int id) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { string sql = @"SELECT Id ,Name ,Quantity ,Price ,CategoryId FROM Product WHERE Id = @Id"; return await conn.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id }); } } public async Task<bool> AddAsync(Product prod) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { string sql = @"INSERT INTO Product (Name ,Quantity ,Price ,CategoryId) VALUES (@Name ,@Quantity ,@Price ,@CategoryId)"; return await conn.ExecuteAsync(sql, prod) > 0; } } public async Task<bool> UpdateAsync(Product prod) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { string sql = @"UPDATE Product SET Name = @Name ,Quantity = @Quantity ,Price= @Price ,CategoryId= @CategoryId WHERE Id = @Id"; return await conn.ExecuteAsync(sql, prod) > 0; } } public async Task<bool> DeleteAsync(int id) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { string sql = @"DELETE FROM Product WHERE Id = @Id"; return await conn.ExecuteAsync(sql, new { Id = id }) > 0; } } }
在Startup ConfigureServices方法里面配置依赖注入async
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IProductRepository, ProductRepository>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
在Controller里面调用仓储方法
[Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private readonly IProductRepository _productRepository; public ProductController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet] public async Task<IActionResult> Get() { var data = await _productRepository.GetAllAsync(); return Ok(data); } [HttpGet("{id}")] public async Task<IActionResult> Get(int id) { var data = await _productRepository.GetByIDAsync(id); return Ok(data); } [HttpPost] public async Task<IActionResult> Post([FromBody] Product prod) { if (!ModelState.IsValid) { return BadRequest(ModelState); } await _productRepository.AddAsync(prod); return NoContent(); } [HttpPut("{id}")] public async Task<IActionResult> Put(int id, [FromBody] Product prod) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var model = await _productRepository.GetByIDAsync(id); model.Name = prod.Name; model.Quantity = prod.Quantity; model.Price = prod.Price; await _productRepository.UpdateAsync(model); return NoContent(); } [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { await _productRepository.DeleteAsync(id); return NoContent(); } }
测试API是否能够正常工做
Dapper对存储过程和事务的支持
存储过程
using (var connection = My.ConnectionFactory()) { connection.Open(); var affectedRows = connection.Execute(sql, new {Kind = InvoiceKind.WebInvoice, Code = "Single_Insert_1"}, commandType: CommandType.StoredProcedure); My.Result.Show(affectedRows); }
事务
using (var connection = My.ConnectionFactory()) { connection.Open(); using (var transaction = connection.BeginTransaction()) { var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction: transaction); transaction.Commit(); } }
Dapper对多表映射的支持
var selectAllProductWithCategorySQL = @"select * from product p inner join category c on c.Id = p.CategoryId Order by p.Id"; var allProductWithCategory = connection.Query<Product, Category, Product>(selectAllProductWithCategorySQL, (prod, cg) => { prod.Category = cg; return prod; });
Dapper Contrib扩展Dapper提供了CRUD的方法
添加NuGet引用Dapper.Contrib
Install-Package Dapper.Contrib
为Product类添加数据注解
[Table("Product")] public class Product { [Key] public int Id { get; set; } public string Name { get; set; } public int Quantity { get; set; } public decimal Price { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } }
增长一个新的仓储类继承
public class ContribProductRepository : IProductRepository { public async Task<bool> AddAsync(Product prod) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { return await conn.InsertAsync(prod) > 0; } } public async Task<IEnumerable<Product>> GetAllAsync() { using (IDbConnection conn = DBConfig.GetSqlConnection()) { return await conn.GetAllAsync<Product>(); } } public async Task<Product> GetByIDAsync(int id) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { return await conn.GetAsync<Product>(id); } } public async Task<bool> DeleteAsync(int id) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { var entity = await conn.GetAsync<Product>(id); return await conn.DeleteAsync(entity); } } public async Task<bool> UpdateAsync(Product prod) { using (IDbConnection conn = DBConfig.GetSqlConnection()) { return await conn.UpdateAsync(prod); } } }
修改Startup ConfigureServices方法里面配置依赖注入
services.AddTransient<IProductRepository, ContribProductRepository>();
测试,这样能够少写了很多基本的SQL语句。
其余一些开源的Dapper扩展
类库 | 提供的方法 |
---|---|
Dapper.SimpleCRUD | Get GetList GetListPaged Insert Update Delete DeleteList RecordCount |
Dapper Plus | Bulk Insert Bulk Delete Bulk Update Bulk Merge Bulk Action Async Bulk Also Action Bulk Then Action |
Dapper.FastCRUD | Get Find Insert Update BulkUpdate Delete BulkDelete Count |
Dapper.Mapper | Multi-mapping |
仓储模式每每须要工做单元模式的介入来负责一系列仓储对象的持久化,确保数据完整性。网上关于工做单元模式的实现方式有多种,但其本质都是工做单元类经过建立一个全部仓储共享的数据库上下文对象,来组织多个仓储对象。
网上的一些实现方式:
Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application
微软以前给出的一个示例,仓储类作为工做单元的变量,并经过工做单元传入一致的context参数建立。
public class UnitOfWork : IDisposable { private SchoolContext context = new SchoolContext(); private GenericRepository<Department> departmentRepository; private GenericRepository<Course> courseRepository; public GenericRepository<Department> DepartmentRepository { get { if (this.departmentRepository == null) { this.departmentRepository = new GenericRepository<Department>(context); } return departmentRepository; } } public GenericRepository<Course> CourseRepository { get { if (this.courseRepository == null) { this.courseRepository = new GenericRepository<Course>(context); } return courseRepository; } } public void Save() { context.SaveChanges(); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践
博客园一位大神的总结,最终采用的方案是仓储类负责查询,工做单元类负责增删改等数据持久化操做。
优缺点不做讨论,适合本身的就是最好的,这里采用了另一种实现方式:
定义DapperDBContext
public interface IContext : IDisposable { bool IsTransactionStarted { get; } void BeginTransaction(); void Commit(); void Rollback(); } public abstract class DapperDBContext : IContext { private IDbConnection _connection; private IDbTransaction _transaction; private int? _commandTimeout = null; private readonly DapperDBContextOptions _options; public bool IsTransactionStarted { get; private set; } protected abstract IDbConnection CreateConnection(string connectionString); protected DapperDBContext(IOptions<DapperDBContextOptions> optionsAccessor) { _options = optionsAccessor.Value; _connection = CreateConnection(_options.Configuration); _connection.Open(); DebugPrint("Connection started."); } #region Transaction public void BeginTransaction() { if (IsTransactionStarted) throw new InvalidOperationException("Transaction is already started."); _transaction = _connection.BeginTransaction(); IsTransactionStarted = true; DebugPrint("Transaction started."); } public void Commit() { if (!IsTransactionStarted) throw new InvalidOperationException("No transaction started."); _transaction.Commit(); _transaction = null; IsTransactionStarted = false; DebugPrint("Transaction committed."); } public void Rollback() { if (!IsTransactionStarted) throw new InvalidOperationException("No transaction started."); _transaction.Rollback(); _transaction.Dispose(); _transaction = null; IsTransactionStarted = false; DebugPrint("Transaction rollbacked and disposed."); } #endregion Transaction #region Dapper Execute & Query public async Task<int> ExecuteAsync(string sql, object param = null, CommandType commandType = CommandType.Text) { return await _connection.ExecuteAsync(sql, param, _transaction, _commandTimeout, commandType); } public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text) { return await _connection.QueryAsync<T>(sql, param, _transaction, _commandTimeout, commandType); } public async Task<T> QueryFirstOrDefaultAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text) { return await _connection.QueryFirstOrDefaultAsync<T>(sql, param, _transaction, _commandTimeout, commandType); } public async Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, object param = null, string splitOn = "Id", CommandType commandType = CommandType.Text) { return await _connection.QueryAsync(sql, map, param, _transaction, true, splitOn, _commandTimeout, commandType); } #endregion Dapper Execute & Query public void Dispose() { if (IsTransactionStarted) Rollback(); _connection.Close(); _connection.Dispose(); _connection = null; DebugPrint("Connection closed and disposed."); } private void DebugPrint(string message) { #if DEBUG Debug.Print(">>> UnitOfWorkWithDapper - Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, message); #endif } }
定义UnitOfWork
public interface IUnitOfWork : IDisposable { void SaveChanges(); } public class UnitOfWork : IUnitOfWork { private readonly IContext _context; public UnitOfWork(IContext context) { _context = context; _context.BeginTransaction(); } public void SaveChanges() { if (!_context.IsTransactionStarted) throw new InvalidOperationException("Transaction have already been commited or disposed."); _context.Commit(); } public void Dispose() { if (_context.IsTransactionStarted) _context.Rollback(); } }
定义UnitOfWorkFactory
public interface IUnitOfWorkFactory { IUnitOfWork Create(); } public class DapperUnitOfWorkFactory : IUnitOfWorkFactory { private readonly DapperDBContext _context; public DapperUnitOfWorkFactory(DapperDBContext context) { _context = context; } public IUnitOfWork Create() { return new UnitOfWork(_context); } }
定义服务扩展
public class DapperDBContextOptions : IOptions<DapperDBContextOptions> { public string Configuration { get; set; } DapperDBContextOptions IOptions<DapperDBContextOptions>.Value { get { return this; } } } public static class DapperDBContextServiceCollectionExtensions { public static IServiceCollection AddDapperDBContext<T>(this IServiceCollection services, Action<DapperDBContextOptions> setupAction) where T : DapperDBContext { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (setupAction == null) { throw new ArgumentNullException(nameof(setupAction)); } services.AddOptions(); services.Configure(setupAction); services.AddScoped<DapperDBContext, T>(); services.AddScoped<IUnitOfWorkFactory, DapperUnitOfWorkFactory>(); return services; } }
建立一个本身的Context并继承DapperDBContext。下面测试的TestDBContext是采用MySQL数据库并返回MySqlConnection。
public class TestDBContext : DapperDBContext { public TestDBContext(IOptions<DapperDBContextOptions> optionsAccessor) : base(optionsAccessor) { } protected override IDbConnection CreateConnection(string connectionString) { IDbConnection conn = new MySqlConnection(connectionString); return conn; } }
仓储类里面添加DapperDBContext引用
public class UowProductRepository : IProductRepository { private readonly DapperDBContext _context; public UowProductRepository(DapperDBContext context) { _context = context; } public async Task<Product> GetByIDAsync(int id) { string sql = @"SELECT Id ,Name ,Quantity ,Price ,CategoryId FROM Product WHERE Id = @Id"; return await _context.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id }); } public async Task<bool> AddAsync(Product prod) { string sql = @"INSERT INTO Product (Name ,Quantity ,Price ,CategoryId) VALUES (@Name ,@Quantity ,@Price ,@CategoryId)"; return await _context.ExecuteAsync(sql, prod) > 0; } }
Startup里面注册服务
public void ConfigureServices(IServiceCollection services) { services.AddDapperDBContext<TestDBContext>(options => { options.Configuration = @"server=127.0.0.1;database=ormdemo;uid=root;pwd=password;SslMode=none;"; }); services.AddTransient<IProductRepository, UowProductRepository>(); services.AddTransient<ICategoryRepository, UowCategoryRepository>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
Controller调用
public class ProductController : ControllerBase { private readonly IUnitOfWorkFactory _uowFactory; private readonly IProductRepository _productRepository; private readonly ICategoryRepository _categoryRepository; public ProductController(IUnitOfWorkFactory uowFactory, IProductRepository productRepository, ICategoryRepository categoryRepository) { _uowFactory = uowFactory; _productRepository = productRepository; _categoryRepository = categoryRepository; } [HttpGet("{id}")] public async Task<IActionResult> Get(int id) { var data = await _productRepository.GetByIDAsync(id); return Ok(data); } [HttpPost] public async Task<IActionResult> Post([FromBody] Product prod) { if (!ModelState.IsValid) { return BadRequest(ModelState); } //await _productRepository.AddAsync(prod); using (var uow = _uowFactory.Create()) { await _productRepository.AddAsync(prod); uow.SaveChanges(); } return NoContent(); } }