博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.Net Core2.0下使用Dapper遇到的问题
阅读量:5462 次
发布时间:2019-06-16

本文共 15721 字,大约阅读时间需要 52 分钟。

今天成功把.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
TypeInsertPairDictionary = new ConcurrentDictionary
(); private static readonly ConcurrentDictionary
TypeUpdateDictionary = new ConcurrentDictionary
(); private static readonly ConcurrentDictionary
TypeColumnsDictionary = new ConcurrentDictionary
(); 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(0, columns.Length - 2), Values = values.ToString().Substring(0, values.Length - 2) }; 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(0, sb.Length - 2); 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(0, sb.Length - 2); 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
Get
(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
)))) throw new Exception("没有输入表名时只支持数据库实体查询!"); var name = string.IsNullOrEmpty(tableName) ? BaseEntity
.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
(sql, param, transaction); } //根据SQL映射实体或ViewModel //使用:传过来SQL,让Dapper进行映射 public static IEnumerable
GetBySql
(string sql, object param = null, ConnectionStringSettings connectionStringSettings = null, IDbTransaction transaction = null) where T : class { var conn = connectionStringSettings.GetDbConnection(); return conn.Query
(sql, param, transaction); } //根据ID获取单个实体对象 //使用:传过来ID public static T GetById
(string id, string tableName = null, ConnectionStringSettings connectionStringSettings = null, IDbTransaction transaction = null) where T : class { var adapter = connectionStringSettings.GetDataAdapter(); return Get
(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中,代码如下:

1 using MySql.Data.MySqlClient; 2 using System.Data.Common; 3 using System.Data.SqlClient; 4  5 namespace System.Data.CommonExts 6 { 7     public static class DbProviderFactories 8     { 9         /// 10         /// 通过在appsettings.json文件中配置 "providerName",来创建对应的数据库链接11         /// 12         /// 例如:MySql.Data.MySqlClient13         /// 
DbProviderFactory
14 public static DbProviderFactory GetFactory(string providerInvariantName)15 {16 if (string.IsNullOrEmpty(providerInvariantName)) throw new Exception("数据库链接字符串配置不正确!");17 18 if(providerInvariantName.ToLower().Contains("mysql"))19 {20 return new MySqlClientFactory();21 }22 else if(providerInvariantName.ToLower().Contains("sql"))23 {24 return SqlClientFactory.Instance;25 }26 27 throw new Exception("暂不支持您使用的数据库类型!");28 29 }30 }31 }

注意,这里需要安装的包,如下:

  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
("select * from user"); ...... ......}

每次对数据库操作,我都需要先new一个MySqlConnection,太烦。于是就有了,下面的代码:

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

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

通过DBProviderFactories,动态创建数据库链接。

(3)在没有对Dapper进行二次封装,如果我们切换数据库,由于不同数据库的语法不一样,修改的工作量不能忽视,如何屏蔽不同数据库之间语法的不同呢,于是就有了下面的代码:

1 public interface IDataAdapter 2     { 3         string BindVariablePrefix { get; } 4         void AppendColumnName(StringBuilder sb, string columnName); 5         void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); 6         void AppendUpdateColumnName(StringBuilder sb, string columnName); 7     } 8  9  public class MySqlDataAdapter : IDataAdapter10     {11         public string BindVariablePrefix12         {13             get { return "@"; }14         }15         public void AppendColumnName(StringBuilder sb, string columnName)16         {17             sb.AppendFormat("{0}, ", columnName.ToUpper());18         }19 20         public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)21         {22             sb.AppendFormat("{0}{1}, ", BindVariablePrefix, columnName.ToUpper());23         }24 25         public void AppendUpdateColumnName(StringBuilder sb, string columnName)26         {27             sb.AppendFormat("{0}={1}{0}, ", columnName.ToUpper(), BindVariablePrefix);28         }29     }

下面是真实项目的整体框架,如下图:

不是说这样的搭建是好的,可以适合任何的项目,只能说,它适合我,适合现在的需求。现在的web已经是一个泛化的web,网站不是web的全部分,只是web的一小部分。现在的产品是一个web产品矩阵,不仅包括网站而且还包括iOS、Android、微信、微信小程序等。所以把接口单独分离出来,到时候可以单独部署在一台服务器上,作为公共服务,不仅我们的网站可以使用,而且我们的小程序也可以使用。好了,有点扯了,说的不对的还请各位指出来。

四、测试时遇到的问题

 (1)链接字符串server=.需要修改为:server=127.0.0.1; 否则会报链接不上数据库的错误,这里就不截图了。

(2)链接字符串需要加上SslMode=None

最后完整的配置文件如下:

1 {2   "db": {3     "mysql": {4       "conStr": "server=127.0.0.1;charset=gb2312;user id=root;password=123456;persist security info=True;database=dappertest;charset=utf8;SslMode=None",5       "providerName": "MySql.Data.MySqlClient"6     }7   }8 }

讲到这里基本上就讲完了,大家如果遇到问题了,可以留言,我看到后会及时回复大家。

五、总结

通过上面的讲解,我们不要为了使用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/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

转载于:https://www.cnblogs.com/runningsmallguo/p/7430693.html

你可能感兴趣的文章
The eleven Day
查看>>
HTTP 无法注册URL 进程不具有命名空间的访问权限
查看>>
spring 基于multipart 文件上传
查看>>
循环冗余校验(CRC)算法入门引导
查看>>
Swift继承的用法
查看>>
【[六省联考2017]组合数问题】
查看>>
数据结构与算法学习 第1季02 链表的基本功能 C++实现
查看>>
Oracle Listener
查看>>
java String spilt 问题
查看>>
【P3056】【USACO12NOV】笨牛Clumsy Cows
查看>>
准标识符(Quasi-dientifier, QI)
查看>>
深入理解VMware虚拟机网络通信原理
查看>>
Linux命令——find/grep
查看>>
TJU1016
查看>>
HttpClientUitl工具类
查看>>
Could not find or load main class
查看>>
VC 预定义宏
查看>>
indexOf()
查看>>
dom4j对xml读取操作
查看>>
Yii2.0实现微信公众号后台开发
查看>>