ASP.NET Core 中的 ORM 之 Dapper
Dapper 简介
Dapper是.NET的一款轻量级ORM工具(GitHub),也可称为简单对象映射器。在速度方面拥有微型ORM之王的称号。
它是半自动的,也就是说实体类和SQL语句都要自己写,但它提供自动对象映射。是通过对IDbConnection接口的扩展来操作数据库的。
优点:
- 轻量,只有一个文件
- 性能高,Dapper的速度接近与IDataReader,取列表的数据超过了DataTable。
- 支持多种数据库。Dapper可以在所有Ado.net Providers下工作,包括sqlite, sqlce, firebird, oracle, MySQL, PostgreSQL and SQL Server
- 使用Dapper可以自动进行对象映射,通过Emit反射IDataReader的序列队列,来快速的得到和产生对象
使用 Dapper
下面简单创建一个Web API应用并通过Dapper访问MySQL数据。
创建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引用
Install-Package MySql.Data
Install-Package Dapper
新建一个Product类
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类用于创建并返回数据库连接
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;
}
}
创建简单的仓储接口和类
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方法里面配置依赖注入
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 Contrib扩展Dapper提供了CRUD的方法
- Get
- GetAll
- Insert
- Update
- Delete
- DeleteAll
添加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 GetGetListGetListPagedInsertUpdateDeleteDeleteListRecordCountDapper Plus Bulk InsertBulk DeleteBulk UpdateBulk MergeBulk Action AsyncBulk Also ActionBulk Then ActionDapper.FastCRUD GetFindInsertUpdateBulkUpdateDeleteBulkDeleteCountDapper.Mapper Multi-mapping
引入工作单元 Unit of Work
仓储模式往往需要工作单元模式的介入来负责一系列仓储对象的持久化,确保数据完整性。网上关于工作单元模式的实现方式有多种,但其本质都是工作单元类通过创建一个所有仓储共享的数据库上下文对象,来组织多个仓储对象。
网上的一些实现方式:
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();
}
}
源代码
参考
ASP.NET Core 中的 ORM 之 Dapper的更多相关文章
- ASP.NET Core 中的 ORM 之 Entity Framework
目录 EF Core 简介 使用 EF Core(Code First) EF Core 中的一些常用知识点 实体建模 实体关系 种子数据 并发管理 执行 SQL 语句和存储过程 延迟加载和预先加载 ...
- asp.net core 六 Oracle ORM
.netcore 中 Oracle ORM 在真正将项目移植到.netcore下,才发现会有很多问题,例如访问Oracle,问题出现的时间在2017年底 参考连接 ...
- 如何在ASP.NET Core中编写高效的控制器
通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可 ...
- ASP.NET Core 中的那些认证中间件及一些重要知识点
前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...
- Asp.net Core中使用Session
前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...
- 在ASP.NET Core中使用百度在线编辑器UEditor
在ASP.NET Core中使用百度在线编辑器UEditor 0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个.不过服务端只有ASP.NET版的,如果是为了 ...
- ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
随机推荐
- windows下 zookeeper
1.zookeeper的安装和配置 下载:http://zookeeper.apache.org/releases.html 把conf目录下的zoo_sample.cfg改名成zoo.cfg,这里我 ...
- 巧克力分配问题——C语言
某品牌巧克力使用500克原料可制作55小块巧克力,请编程实现:输入原料重量(以千克为单位),计算出制作巧克力的块数(四舍五入).然后对这些巧克力进行分包,小盒放11块,大盒放24块,问各分装多少大盒多 ...
- 探索未知种族之osg类生物---呼吸分解之渲染遍历一
总结 前面我们基本上已经完成对ViewerBase::frame()函数的探究,只剩下renderingTraversals()渲染遍历的探究,虽然就剩下了一个函数,但是这却是最重要的,不可少的一个步 ...
- osg探究补充:DatabasePager类简介
简介 DatabasePager类,也就是常说的数据库分页技术,简单来说,就是在进行数据库查找时,有可能满足条件的数据很多,为了提高相应速度我们进行数据查找时进行分页查找与显示,当点击下一页时才会进行 ...
- 《C#从现象到本质》读书笔记(七)第9章 泛型
<C#从现象到本质>读书笔记(七)第9章 泛型 泛型的三大好处:类型安全,增强性能(避免装箱和拆箱),代码复用. 泛型方法是传入的参数至少有一个类型为T(尚未制定的类型,根据微软的命名规则 ...
- virtual和abstract的区别
virtual和abstract都是用来修饰父类的,前面不能用private私有,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的. 毕竟加上virtual或abstract就是让子类重新定义 ...
- Awesome Python 中文版
Awesome Python ,这又是一个 Awesome XXX 系列的资源整理,由 vinta 发起和维护.内容包括:Web框架.网络爬虫.网络内容提取.模板引擎.数据库.数据可视化.图片处理.文 ...
- 2019.02.14 codechef Chef at the Food Fair(线段树+泰勒展开)
传送门 题意:现在有nnn个位置,每个位置上有一个值aia_iai. 要求支持如下两种操作: 区间乘vvv 求区间的(1−ai)(1-a_i)(1−ai)之积 思路: 考虑转换式子: Ans=∏i ...
- ZOJ 3229 Shoot the Bullet (有源有汇有上下界最大流)
题意:一个人要给女孩子们拍照,一共 n 天,m 个女孩子,每天他至多拍 d[i] 张照片,每个女孩子总共要被至少拍 g[i] 次.在第 i 天,可以拍 c[i] 个女孩子,c[i] 个女孩子中每个女孩 ...
- line number is important in Exceptions.
行号作为debug信息 在出现异常时可以迅速定位 package ztest; public class Test { public static void main(String[] args) { ...