ChuanGoing 2019-09-10
html
距离上一篇近一个月时间,断断续续才把本篇码完,后面将加快进度,争取年度内把本系列基本介绍完成,同时督促本人持续学习。mysql
本篇学习曲线:git
1.初识Dappergithub
2.DbConnectionsql
3.CommandBuilder实现单表操做(略)shell
4.演示数据库
初识Dapper缓存
Dapper是一个轻量级/高性能的ORM,核心功能是利用Emit反射获取IDataReader中的数据。咱们能够利用它的对象关系映射实现简单CURD操做,或者直接用SQL语句实现复杂场景的CURD操做。oracle
DbConnectionapp
顾名思义,数据库链接对象。Dapper提供DbConnection对象的扩展来操做数据库
public virtual int Execute(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) { return _dbConnection.Execute(sql: sql, param: param, transaction: null, commandTimeout: commandTimeout, commandType: commandType); } public virtual IEnumerable<TResult> Query<TResult>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) { return _dbConnection.Query<TResult>(sql: sql, param: param, transaction: null, commandTimeout: commandTimeout, commandType: commandType); }
上面贴出的两个方法:Execute方法执行(增删改),Query执行查询操做。由此能够看到,Dapper操做数据库主要是手写SQL,固然咱们也能够封装一些经常使用的方法来提升开发效率。
固然,本篇重点不在于Dapper的介绍。接下来看看如何对Dapper来封装出咱们本身可用的ORM。
CommandBuilder实现单表操做须要实现通用的单表的增删改查,咱们得先定义/分解SQL语句:
1.操做的表对象(表)
2.表对象中的列对象(字段)
3.条件
定义字段对象/对象集合
public class Field { public Field(string name, object value = null) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name), "invalid name"); } Name = name; Value = value; } public string Name { set; get; } public object Value { set; get; } }
public class FieldsCollection : IEnumerable<Field> { private List<Field> _fields; public Field this[int index] => _fields[index]; public FieldsCollection() { _fields = new List<Field>(); } public int Count => _fields.Count; public void Add(Field field) { _fields.Add(field); } public void Add(params Field[] fields) { _fields.AddRange(fields); } public IEnumerable<Field> GetFields() { return _fields; } public IEnumerator<Field> GetEnumerator() { return _fields.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _fields.GetEnumerator(); } }
定义条件
public abstract class Filter { public Filter(string field) { Field = field; } public virtual string Field { get; private set; } }
/// <summary> /// 相等过滤 /// </summary> public class EqualFilter : Filter { public EqualFilter(string field, object value) : base(field) { Value = value; } public object Value { get; } }
这里只贴出了"相等"条件,详细代码请查看篇尾给出的Github源码连接
定义排序字段
public class Sort { public string Field { get; set; } public bool Order { get; set; } public Sort(string field, bool order = true) { Field = field; Order = order; } }
查询语句的组装
public class QueryParameter { private List<Filter> _filters; private List<Sort> _sorts; public QueryParameter(FieldsCollection fileds, IEnumerable<Filter> filters = null, IEnumerable<Sort> sorts = null) { Fields = fileds.GetFields(); _filters = new List<Filter>(); if (filters != null) { _filters.AddRange(filters); } _sorts = new List<Sort>(); if (sorts != null) { _sorts.AddRange(sorts); } } public void AddFilter(Filter filter) { _filters.Add(filter); } public void AddSort(Sort sort) { _sorts.Add(sort); } public IEnumerable<Field> Fields { get; } public IEnumerable<Filter> Filters => _filters; public IEnumerable<Sort> Sorts => _sorts; }
完成以上对象定义后,咱们再来看看如何利用上述对象完成增删改查操做
public SqlCommand GetCommand(TPrimaryKey key) { var obj = GetObjectContext<TEntity>(); FieldsCollection fields = new FieldsCollection(); List<Filter> filters = new List<Filter>(); foreach (var prop in obj.Properties) { foreach (var attr in prop.Attributes) { if (attr is PrimaryKeyAttribute keyAttr) { filters.Add(new Equal(prop.Info.Name, key)); } } fields.Add(new Field(prop.Info.Name)); } QueryParameter queryParameter = new QueryParameter(fields, filters); return CommandBuilder.QueryCommand(obj.Table, queryParameter, count: 1); }
查询方法是根据主键作查询操做,其中数据库上下文对象经过泛型对象反射获得
public virtual ObjectContext GetObjectContext<T>() { var type = typeof(T); string tableKey = ObjectContext.GetTableKey(typeof(T)); return DbContext.ObjectCollection.GetOrAdd(tableKey, entity => new ObjectContext(type)); }
新增方法相似上面的查询,只是SQL语句形式有区别
public SqlCommand InsertCommand(TEntity entity) { var obj = GetObjectContext<TEntity>(); FieldsCollection fields = new FieldsCollection(); foreach (var prop in obj.Properties) { fields.Add(new Field(prop.Info.Name, prop.Info.GetValue(entity))); } var com = CommandBuilder.InsertCommand(obj.Table, fields); return com; }
查询/新增方法,能够看到,上面代码经过反射/缓存获得增删改查的参数/值得信息,到这里为止,尚未造成有效的SQL语句。那么如何实现呢?
因为各个数据库(Mysql/Mssql/oracle..)中SQL语法有些差别,所以转化SQL的工做应该交由具体的某种数据库语句生成器去生成。本例采用的是Mysql数据库,所以咱们能够看到上诉代码中涉及到CommandBuilder是基于mysql实现的,具体代码在这里就不贴了,详情看篇末Github连接。
演示
基于上一篇Asp.net Core 系列之--1.事件驱动初探:简单事件总线实现(SimpleEventBus),改写一下CustomersController,repository由直接经过sql语句操做替换为本篇实现的封装代码,而后将事件/事件处理定义、实体/Dto等移到Domain层(为后续介绍铺路)
private readonly IRepository<Customer, Guid> _repository; private readonly IEventBus _eventBus; public CustomersController(IEventBus eventBus, IRepository<Customer, Guid> repository) { _repository = repository; _eventBus = eventBus; } // 获取指定ID的客户信息 [HttpGet("{id}")] public async Task<IActionResult> Get(Guid id) { var customer = await _repository.GetAsync(id); if (customer == null) { return NotFound(); } return Ok(customer); } // 建立新的客户信息 [HttpPost] public async Task<IActionResult> Create([FromBody] CustomerDto model) { var name = model.Name; if (string.IsNullOrEmpty(name)) { return BadRequest(); } var customer = new Customer(name); var result = await _repository.InsertAsync(customer); await _eventBus.PublishAsync(new CustomerCreatedEvent(name)); return Created(Url.Action("Get", new { id = customer.Id }), customer.Id); }
运行程序后,正确获得返回数据
Dapper实现ORM基本功能到此算告一段落,读者有兴趣的话能够查阅Dapper源码,后续有机会的话再介绍下它的扩展功能
回顾
回顾一下本篇内容,首先简单介绍了Dapper是什么、能作什么,而后咱们基于mysql实现了Dapper的简单对象关系映射,最后利用WinPowershell的Invoke-WebRequest模拟http请求演示了数据的建立与获取。
本篇已涉及到仓储的概念,也是领域模型的重要环节,后续咱们将会渐进式的介绍DDD相关概念及设计原理
代码
本篇涉及的源码在Github的https://github.com/ChuanGoing/Start.git 的DapperOrm分支能够找到。