.Net Core下使用Dapper的方法

2022-04-16 12:40:18
目录
一、前言二、Dapper环境搭建三、Dapper封装定义DapperDBContext类异步分页构建(PageAsync)定义工作单元与事务定义数据仓储数据库连接四、Dapper使用

一、前言

关于什么是Dapper(详细入口),在此不做赘述;本文仅对Dapper在.Net Core中的使用作扼要说明,所陈代码以示例讲解为主,乃抛砖引玉,开发者可根据自身需要进行扩展和调整;其中如有疏漏之处,望不吝斧正。

不了解Dapper的朋友可以看这篇文章:ORM框架之Dapper简介和性能测试

二、Dapper环境搭建

当前以.Net Core WebAPI或MVC项目为例,框架版本为.NET 5.0,相关NuGet包引用如下:

Install-Package Dapper

Install-Package Dapper.Contrib

Install-Package Dapper.SqlBuilder

Install-Package System.Data.SqlClient

其中Dapper.Contrib和Dapper.SqlBuilder为Dapper的扩展,当然,Dapper的扩展还有如Dapper.Rainbow等其他包,根据自身需要引用,对相关引用作下说明:

Dapper:不言而喻;Dapper.Contrib:可使用对象进行数据表的增删改查,免却SQL语句的编写;Dapper.SqlBuilder:可以方便动态构建SQL语句,如Join、SELECT、Where、OrderBy等等;System.Data.SqlClient:由于示例数据库为Sql Server,如mysql则引用MySql.Data;


对于Dapper.Contrib实体配置选项,以Product类为例,作扼要说明如下:

[Table("Product")]public class Product{    [Key]      public int Id { get; set; }    public string Name{ get; set; }    public string Description { get; set; }    public decimal Price { get; set; }    public DateTime CreateTime { get; set; }}

对于实体配置项,有如下几个主要项:

Table:指定数据库表名,可忽略;Key:指定为自动增长主键;ExplicitKey:指定非自动增长主键,如guid;Computed:计算列属性,Insert、Update操作将忽略此列;Write:是否可写入,true/false,如[Write(false)],false时Insert、Update操作将忽略此列,比如可扩展局部类作数据表额外查询字段使用;

对于数据表对象实体,可结合T4模板生成即可。

三、Dapper封装

  关于Dapper数据访问,这里参考github上的某示例(入口:https://github.com/EloreTec/UnitOfWorkWithDapper),作修改调整封装如下:

定义DapperDBContext类

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.Contrib.Extensions        public async Task<T> GetAsync<T>(int id) where T : class, new()        {            return await _connection.GetAsync<T>(id, _transaction, _commandTimeout);        }        public async Task<T> GetAsync<T>(string id) where T : class, new()        {            return await _connection.GetAsync<T>(id, _transaction, _commandTimeout);        }        public async Task<IEnumerable<T>> GetAllAsync<T>() where T : class, new()        {            return await _connection.GetAllAsync<T>();        }        public long Insert<T>(T model) where T : class, new()        {            return _connection.Insert<T>(model, _transaction, _commandTimeout);        }        public async Task<int> InsertAsync<T>(T model) where T : class, new()        {            return await _connection.InsertAsync<T>(model, _transaction, _commandTimeout);        }        public bool Update<T>(T model) where T : class, new()        {            return _connection.Update<T>(model, _transaction, _commandTimeout);        }        public async Task<bool> UpdateAsync<T>(T model) where T : class, new()        {            return await _connection.UpdateAsync<T>(model, _transaction, _commandTimeout);                  }        public async Task<Page<T>> PageAsync<T>(long pageIndex, long pageSize, string sql, object param = null)        {            DapperPage.BuildPageQueries((pageIndex - 1) * pageSize, pageSize, sql, out string sqlCount, out string sqlPage);            var result = new Page<T>            {                CurrentPage = pageIndex,                ItemsPerPage = pageSize,                TotalItems = await _connection.ExecuteScalarAsync<long>(sqlCount, param)            };            result.TotalPages = result.TotalItems / pageSize;            if ((result.TotalItems % pageSize) != 0)                result.TotalPages++;            result.Items = await _connection.QueryAsync<T>(sqlPage, param);            return result;        }              #endregion        #region Dapper Execute & Query              public int ExecuteScalar(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return _connection.ExecuteScalar<int>(sql, param, _transaction, _commandTimeout, commandType);        }        public async Task<int> ExecuteScalarAsync(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return await _connection.ExecuteScalarAsync<int>(sql, param, _transaction, _commandTimeout, commandType);        }        public int Execute(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return _connection.Execute(sql, param, _transaction, _commandTimeout, commandType);        }        public async Task<int> ExecuteAsync(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return await _connection.ExecuteAsync(sql, param, _transaction, _commandTimeout, commandType);        }        public IEnumerable<T> Query<T>(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return _connection.Query<T>(sql, param, _transaction, true, _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 T QueryFirstOrDefault<T>(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return _connection.QueryFirstOrDefault<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 IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, object param = null, string splitOn = "Id", CommandType commandType = CommandType.Text)        {            return _connection.Query(sql, map, param, _transaction, true, splitOn, _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);        }        public async Task<SqlMapper.GridReader> QueryMultipleAsync(string sql, object param = null, CommandType commandType = CommandType.Text)        {            return await _connection.QueryMultipleAsync(sql, param, _transaction, _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        }    }fObLHEIt

以上代码涵盖了Dapper访问数据库的基本操作,分同步和异步,其中大部分不作赘述,着重说下分页部分;

异步分页构建(PageAsync)

分页这里为方便调用,只需传入要查询的Sql语句(如:SELECT * FROM Table,必须带Order BY)、页索引、页大小即可;

至于具体如何构建的,这里参照某小型ORM工具PetaPoco,抽取相关代码如下,有兴趣的同学也可以自行改造:

public class Page<T>    {        /// <summary>        /// The current page number contained in this page of result set         /// </summary>        public long CurrentPage { get; set; }        /// <summary>        /// The total number of pages in the full result set        /// </summary>        public long TotalPages { get; set; }        /// <summary>        /// The total number of records in the full result set        /// </summary>        public long TotalItems { get; set; }        /// <summary>        /// The number of items per page        /// </summary>        public long ItemsPerPage { get; set; }        /// <summary>        /// The actual records on this page        /// </summary>        public IEnumerable<T> Items { get; set; }        //public List<T> Items { get; set; }    }    public class DapperPage    {        public static void BuildPageQueries(long skip, long take, string sql, out string sqlCount, out string sqlPage)        {            // Split the SQL            if (!PagingHelper.SplitSQL(sql, out PagingHelper.SQLParts parts))                throw new Exception("Unable to parse SQL statement for paged query");            sqlPage = BuildPageSql.BuildPageQuery(skip, take, parts);            sqlCount = parts.sqlCount;        }    }    static class BuildPageSql    {        public static string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts)        {            parts.sqlSelectRemoved = PagingHelper.rxOrderBy.Replace(parts.sqlSelectRemoved, "", 1);            if (PagingHelper.rxDistinct.IsMatch(parts.sqlSelectRemoved))            {                parts.sqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.sqlSelectRemoved + ") peta_inner";            }            var sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>{2} AND peta_rn<={3}",                                    parts.sqlOrderBy ?? "ORDER BY (SELECT NULL)", parts.sqlSelectRemoved, skip, skip + take);            //args = args.Concat(new object[] { skip, skip + take }).ToArray();            return sqlPage;        }        //SqlServer 2012及以上        public static string BuildPageQuery2(long skip, long take, PagingHelper.SQLParts parts)        {            parts.sqlSelectRemoved = PagingHelper.rxOrderBy.Replace(parts.sqlSelectRemoved, "", 1);            if (PagingHelper.rxDistinct.IsMatch(parts.sqlSelectRemoved))            {                parts.sqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.sqlSelectRemoved + ") peta_inner";            }                var sqlOrderBy = parts.sqlOrderBy ?? "ORDER BY (SELECT NULL)";            var sqlPage = $"SELECT {parts.sqlSelectRemoved} {sqlOrderBy} OFFSET {skip} ROWS FETCH NEXT {take} ROWS ONLY";            return sqlPage;        }    }    static class PagingHelper    {        public struct SQLParts        {            public string sql;            public string sqlCount;            public string sqlSelectRemoved;            public string sqlOrderBy;        }        public static bool SplitSQL(string sql, out SQLParts parts)        {            parts.sql = sql;            parts.sqlSelectRemoved = null;            parts.sqlCount = null;            parts.sqlOrderBy = null;            // Extract the columns from "SELECT <whatever> FROM"            var m = rxColumns.Match(sql);            if (!m.Success)                return false;            // Save column list and replace with COUNT(*)            Group g = m.Groups[1];            parts.sqlSelectRemoved = sql.Substring(g.Index);            if (rxDistinct.IsMatch(parts.sqlSelectRemoved))                parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);            else                parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);            // Look for the last "ORDER BY <whatever>" clause not part of a ROW_NUMBER expression            m = rxOrderBy.Match(parts.sqlCount);            if (!m.Success)            {                parts.sqlOrderBy = null;            }            else            {                g = m.Groups[0];                parts.sqlOrderBy = g.ToString();                parts.sqlCount = parts.sqlCount.Substring(0, g.Index) + parts.sqlCount.Substring(g.Index + g.Length);            }            return true;        }        public static Regex rxColumns = new Regex(@"As*SELECTs+((?:((?>((?<depth>)|)(?<-depth>)|.?)*(?(depth)(?!)))|.)*?)(?<!,s+)bFROMb", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);        public static Regex rxOrderBy = new Regex(@"bORDERs+BYs+(?!.*?(?:)|s+)ASs)(?:((?>((?<depth>)|)(?<-depth>)|.?)*(?(depth)(?!)))|[w().])+(?:s+(?:ASC|DESC))?(?:s*,s*(?:((?>((?<depth>)|)(?<-depth>)|.?)*(?(depth)(?!)))|[w().])+(?:s+(?:ASC|DESC))?)*", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);        public static Regex rxDistinct = new Regex(@"ADISTINCTs", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);    }

对于构建分页语句,分别示例BuildPageQuery和BuildPageQuery2,前者为通过ROW_NUMBER进行分页(针对SqlServer2005、2008),后者通过OFFSET、FETCH分页(针对SqlServer2012及以上版本),相关辅助操作类一览便知,如果使用MySql数据库,可酌情自行封装; 

至于Where查询的进一步封装,有兴趣的也可兑Dapper lamada查询进行扩展。

定义工作单元与事务

public interface IUnitOfWork : IDisposable    {        void SaveChanges();    }    public interface IUnitOfWorkFactory    {        IUnitOfWork Create();    }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();        }    }public class DapperUnitOfWorkFactory : IUnitOfWorkFactory    {        private readonly DapperDBContext _context;        public DapperUnitOfWorkFactory(DapperDBContext context)        {            _context = context;        }        public IUnitOfWork Create()        {            return new UnitOfWork(_context);        }    }

定义数据仓储

#region Product    public partial interface IProductRepository    {        Task<Product> GetAsync(int id);        Task<IEnumerable<Product>> GetAllAsync();        long Insert(Product model);        Task<int> InsertAsync(Product model);        bool Update(Product model);        Task<bool> UpdateAsync(Product model);               int Count(string where, object param = null);        Task<int> CountAsync(string where, object param = null);        bool Exists(string where, object param = null);        Task<bool> ExistsAsync(string where, object param = null);                Product FirstOrDefault(string where, object param = null);        Task<Product> FirstOrDefaultAsync(string where, object param = null);        T FirstOrDefault<T>(string sql, object param = null);        Task<T> FirstOrDefaultAsync<T>(string sql, object param = null);        IEnumerable<Product> Fetch(SqlBuilder where);        Task<IEnumerable<Product>> FetchAsync(SqlBuilder where);        IEnumerable<T> Fetch<T>(string sql, SqlBuilder where, bool orderBy = true);        Task<IEnumerable<T>> FetchAsync<T>(string sql, SqlBuilder where, bool orderBy = true);        Task<Page<Product>> PageAsync(long pageIndex, long pageSize, SqlBuilder builder);        Task<Page<T>> PageAsync<T>(string sql, long pageIndex, long pageSize, SqlBuilder builder);        Task<SqlMapper.GridReader> QueryMultipleAsync(string sql, object param = null);    }    public partial class ProductRepository : IProductRepository    {        private readonly DapperDBContext _context;        public ProductRepository(DapperDBContext context)        {            _context = context;        }        public async Task<Product> GetAsync(int id)        {             return await _context.GetAsync<Product>(id);        }        public async Task<IEnumerable<Product>> GetAllAsync()        {            return await _context.GetAllAsync<Product>();        }        public long Insert(Product model)        {            return _context.Insert<Product>(model);        }        public async Task<int> InsertAsync(Product model)        {            return await _context.InsertAsync<Product>(model);        }            public bool Update(Product model)        {             return _context.Update<Product>(model);        }            public async Task<bool> UpdateAsync(Product model)        {             return await _context.UpdateAsync<Product>(model);        }        public int Count(string where, object param = null)        {            string strSql = $"SELECT COUNT(1) FROM Product {where}";            return _context.ExecuteScalar(strSql, param);        }        public async Task<int> CountAsync(string where, object param = null)        {            string strSql = $"SELECT COUNT(1) FROM Product {where}";            return await _context.ExecuteScalarAsync(strSql, param);        }        public bool Exists(string where, object param = null)        {            string strSql = $"SELECT TOP 1 1 FROM Product {where}";            var count = _context.ExecuteScalar(strSql, param);            return count > 0;        }        public async Task<bool> ExistsAsync(string where, object param = null)        {            string strSql = $"SELECT TOP 1 1 FROM Product {where}";            var count = await _context.ExecuteScalarAsync(strSql, param);            return count > 0;        }        public Product FirstOrDefault(string where, object param = null)        {            string strSql = $"SELECT TOP 1 * FROM Product {where}";            return _context.QueryFirstOrDefault<Product>(strSql, param);        }        public async Task<Product> FirstOrDefaultAsync(string where, object param = null)        {            string strSql = $"SELECT TOP 1 * FROM Product {where}";            return await _context.QueryFirstOrDefaultAsync<Product>(strSql, param);        }        public T FirstOrDefault<T>(string sql, object param = null)        {            return _context.QueryFirstOrDefault<T>(sql, param);        }        public async Task<T> FirstOrDefaultAsync<T>(string sql, object param = null)        {            return await _context.QueryFirstOrDefaultAsync<T>(sql, param);        }        public IEnumerable<Product> Fetch(SqlBuilder where)        {            var strSql = where.AddTemplate(@"SELECT * FROM Product /**where**/ /**orderby**/");            return _context.Query<Product>(strSql.RawSql, strSql.Parameters);        }        public async Task<IEnumerable<Product>> FetchAsync(SqlBuilder where)        {            var strSql = where.AddTemplate(@"SELECT * FROM Product /**where**/ /**orderby**/");            return await _context.QueryAsync<Product>(strSql.RawSql, strSql.Parameters);        }        public IEnumerable<T> Fetch<T>(string sql, SqlBuilder where, bool orderBy = true)        {            var _sql = orderBy ? $"{sql} /**where**/ /**orderby**/" : $"{sql} /**where**/";            var strSql = where.AddTemplate(_sql);            return _context.Query<T>(strSql.RawSql, strSql.Parameters);        }        public async Task<IEnumerable<T>> FetchAsync<T>(string sql, SqlBuilder where, bool orderBy = true)        {            var _sql = orderBy ? $"{sql} /**where**/ /**orderby**/" : $"{sql} /**where**/";            var strSql = where.AddTemplate(_sql);            return await _context.QueryAsync<T>(strSql.RawSql, strSql.Parameters);        }        public async Task<Page<Product>> PageAsync(long pageIndex, long pageSize, SqlBuilder builder)        {                     var strSql = "SELECT * FROM Product";               return await PageAsync<Product>(strSql, pageIndex, pageSize, builder);        }        public async Task<Page<T>> PageAsync<T>(string sql, long pageIndex, long pageSize, SqlBuilder builder)        {            var strSql = builder.AddTemplate($"{sql} /**where**/ /**orderby**/");            return await _context.PageAsync<T>(pageIndex, pageSize, strSql.RawSql, strSql.Parameters);        }        public async Task<SqlMapper.GridReader> QueryMultipleAsync(string sql, object param = null)        {                      return await _context.QueryMultipleAsync(sql, param);        }    }    #endregion

根据自身需要进行调整或扩展,一般借助T4模板生成

数据库连接

通过Ioptions模式读取配置文件appsettings中连接字符串
 

public class MyDBContext : DapperDBContext    {        public MyDBContext(IOptions<DapperDBContextOptions> optionsAccessor) : base(optionsAccessor)        {        }        protected override IDbConnection CreateConnection(string connectionString)        {            IDbConnection conn = new SqlConnection(connectionString);            return conn;        }    }

四、Dapper使用

Startup.cs注入并读取数据库连接字符串

{  "SQLConnString": "Data Source=(local);Initial Catalog=database;Persist Security Info=True;User ID=sa;Password=123456;MultipleActiveResultSets=True;",    "Logging": {    "LogLevel": {      "Default": "Information",      "Microsoft": "Warning",      "Microsoft.Hosting.Lifetime": "Information"    }  },  "AllowedHosts": "*"}
services.AddDapperDBContext<MyDBContext>(options =>            {                options.Configuration = Configuration["SQLConnString"];            });

简单示例WebAPI或Net Core MVC下的调用示例:

public class ProductController : BaseController{    private readonly IProductRepository _productRepository;             public ProductController(        IProductRepository productRepository                )    {        _productRepository = productRepository;                      }    //商品列表    [HttpGet]            public async Task<IActionResult> ProductList(DateTime? startDate, DateTime? endDate, int id = 1, int productStatus = 0, string keyword = "")    {        var model = new ProductModels();        var builder = new Dapper.SqlBuilder();        builder.Where("ProductStatus!=@ProductStatus", new { ProductStatus = productStatus });        if (startDate.HasValue)        {            builder.Where("CreateTime>=@startDate", new { startDate = startDate.Value});        }        if (endDate.HasValue)        {            builder.Where("CreateTime<@endDate", new { endDate = endDate.Value.AddDays(1)});        }                   if (!string.IsNullOrWhiteSpace(keyword))        {            builder.Where("Name LIKE @keyword", new { keyword = $"%{StringHelper.ReplaceSql(keyword)}%" });        }          builder.OrderBy("SortNum DESC,CreateTime DESC");        var list = await _productRepository.PageAsync(id, PageSize, builder);                model.ProductList = new PagedList<Product>(list.Items, id, PageSize, list.TotalItems);        if (Request.IsAjaxRequest())            return PartialView("_ProductList", model.ProductList);                return View(model);    }    //添加商品    [HttpPost]     public async Task<int> AddProduct(ProductModels model)    {        return await _productRepository.InsertAsync(model);    }}
public partial interface IProductService    {         Task<bool> AddProduct(Product productInfo, List<ProductStock> skuList);         }    public class ProductService: IProductService    {        private readonly DapperDBContext _context;        private readonly IUnitOfWorkFactory _uowFactory;        public ProductService(DapperDBContext context, IUnitOfWorkFactory uowFactory)        {            _context = context;            _uowFactory = uowFactory;        }        /// <summary>        /// 添加商品        /// </summary>        /// <param name="productInfo"></param>        /// <param name="skuList"></param>        /// <returns></returns>        public async Task<bool> AddProduct(Product productInfo, List<ProductStock> skuList)        {            var result = false;            using (var uow = _uowFactory.Create())            {                //添加产品                await _context.InsertAsync(productInfo);                //添加Sku库存售价                               //await _context.InsertAsync(skuList);                uow.SaveChanges();                result = true;            }            return result;        }            }

以上所述是小编给大家介绍的.Net Core下使用Dapper的方法,希望对大家有所帮助。在此也非常感谢大家对我们网站的支持!