.Net Core2.0下使用Dapper遇到的问题

今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程当中也遇到一些颇有意思的问题,值得和你们分享一下。下面我会还原迁移的每个过程,以及在此过程当中遇到的问题和处理这些问题的方法。html

1、迁移前的准备

以前对Dapper的封装使用的是.Net Framework下的ORM 框架Dapper,开发工具VS2013,如今既然想在.Net Core2.0上使用Dapper,我要先到NuGet看看有没有支持 .Net Core的,在Nuget找到以下:mysql

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

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

2、正式迁移

.Net Framework下对Dapper进行的二次封装,代码部分以下,后面会介绍我为何要这样封装:web

 

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(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<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包:sql

(1)数据库

在图中标识的项目中须要引用Dapper和MySQLjson

  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         /// <summary>
10         /// 经过在appsettings.json文件中配置 "providerName",来建立对应的数据库连接
11         /// </summary>
12         /// <param name="providerInvariantName">例如:MySql.Data.MySqlClient</param>
13         /// <returns>DbProviderFactory</returns>
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以及正式项目中为何要这样搭建。

3、对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进行二次封装,若是咱们切换数据库,因为不一样数据库的语法不同,修改的工做量不能忽视,如何屏蔽不一样数据库之间语法的不一样呢,因而就有了下面的代码:

 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 : IDataAdapter
10     {
11         public string BindVariablePrefix
12         {
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、微信、微信小程序等。因此把接口单独分离出来,到时候能够单独部署在一台服务器上,做为公共服务,不只咱们的网站可使用,并且咱们的小程序也可使用。好了,有点扯了,说的不对的还请各位指出来。

4、测试时遇到的问题

 (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 }

讲到这里基本上就讲完了,你们若是遇到问题了,能够留言,我看到后会及时回复你们。

5、总结

经过上面的讲解,咱们不要为了使用ORM而使用ORM,而忘记了他们底层使用的是ADO.Net,把他们搞明白,比任何ORM都重要!!!,谢谢你们,但愿对你有帮助!

 

6、补充

这里要指出来一点,使用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/

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

相关文章
相关标签/搜索