.Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍
今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下。下面我会还原迁移的每一个过程,以及在此过程中遇到的问题和处理这些问题的方法。
一、迁移前的准备
之前对Dapper的封装使用的是.Net Framework下的ORM 框架Dapper,开发工具VS2013,现在既然想在.Net Core2.0上使用Dapper,我要先到NuGet看看有没有支持 .Net Core的,在Nuget找到如下:

果然有!!!因为项目中使用的是MySQL,所以还要看看有没有MySQL的.Net驱动,发现也有,但是是预发行版本,算了等不及正式版了,先用(生产环境中我暂时没使用)它来测试,等正式版出来就正式迁移了(* ̄︶ ̄)

好了,该准备的已经准备好了,下面就是使用VS2017新建一个项目,用来测试,项目的整体结构如下:

二、正式迁移
.Net Framework下对Dapper进行的二次封装,代码部分如下,后面会介绍我为什么要这样封装:
namespace ZSZ.Core.Respository
{ public interface IDataAdapter
{
string BindVariablePrefix { get; }
void AppendColumnName(StringBuilder sb, string columnName);
void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
void AppendUpdateColumnName(StringBuilder sb, string columnName);
}
public class OracleDataAdapter : IDataAdapter
{
public string BindVariablePrefix
{
get { return ":"; }
}
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}, ", columnName.ToUpper());
}
public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}{1}, ", BindVariablePrefix, columnName.ToUpper());
}
public void AppendUpdateColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}={1}{0}, ", columnName.ToUpper(), BindVariablePrefix);
}
}
public class SqlServerDataAdapter : IDataAdapter
{
public string BindVariablePrefix
{
get { return "@"; }
}
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}, ", columnName.ToUpper());
} public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}{1}, ", BindVariablePrefix, columnName.ToUpper());
} public void AppendUpdateColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}={1}{0}, ", columnName.ToUpper(), BindVariablePrefix);
}
}
public class MySqlDataAdapter : IDataAdapter
{
public string BindVariablePrefix
{
get { return "@"; }
}
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}, ", columnName.ToUpper());
} public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}{1}, ", BindVariablePrefix, columnName.ToUpper());
} public void AppendUpdateColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}={1}{0}, ", columnName.ToUpper(), BindVariablePrefix);
}
}
public static class DataBase
{
internal class TypeInsertPair
{
public string Columns { get; set; }
public string Values { get; set; }
} /*
*
*
* 线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
* http://www.cnblogs.com/CreateMyself/p/6086752.html
* http://www.cnblogs.com/PurpleTide/archive/2011/11/21/2256577.html
* http://www.cnblogs.com/lori/p/4344026.html
*/ private static readonly ConcurrentDictionary<RuntimeTypeHandle, TypeInsertPair> TypeInsertPairDictionary = new ConcurrentDictionary<RuntimeTypeHandle, TypeInsertPair>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeUpdateDictionary = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeColumnsDictionary = new ConcurrentDictionary<RuntimeTypeHandle, string>(); private static IDataAdapter defaultDataAdapter;
private static IDataAdapter DefaultDataAdapter
{
get
{
if (defaultDataAdapter == null)
{
defaultDataAdapter = GetDataAdapter(DefaultConnectionStringSettings);
}
return defaultDataAdapter;
}
set
{
defaultDataAdapter = value;
}
}
public static IDataAdapter GetDataAdapter(this ConnectionStringSettings connectionStringSettings)
{
if (connectionStringSettings == null)
{
return defaultDataAdapter;
}
if (string.IsNullOrEmpty(connectionStringSettings.ProviderName))
{
throw new Exception("数据库连接串的配置不正确!");
}
if (connectionStringSettings.ProviderName.ToLower().Contains("oracle"))
{
return new OracleDataAdapter();
}
else if (connectionStringSettings.ProviderName.ToLower().Contains("mysql"))
{
return new MySqlDataAdapter();
}
else if (connectionStringSettings.ProviderName.ToLower().Contains("sql"))
{
return new SqlServerDataAdapter();
} throw new Exception("暂不支持您使用的数据库类型!");
}
private static ConnectionStringSettings defaultConnectionStringSettings;
public static ConnectionStringSettings DefaultConnectionStringSettings
{
get
{
if (defaultConnectionStringSettings == null)
{
defaultConnectionStringSettings = ConfigurationManager.ConnectionStrings["db"];
}
return defaultConnectionStringSettings;
}
set
{
if (value == null) throw new Exception("默认的数据库连接配置信息不能为空!");
defaultConnectionStringSettings = value;
DefaultDataAdapter = GetDataAdapter(value);
}
}
private static IDbConnection GetDbConnection(this ConnectionStringSettings connectionStringSettings)
{
if (connectionStringSettings != null && (string.IsNullOrEmpty(connectionStringSettings.ConnectionString) || string.IsNullOrEmpty(connectionStringSettings.ProviderName))) throw new Exception("数据库链接字符串配置不正确!");
var settings = connectionStringSettings == null ? DefaultConnectionStringSettings : connectionStringSettings;
var factory = System.Data.Common.DbProviderFactories.GetFactory(settings.ProviderName);
var connection = factory.CreateConnection();
connection.ConnectionString = settings.ConnectionString;
return connection; }
private static TypeInsertPair GetTypeInsertPair(this Type type, IDataAdapter adapter)
{
if (TypeInsertPairDictionary.ContainsKey(type.TypeHandle)) return TypeInsertPairDictionary[type.TypeHandle]; var columns = new StringBuilder();
var values = new StringBuilder();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.IsIgnore() && !"id".Equals(property.Name, StringComparison.OrdinalIgnoreCase)) continue; adapter.AppendColumnName(columns, property.Name);
adapter.AppendColumnNameEqualsValue(values, property.Name);
} var pair = new TypeInsertPair() { Columns = columns.ToString().Substring(, columns.Length - ), Values = values.ToString().Substring(, values.Length - ) };
TypeInsertPairDictionary[type.TypeHandle] = pair; return pair;
}
private static string GetTypeColumns(this Type type, ConnectionStringSettings connectionStringSettings)
{
if (TypeColumnsDictionary.ContainsKey(type.TypeHandle)) return TypeColumnsDictionary[type.TypeHandle]; var sb = new StringBuilder();
var adapter = connectionStringSettings.GetDataAdapter();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
//查询时的字段
adapter.AppendColumnName(sb, property.Name);
} var columns = sb.ToString().Substring(, sb.Length - );
TypeColumnsDictionary[type.TypeHandle] = columns;
return columns;
}
private static string GetTypeUpdateSetString(this Type type, ConnectionStringSettings connectionStringSettings)
{
if (TypeUpdateDictionary.ContainsKey(type.TypeHandle)) return TypeUpdateDictionary[type.TypeHandle]; var sb = new StringBuilder();
var adapter = connectionStringSettings.GetDataAdapter();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
//更新时如果传入实体对象的话,会有ID在里面,所以在这里要把ID(主键)去掉
if (property.IsIgnore() || "id".Equals(property.Name, StringComparison.OrdinalIgnoreCase)) continue; adapter.AppendUpdateColumnName(sb, property.Name);
} var update = sb.ToString().Substring(, sb.Length - );
TypeUpdateDictionary[type.TypeHandle] = update;
return update;
}
//如果对应的字段上有这样的特性就不参与对应的数据库操作
private static bool IsIgnore(this PropertyInfo property)
{
var attribute = property.GetCustomAttributes(typeof(IgnoreAttribute), true).FirstOrDefault() as IgnoreAttribute;
return attribute != null && attribute.Ignore;
} #region 查询
//根据实体生成sql,映射返回实体集合
//使用:传过来condition、param参数即可
public static IEnumerable<T> Get<T>(string condition = null, object param = null, string tableName = null, ConnectionStringSettings connectionStringSettings = null, IDbTransaction transaction = null) where T : class
{
if (string.IsNullOrEmpty(tableName) && !(typeof(T).IsSubclassOf(typeof(BaseEntity<T>)))) throw new Exception("没有输入表名时只支持数据库实体查询!");
var name = string.IsNullOrEmpty(tableName) ? BaseEntity<T>.TableName : tableName;
var columns = string.IsNullOrEmpty(tableName) ? "*" : typeof(T).GetTypeColumns(connectionStringSettings);
var sql = string.IsNullOrEmpty(condition) ? string.Format("select {0} from {1}", columns, name) : string.Format("select {0} from {1} where {2}", columns, name, condition);
var conn = connectionStringSettings.GetDbConnection();
return conn.Query<T>(sql, param, transaction);
} //根据SQL映射实体或ViewModel
//使用:传过来SQL,让Dapper进行映射
public static IEnumerable<T> GetBySql<T>(string sql, object param = null, ConnectionStringSettings connectionStringSettings = null, IDbTransaction transaction = null) where T : class
{
var conn = connectionStringSettings.GetDbConnection();
return conn.Query<T>(sql, param, transaction);
} //根据ID获取单个实体对象
//使用:传过来ID
public static T GetById<T>(string id, string tableName = null, ConnectionStringSettings connectionStringSettings = null, IDbTransaction transaction = null) where T : class
{
var adapter = connectionStringSettings.GetDataAdapter();
return Get<T>(connectionStringSettings: connectionStringSettings, tableName: tableName, condition: string.Format("id ={0}id", adapter.BindVariablePrefix), param: new { id = id }, transaction: transaction).FirstOrDefault();
}
}
在把.Net Framework下对Dapper进行二次封装的代码放到.Net Core 2.0之前,我们需要在上面新建的项目中引用,如下Nuget包:
(1)

在图中标识的项目中需要引用Dapper和MySQL
1)Install-Package MySql.Data -Version 8.0.8-dmr
2)Install-Package Dapper -Version 1.50.2
(2)迁移到.Net Core2.0上之后报错截图,前提是有些可以手动引用,下面列出来的是ctrl+.还解决不了的。


1)在.Net Framework下 System.Configuration下有ConnectionStringSettings类,但是在.Net Core中是不是还在同样的命名空间下?于是查看接口文档https://docs.microsoft.com/zh-cn/dotnet/api/system.configuration.connectionstringsettings?view=netcore-2.0,发现,真有!!!于是就using System.Configuration;同时还需要安装Nuget包: Install-Package System.Configuration.ConfigurationManager,这样就可以把ConnectionStringSettings类的错误解决掉。
2)ConfigurationManager类的错误,关于读取配置文件中的信息,可以添加 Microsoft.Extensions.Configuration和Microsoft.Extensions.Configuration.Json来解决配置文件的读取问题,还可以使用原始的方式来读取,但需要添加 System.Configuration.ConfigurationManager,关于怎么读取配置文件中的信息,很简单,在这里就不介绍了。下面给一篇关于如何读取配置文件信息的文章 :http://www.cnblogs.com/mantgh/p/7425113.html
3)在.Net Framework下, system.Data.Common下有DbProviderFactories类,如下图:

但是在.Net Core 2.0的接口文档中没有找到该类,那该怎么办?首先在这里讲点ADO.Net的相关知识,我为什么要使用该类?因为使用该类中的 public static DbProviderFactory GetFactory(string providerInvariantName);可以通过providerInvariantName来创建对应的 ClientFactory,因为该方法返回DbProviderFactory ,同时SqlClientFactory、MySqlClientFactory等都继承DbProviderFactory,如下图所示:

我们通过该方法拿到对应的DbProviderFactory工厂了 ,也就意味着可以通过providerInvariantName拿到对应数据库的ClientFactory,然后调用里面的CreateConnection()方法就可以得到一个DbConnection,再调用里面的ConnectionString属性,把链接字符串赋值给该属性即可。该部分的代码在上面测试项目System.Data.CommonExts中,代码如下:
using MySql.Data.MySqlClient;
using System.Data.Common;
using System.Data.SqlClient; namespace System.Data.CommonExts
{
public static class DbProviderFactories
{
/// <summary>
/// 通过在appsettings.json文件中配置 "providerName",来创建对应的数据库链接
/// </summary>
/// <param name="providerInvariantName">例如:MySql.Data.MySqlClient</param>
/// <returns>DbProviderFactory</returns>
public static DbProviderFactory GetFactory(string providerInvariantName)
{
if (string.IsNullOrEmpty(providerInvariantName)) throw new Exception("数据库链接字符串配置不正确!"); if(providerInvariantName.ToLower().Contains("mysql"))
{
return new MySqlClientFactory();
}
else if(providerInvariantName.ToLower().Contains("sql"))
{
return SqlClientFactory.Instance;
} throw new Exception("暂不支持您使用的数据库类型!"); }
}
}
注意,这里需要安装的包,如下:

4)配置文件配置如下:
{
"db": {
"mysql": {
"conStr": "server=.;charset=gb2312;user id=root;password=123456;persist security info=True;database=dappertest;charset=utf8;",
"providerName": "MySql.Data.MySqlClient"
}
}
}
我知道你会想,为什么要这样做???神经病吧!!!,如果你有好的办法也可以分享出来,后面会介绍为什么我要这样封装。
好了,完成上面的三步即可完成迁移,下面是在测试时遇到的问题,在讲测试时遇到的问题前,需要给大家介绍一下,为什么我要这样封装Dapper以及正式项目中为什么要这样搭建。
三、对Dapper进行封装的原因以及正式项目中搭建这样的框架的背景
(1)如果不对Dapper进行二次封装,我们是这样使用的
using(MySqlConnection con = new MySqlConnection("server=127.0.0.1;database=test;uid=root;pwd=;charset='gbk'"))
{
var list=con.Query<User>("select * from user");
......
......
}
每次对数据库操作,我都需要先new一个MySqlConnection,太烦。于是就有了,下面的代码:

图中的ConnectionStringSettings是没有找到对应程序集时,自己定义的。现在在.Net Core2.0中可以找到了,上面已经介绍了。

(2)我现在使用的是MySQL数据库,如果要切换数据库,比如使用SqlServer、oracle等其他数据库,我还需要修改connection,太麻烦,于是就有了下面的代码:

通过DBProviderFactories,动态创建数据库链接。
(3)在没有对Dapper进行二次封装,如果我们切换数据库,由于不同数据库的语法不一样,修改的工作量不能忽视,如何屏蔽不同数据库之间语法的不同呢,于是就有了下面的代码:
public interface IDataAdapter
{
string BindVariablePrefix { get; }
void AppendColumnName(StringBuilder sb, string columnName);
void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
void AppendUpdateColumnName(StringBuilder sb, string columnName);
} public class MySqlDataAdapter : IDataAdapter
{
public string BindVariablePrefix
{
get { return "@"; }
}
public void AppendColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}, ", columnName.ToUpper());
} public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}{1}, ", BindVariablePrefix, columnName.ToUpper());
} public void AppendUpdateColumnName(StringBuilder sb, string columnName)
{
sb.AppendFormat("{0}={1}{0}, ", columnName.ToUpper(), BindVariablePrefix);
}
}
下面是真实项目的整体框架,如下图:

不是说这样的搭建是好的,可以适合任何的项目,只能说,它适合我,适合现在的需求。现在的web已经是一个泛化的web,网站不是web的全部分,只是web的一小部分。现在的产品是一个web产品矩阵,不仅包括网站而且还包括iOS、Android、微信、微信小程序等。所以把接口单独分离出来,到时候可以单独部署在一台服务器上,作为公共服务,不仅我们的网站可以使用,而且我们的小程序也可以使用。好了,有点扯了,说的不对的还请各位指出来。
四、测试时遇到的问题
(1)链接字符串server=.需要修改为:server=127.0.0.1; 否则会报链接不上数据库的错误,这里就不截图了。
(2)链接字符串需要加上SslMode=None
最后完整的配置文件如下:
{
"db": {
"mysql": {
"conStr": "server=127.0.0.1;charset=gb2312;user id=root;password=123456;persist security info=True;database=dappertest;charset=utf8;SslMode=None",
"providerName": "MySql.Data.MySqlClient"
}
}
}
讲到这里基本上就讲完了,大家如果遇到问题了,可以留言,我看到后会及时回复大家。
五、总结
通过上面的讲解,我们不要为了使用ORM而使用ORM,而忘记了他们底层使用的是ADO.Net,把他们搞明白,比任何ORM都重要!!!,谢谢大家,希望对你有帮助!
六、补充
这里要指出来一点,使用System.Configuration.ConfigurationManager会导致无法跨平台,之前园子里有人介绍过如何使用ConfigurationManager,但是为了跨平台,不建议使用它。那如果不使用System.Configuration.ConfigurationManager,那ConnectionStringSettings就没法使用了,所以需要自定义一个这样的类,代码如下:
using System;
using System.Collections.Generic;
using System.Text; namespace DapperMigrationServices
{
public class ConnectionStringSettings
{
public string ProviderName { get; set; }
public string ConnectionString { get; set; } }
}
一位网友分享的Dapper封装,思想比我的好,大家可以借鉴一下,感谢能分享出来,多看看可以开拓视野:
https://github.com/xakepbean/Dapper-Extensions
作者:郭峥
出处:http://www.cnblogs.com/runningsmallguo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
.Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍的更多相关文章
- .Net Core2.0下使用Dapper遇到的问题
今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下.下面我会还原迁移的每一个过程 ...
- Net Core2.0下使用Dapper
Net Core2.0下使用Dapper 今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和 ...
- 9.2.2 .net framework下的MVC 控件的封装(下)
控件封装的部分说明 可能有人觉得应该前后端分离,我也承认这是应该的方向,我们也在考虑使用ng2等简化前端.但是,我们封装控件还是因为如下原因综合考虑的: 我们这是个框架,上面支撑了许多个应用,包含几百 ...
- 9.2.1 .net framework下的MVC 控件的封装(上)
在写.net core下mvc控件的编写之前,我先说一下.net framework下我们MVC控件的做法. MVC下控件的写法,主要有如下三种,最后一种是泛型的写法,mvc提供的控件都是基本控件. ...
- 基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil
基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil,把日常能用到的各种CRUD都进行了简化封装,让普通程序员只需关注业务即可,因为非常简单,故直接贴源代码,大家若需使用可以直 ...
- STSDB、NDataBase 对象数据库在不同.net framework下无法读取的解决办法
STSDB.NDataBase 等对象数据库将对象保存在文件中后,如果在不同的windows平台.不同的.net frameWork下总是无法读取,原因是对象模式已经不同了. 解决的办法也很简单,就是 ...
- 不同Framework下StringBuilder和String的性能对比,及不同Framework性能比(附Demo)
本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 阅读目录 介绍 环境搭建 测试用例 MSDN说明 ...
- WINCE下进程间通信(二)
WINCE下进程间通信(二) 接着前面的文章<WINCE下进程间通信(一)>,现在介绍进程间通信的另一种方法. 三.管道(消息队列) WINCE并不支持类似于PC机上匿名管道.命名管道的通 ...
- .net core2.0下使用Identity改用dapper存储数据
前言. 已经好多天没写博客了,鉴于空闲无聊之时又兴起想写写博客,也当是给自己做个笔记.过了这么些天,我的文笔还是依然那么烂就请多多谅解了.今天主要是分享一下在使用.net core2.0下的实际遇到的 ...
随机推荐
- 历年NOIP中的搜索题
什么题目都不会做于是开始做搜索题. 然而我搜索题也不会做了. 铁定没戏的蒟蒻. 1.NOIP2004 虫食算 “对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立.输入数据 ...
- 00002、div的文字垂直居中与背景的渐变
1.div可以放多行的文字,当显示文字较少时,文字可垂直居中 text-align: center; display: table-cell; vertical-align: middle; text ...
- PHP+js实现图片上传,编辑
文件上传,点击按钮并选择文件后,文件会临时存到一个位置,会有一个临时名字: 然后在php文件中处理,给文件起名并将文件从临时为止搬到服务器,把需要的文件信息返回给前端页面: 最后表单提交时,把文件信息 ...
- 微信小程序探索(一)
一.小程序到底是个什么鬼? 现在Android系统越用越卡的现状很头疼,手机上再也不想装太多的App,而市面上层出不穷的各种应用,有时又是我们需要用到的,怎么办呢!!微信小程序就是一种不需要下载安装即 ...
- rsync工作机制(翻译)
本篇为rsync官方推荐文章How Rsync Works的翻译,主要内容是Rsync术语说明和简单版的rsync工作原理.本篇没有通篇都进行翻译,前言直接跳过了,但为了文章的完整性,前言部分的原文还 ...
- ES6——块级作用域
前面的话 过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域.本文将详细介绍ES6新引入的块级作用域绑定机制.let和 ...
- nodejs 搭建 RESTful API 服务器的常用包及其简介
常用包 框架: yarn add express 数据库链接: yarn add sequelize yarn add mysql2 处理 favicon: yarn add serve-favico ...
- SpringMVC源码情操陶冶-View视图渲染
本节简单分析View视图对象的render方法 View接口 最重要的就是render()方法,具体源码如下 /** * Render the view given the specified mod ...
- maven:pom.xml中没有dependency标签错误
dependency的标签是包含在dependencies中的.
- IT软件管理人员的职业路线(从技术经理到总经理) - CEO之公司管理经验谈
技术.业务和管理永远是工作的一个话题.笔者今天就根据自身的经验,通过这三个方面介绍下IT软件管理人员的职业路线.前面写过一个文(IT软件技术人员的职位路线(从程序员到技术总监) - 部门管理经验谈), ...