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下PythonQt3.2使用pandas.pivot_table
本机环境 1.win7 64 旗舰版 2.Qt 5.9.1(MSVC 2015,32 bit) 3.Python 3.7.1 (32-bit),二进制包安装的,即Windows x86 execut ...
- VirtualBox 克隆后 IP 地址相同(DHCP 分配),如何变更MAC以获取不同的IP?
由于需要做实验需要两个相同环境的虚拟机,在linux下使用virtualbox最小化安装centos6.0,并克隆了一个相同的,联网模式为桥接,修改配置文件之后重启网络发现二者的网络信息相同,所获取的 ...
- 深拷贝 deepAssign
实现代码: <script type="text/javascript"> Object.deepAssign = function() { var args = Ar ...
- 学习Acegi应用到实际项目中(2)
Acegi应用到实际项目中(1)是基于BasicProcessingFilter的基本认证,这篇改用AuthenticationProcessingFilter基于表单的认证方式. 1.authent ...
- Zip包解压工具类
最近在做项目的自动检测离线升级,使用到了解压zip包的操作,本着拿来主义精神,搞了个工具类(同事那边拿的),用着还不错. package com.winning.polaris.admin.utils ...
- DedeCMS文章页去img图片width和height属性
方法一:正则匹配去除 打开include/ arc.archives.class.php,查找代码: //设置全局环境变量 $this->Fields['typename'] = $this-& ...
- (钉钉)第三方WEB网站扫码登录
年底在做钉钉和公司的知识库产品的对接,怎么使用钉钉api的如下: 第一步: 登录:https://oa.dingtalk.com/#/welcome 这点可以自己建立一个企业账号进行测试 点击工作台建 ...
- Linux基础理论
本节内容 1. Linux的安装及相关配置 2. UNIX和Linux操作系统概述 3. Linux命令及帮助 4. 目录结构 6. 用户.群组和权限 7. 用户.群组和权限的深入讨论 1 ...
- uniGUI经验几则
uniGUI经验几则 (2015-11-07 21:42:41) 转载▼ 标签: it 分类: uniGUI 1.uniTimer的妙用 很多时候,都会遇到在一个uniForm或者uniFrame加载 ...
- nginx反向代理mysql及负载
下载地址: http://nginx.org/packages/mainline/centos/7/x86_64/RPMS/nginx-1.15.9-1.el7_4.ngx.x86_64.rpm 安装 ...