.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/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

相關文章
相關標籤/搜索