今天成功把.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 Core2.0下使用Dapper遇到的问题的更多相关文章

  1. Net Core2.0下使用Dapper

    Net Core2.0下使用Dapper 今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和 ...

  2. .net core2.0下使用Identity改用dapper存储数据

    前言. 已经好多天没写博客了,鉴于空闲无聊之时又兴起想写写博客,也当是给自己做个笔记.过了这么些天,我的文笔还是依然那么烂就请多多谅解了.今天主要是分享一下在使用.net core2.0下的实际遇到的 ...

  3. .net core2.0下Ioc容器Autofac使用

    .net core发布有一段时间了,最近两个月开始使用.net core2.0开发项目,大大小小遇到了一些问题.准备写个系列介绍一下是如何解决这些问题以及对应技术.先从IOC容器Autofac开始该系 ...

  4. .Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍

    今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下.下面我会还原迁移的每一个过程 ...

  5. NET Core2.0 Memcached踩坑,基于EnyimMemcachedCore整理MemcachedHelper帮助类。

    DotNetCore2.0下使用memcached缓存. Memcached目前微软暂未支持,暂只支持Redis,由于项目历史原因,先用博客园开源项目EnyimMemcachedCore,后续用到的时 ...

  6. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-12基于cookie登录授权认证并实现前台会员、后台管理员同时登录

    1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...

  7. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-10项目各种全局帮助类

    本文目录 1.  前沿2.CacheHelper基于Microsoft.Extensions.Caching.Memory封装3.XmlHelper快速操作xml文档4.SerializationHe ...

  8. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-9项目各种全局帮助类

    本文目录 1.  前沿2.CacheHelper基于Microsoft.Extensions.Caching.Memory封装3.XmlHelper快速操作xml文档4.SerializationHe ...

  9. net core体系-web应用程序-4asp.net core2.0 项目实战(1)-8项目加密解密方案

    本文目录1. 摘要2. MD5加密封装3. AES的加密.解密4. DES加密/解密5. 总结 1.  摘要 C#中常用的一些加密和解密方案,如:md5加密.RSA加密与解密和DES加密等,Asp.N ...

随机推荐

  1. js 时间转字符串,转成yyyy-MM-dd HH:mm:SS格式

    // 时间转字符串,转成yyyy-MM-dd HH:mm:SS格式 function dateToStr(datetime){ var dateTime = new Date(datetime); v ...

  2. CSS margin 外边距 属性的位置关系

    padding:内边距 margin :外边距 margin:10px; 所有 4 个外边距都是 10px ******************************************* ma ...

  3. iOS 指纹解锁 验证TouchID

    iOS指纹解锁 1.首先,引入依赖框架 LocalAuthentication.framework #import <LocalAuthentication/LocalAuthenticatio ...

  4. IPerf——网络测试工具介绍与源码解析(5)

    本篇随笔讲述一下TCP协议下,双向测试模式和交易测试模式下客户端和服务端执行的情况: 双向测试模式: 官方文档的解释 Run Iperf in dual testing mode. This will ...

  5. 排序算法之希尔排序的思想以及Java实现

    1 基本思想 shell排序又称之为缩小增量排序,基本思想是,先将待排序序列分割成若干个特殊的子表,分别进行插入排序,当整个表中元素"基本有序"时,再对全体记录进行一次直接插入排序 ...

  6. wamp 中安装cakephp Fatal error: You must enable the intl extension to use CakePHP. in XXX

    今天在wamp下安装cakephp3.x的时候,报出这么一条错误:Fatal error: You must enable the intl extension to use CakePHP. in ...

  7. Can We Make Operating Systems Reliable and Secure?

    Andrew S. Tanenbaum, Jorrit N. Herder, and Herbert Bos Vrije Universiteit, Amsterdam Microkernels-lo ...

  8. java操作elasticsearch实现query String

    1.CommonTersQuery: 指定字段进行模糊查询 //commonTermsQuery @Test public void test35() throws UnknownHostExcept ...

  9. 面试总结——Java篇

    前言:前期对Java基础的相关知识点进行了总结,具体参看:Java基础和面试知识点.近期由于笔者正在换工作(ing),因此下面将笔者在面试过程中或笔者朋友面试过程中反馈的题目进行总结,相信弄清楚下面题 ...

  10. 设计模式のIOC(控制反转)

    一.什么是Ioc IoC(Inverse of Control)的字面意思是控制反转,它包括两个内容: 控制.反转 可以假设这样一个场景:火车运货,不同类型的车厢运送不同类型的货物,板车运送圆木,罐车 ...