1. 程式人生 > >使用Dapper.Contrib 開發.net core程式,相容多種資料庫

使用Dapper.Contrib 開發.net core程式,相容多種資料庫

原文: 使用Dapper.Contrib 開發.net core程式,相容多種資料庫

關於Dapper的介紹,我想很多人都對它有一定的瞭解,這個類似一個輕型的ORM框架是目前應用非常火的一個東西,據說各方面的效能都不錯,而且可以支援多種資料庫,在開始介紹這個文章之前,我花了不少功夫來學習了Dapper 的相關使用。Dapper.Contrib是對Dapper的進一步封裝,使物件的基本增刪改查等操作進一步簡化,我做了一個案例使用Dapper.Contrib 開發.net core程式,測試它對多種資料庫的處理。

1、Dapper.Contrib的使用

前面介紹過,Dapper.Contrib是對Dapper的進一步封裝,使物件的基本增刪改查等操作進一步簡化。

它主要是通過特性對映的方式實現自定義類和資料庫之間的關係處理,如下是實體類的定義資訊。

    [Table("T_Customer")]
    public class CustomerInfo
    {
        [ExplicitKey]//非自增長的用此標識
        public virtual string ID { get; set; }

        public virtual string Name { get; set; }

        public virtual int Age { get; set; }

        
public virtual string Creator { get; set; } public virtual DateTime CreateTime { get; set; } }

Dapper.Contrib的所有實體配置選項

  • Table:指定實體對應地資料庫表名,如果類名和資料庫表名不同,需要設定(如案例所示)
  • Key:指定此列為自動增長主鍵
  • ExplicitKey:指定此列為非自動增長主鍵(例如guid,字串列)
  • Computed:計算屬性,此列不作為更新
  • Write:指定列是否可寫

通過定義好實體類和資料庫表的對映關係,就可以通過強型別處理相關的介面了,如下所示。

T Get<T>(id);
IEnumerable<T> GetAll<T>();
int Insert<T>(T obj);
int Insert<T>(Enumerable<T> list);
bool Update<T>(T obj);
bool Update<T>(Enumerable<T> list);
bool Delete<T>(T obj);
bool Delete<T>(Enumerable<T> list);
bool DeleteAll<T>();

這樣通過對映指定表名或者欄位資訊後,就可以知道類和表之間的關係,可以封裝對應的強型別處理介面了。

2、Dapper.Contrib 開發.net core程式

我們建立一個空白的.net core程式框架後,就在它的基礎上做一些Dapper的資料庫測試。

首先為了考慮多資料庫的處理,我們需要建立一個配置檔案,並可以動態配置不同的資料庫,配置檔案appSettings.json如下所示。

上面我配置了多種資料庫的連線字串,並且通過動態指定節點名稱和資料庫型別,來實現對專案指向不同資料庫的訪問。

例如我們準備需要讓Dapper支援我們常見的資料庫型別,如下定義資料庫型別。

    /// <summary>
    /// 資料庫型別定義
    /// </summary>
    public enum DatabaseType
    {
        SqlServer,  //SQLServer資料庫
        MySql,      //Mysql資料庫
        Npgsql,     //PostgreSQL資料庫
        Oracle,     //Oracle資料庫
        Sqlite,     //SQLite資料庫
        DB2         //IBM DB2資料庫
    }

對於不同的資料庫資訊,我們需要根據不同的配置連線字串,並建立對應的資料庫連線物件供Dapper使用,如對於SQLServer的資料庫,那麼建立的是SqlConnection物件,對於Mysql,建立的是MySqlConnection連線物件,對於PostgreSQL對應的是NpgsqlConnection,以此類推。而Dapper則通過對連線物件的擴充套件實現了多種資料請求。

對於多資料庫的支援,我們需要統一解析配置內容appSetting.json的內容,並返回不同資料庫的連線物件,如下是連線工廠的統一處理方式,通過 CreateConnection() 返回配置的連線物件。

    /// <summary>
    /// 資料庫連線輔助類
    /// </summary>
    public class ConnectionFactory
    {
        /// <summary>
        /// 轉換資料庫型別
        /// </summary>
        /// <param name="databaseType">資料庫型別</param>
        /// <returns></returns>
        private static DatabaseType GetDataBaseType(string databaseType)
        {
            DatabaseType returnValue = DatabaseType.SqlServer;
            foreach (DatabaseType dbType in Enum.GetValues(typeof(DatabaseType)))
            {
                if (dbType.ToString().Equals(databaseType, StringComparison.OrdinalIgnoreCase))
                {
                    returnValue = dbType;
                    break;
                }
            }
            return returnValue;
        }

        /// <summary>
        /// 獲取資料庫連線
        /// </summary>
        /// <returns></returns>
        public static IDbConnection CreateConnection()
        {
            IDbConnection connection = null;

            //獲取配置進行轉換
            var type = AppConfig.GetConfig("ComponentDbType");
            var dbType = GetDataBaseType(type);

            //DefaultDatabase 根據這個配置項獲取對應連線字串
            var database = AppConfig.GetConfig("DefaultDatabase");
            if (string.IsNullOrEmpty(database))
            {
                database = "sqlserver";//預設配置
            }
            var strConn = AppConfig.Configuration.GetConnectionString(database);

            switch (dbType)
            {
                case DatabaseType.SqlServer:
                    connection = new System.Data.SqlClient.SqlConnection(strConn);
                    break;
                case DatabaseType.MySql:
                    connection = new MySql.Data.MySqlClient.MySqlConnection(strConn);
                    break;
                case DatabaseType.Npgsql:
                    connection = new Npgsql.NpgsqlConnection(strConn);
                    break;
                case DatabaseType.Sqlite:
                    connection = new SQLiteConnection(strConn);
                    break;
                case DatabaseType.Oracle:
                    connection = new Oracle.ManagedDataAccess.Client.OracleConnection(strConn);
                    //connection = new System.Data.OracleClient.OracleConnection(strConn);
                    break;
                case DatabaseType.DB2:
                    //connection = new System.Data.OleDb.OleDbConnection(strConn);
                    break;
            }

            return connection;
        }
    }

有了資料庫物件工廠,我們的配置就可以動態化了。

下面我們來看看,獲得這些連線物件後,如何通過Dapper.Contrib來獲取對應的物件了,下面的類是常規的對資料庫資訊的處理,包括常規的增刪改查等基礎介面。

    /// <summary>
    /// 常規的資料訪問層
    /// </summary>
    public class Customer
    {
        public IDbConnection Connection
        {
            get
            {
                var connection = ConnectionFactory.CreateConnection();
                connection.Open();
                return connection;
            }
        }

        public IEnumerable<CustomerInfo> GetAll()
        {
            using (IDbConnection dbConnection = Connection)
            {
                return dbConnection.GetAll<CustomerInfo>();
                //return dbConnection.Query<CustomerInfo>("SELECT * FROM T_Customer");
            }
        }


        public CustomerInfo FindByID(string id)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return dbConnection.Get<CustomerInfo>(id);
                //string query = "SELECT * FROM T_Customer WHERE ID = @Id";
                //return dbConnection.Query<CustomerInfo>(query, new { Id = id }).FirstOrDefault();
            }
        }

        public bool Insert(CustomerInfo info)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Insert(info) > 0;
                result = true;
                //string query = "INSERT INTO T_Customer (ID, Name, Age, Creator, CreateTime)"
                //                + " VALUES(@ID, @Name, @Age, @Creator, @CreateTime)";
                //result = dbConnection.Execute(query, info) > 0;
            }
            return result;
        }
        
        public bool Update(CustomerInfo prod)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Update(prod);
                //string query = "UPDATE T_Customer SET Name = @Name,"
                //               + " Age = @Age, Creator= @Creator, [email protected]"
                //               + " WHERE ID = @ID";
                //result = dbConnection.Execute(query, prod) > 0;
            }
            return result;
        }
        public bool Delete(string id)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Delete(new CustomerInfo { ID = id });
                //string query = "DELETE FROM T_Customer WHERE ID = @Id";
                //result = dbConnection.Execute(query, new { ID = id }) > 0;
            }
            return result;
        }
        public bool DeleteAll()
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.DeleteAll<CustomerInfo>();
                //string query = "DELETE FROM T_Customer WHERE ID = @Id";
                //result = dbConnection.Execute(query, new { ID = id }) > 0;
            }
            return result;
        }
    }

其中的備註部分的程式碼是等同於上面的執行程式碼的,是Dapper 的SQL版本的一種處理方式。

我們看到,對於Customer表來說,使用物件的介面處理,我們已經隔離了很多硬編碼的SQL處理,不過我們還可以對它進行進一步的優化處理。

我們定義一個通用的BaseDAL來剝離常規的增刪改查處理,並且把同步和非同步的操作分來兩個檔案來管理,同步處理的基類如下程式碼所示。

    /// <summary>
    /// 資料庫訪問基類
    /// </summary>
    /// <typeparam name="T">實體類型別</typeparam>
    public partial class BaseDAL<T> where T : class
    {
        /// <summary>
        /// 物件的表名
        /// </summary>
        public string TableName { get; set; }

        /// <summary>
        /// 主鍵屬性物件
        /// </summary>
        public PropertyInfo PrimaryKey { get; set; }

        public BaseDAL()
        {
            this.TableName = EntityHelper.GetTableName(typeof(T));
            this.PrimaryKey = EntityHelper.GetSingleKey<T>();
        }

        /// <summary>
        /// 資料庫連線
        /// </summary>
        protected IDbConnection Connection
        {
            get
            {
                var connection = ConnectionFactory.CreateConnection();
                connection.Open();
                return connection;
            }
        }

        /// <summary>
        /// 返回資料庫所有的物件集合
        /// </summary>
        /// <returns></returns>
        public IEnumerable<T> GetAll()
        {
            using (IDbConnection dbConnection = Connection)
            {
                return dbConnection.GetAll<T>();
            }
        }

        /// <summary>
        /// 查詢資料庫,返回指定ID的物件
        /// </summary>
        /// <param name="id">主鍵的值</param>
        /// <returns></returns>
        public T FindByID(object id)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return dbConnection.Get<T>(id);
            }
        }

        /// <summary>
        /// 插入指定物件到資料庫中
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public bool Insert(T info)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                dbConnection.Insert(info);
                result = true;
            }
            return result;
        }
        /// <summary>
        /// 插入指定物件集合到資料庫中
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public bool Insert(IEnumerable<T> list)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Insert(list) > 0;
            }
            return result;
        }

        /// <summary>
        /// 更新物件屬性到資料庫中
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public bool Update(T info)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Update(info);
            }
            return result;
        }
        /// <summary>
        /// 更新指定物件集合到資料庫中
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public bool Update(IEnumerable<T> list)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Update(list);
            }
            return result;
        }
        /// <summary>
        /// 從資料庫中刪除指定物件
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public bool Delete(T info)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Delete(info);
            }
            return result;
        }
        /// <summary>
        /// 從資料庫中刪除指定物件集合
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public bool Delete(IEnumerable<T> list)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.Delete(list);
            }
            return result;
        }
        /// <summary>
        /// 根據指定物件的ID,從資料庫中刪除指定物件
        /// </summary>
        /// <param name="id">物件的ID</param>
        /// <returns></returns>
        public bool Delete(object id)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                string query = string.Format("DELETE FROM {0} WHERE {1} = @id", TableName, PrimaryKey.Name);
                var parameters = new DynamicParameters();
                parameters.Add("@id", id);

                result = dbConnection.Execute(query, parameters) > 0;
            }
            return result;
        }
        /// <summary>
        /// 從資料庫中刪除所有物件
        /// </summary>
        /// <returns></returns>
        public bool DeleteAll()
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                result = dbConnection.DeleteAll<T>();
            }
            return result;
        }

    }

非同步類的程式碼如下所示。

    /// <summary>
    /// 資料庫訪問基類
    /// </summary>
    /// <typeparam name="T">實體類型別</typeparam>
    public partial class BaseDAL<T> where T : class
    {
        /// <summary>
        /// 返回資料庫所有的物件集合
        /// </summary>
        /// <returns></returns>
        public virtual async Task<IEnumerable<T>> GetAllAsync()
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.GetAllAsync<T>();
            }
        }

        /// <summary>
        /// 查詢資料庫,返回指定ID的物件
        /// </summary>
        /// <param name="id">主鍵的值</param>
        /// <returns></returns>
        public virtual async Task<T> FindByIDAsync(object id)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.GetAsync<T>(id);
            }
        }
        /// <summary>
        /// 插入指定物件到資料庫中
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public virtual async Task<bool> InsertAsync(T info)
        {
            bool result = false;
            using (IDbConnection dbConnection = Connection)
            {
                await dbConnection.InsertAsync(info);
                result = true;
            }
            return await Task<bool>.FromResult(result);
        }

        /// <summary>
        /// 插入指定物件集合到資料庫中
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public virtual async Task<bool> InsertAsync(IEnumerable<T> list)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.InsertAsync(list) > 0;
            }
        }
        /// <summary>
        /// 更新物件屬性到資料庫中
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public virtual async Task<bool> UpdateAsync(T info)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.UpdateAsync(info);
            }
        }

        /// <summary>
        /// 更新指定物件集合到資料庫中
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public virtual async Task<bool> UpdateAsync(IEnumerable<T> list)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.UpdateAsync(list);
            }
        }

        /// <summary>
        /// 從資料庫中刪除指定物件
        /// </summary>
        /// <param name="info">指定的物件</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(T info)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.DeleteAsync(info);
            }
        }

        /// <summary>
        /// 從資料庫中刪除指定物件集合
        /// </summary>
        /// <param name="list">指定的物件集合</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(IEnumerable<T> list)
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.DeleteAsync(list);
            }
        }

        /// <summary>
        /// 根據指定物件的ID,從資料庫中刪除指定物件
        /// </summary>
        /// <param name="id">物件的ID</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(object id)
        {
            using (IDbConnection dbConnection = Connection)
            {
                string query = string.Format("DELETE FROM {0} WHERE {1} = @id", TableName, PrimaryKey.Name);
                var parameters = new DynamicParameters();
                parameters.Add("@id", id);

                return await dbConnection.ExecuteAsync(query, parameters) > 0;
            }
        }

        /// <summary>
        /// 從資料庫中刪除所有物件
        /// </summary>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAllAsync()
        {
            using (IDbConnection dbConnection = Connection)
            {
                return await dbConnection.DeleteAllAsync<T>();
            }
        }
    }

這樣,我們如果需要增加一個如客戶資訊表的管理類,就很簡單的繼承基類就可以了,程式碼很少,但是增刪改查介面一個也少不了。

    /// <summary>
    /// 繼承基類物件管理
    /// </summary>
    public class CustomerDAL :BaseDAL<CustomerInfo>
    {
    }

為了測試一下資料訪問層的處理介面,我建立了一個.net core的控制檯程式進行測試,如下專案檢視所示。

主要目的是確認資料處理的效果。

我們在Program.cs類裡面增加相關的測試程式碼,為了簡便和處理效果沒有用UnitTest處理。

            //建立管理物件,並測試介面
            var customer = new CustomerDAL();
            var list = customer.GetAll();
            foreach (var item in list)
            {
                Console.WriteLine(item.ToJson());
                var info = customer.FindByID(item.ID);
                Console.WriteLine(info.ToJson());
                Console.WriteLine();
            }

            //插入記錄
            var insertInfo = new CustomerInfo() { Name = "test", Age = 30, Creator = "test" };
            var insertList = new List<CustomerInfo>() { insertInfo };
            var flag = customer.Insert(insertList);
            Console.WriteLine("插入操作" + (flag ? "成功" : "失敗"));

            Console.WriteLine("插入的新內容");
            insertInfo = customer.FindByID(insertInfo.ID);
            Console.WriteLine(insertInfo.ToJson());

            Console.WriteLine("更新內容");
            insertInfo.Name = "Test" + DateTime.Now.ToShortDateString();
            flag = customer.Update(insertInfo);
            Console.WriteLine("更新操作" + (flag ? "成功" : "失敗"));

            Console.WriteLine("更新的新內容");
            insertInfo = customer.FindByID(insertInfo.ID);
            Console.WriteLine(insertInfo.ToJson());

            Console.WriteLine("刪除內容");
            flag = customer.Delete(insertInfo.ID);
            Console.WriteLine("刪除操作" + (flag ? "成功" : "失敗"));


            Console.WriteLine("所有內容");
            list = customer.GetAll();
            foreach (var item in list)
            {
                Console.WriteLine(item.ToJson());
                Console.WriteLine();
            }

            Console.ReadLine();

測試Mysql、SQLite資料庫同樣沒有問題

Mysql配置資訊如下

處理的Mysql記錄資訊如下。 

SQLite配置資訊如下

處理SQLite資料資訊如下

 

 而在處理PostgreSQL的資訊(配置節點npgsql裡面)的時候,查詢的主鍵好像和大小寫有關係,導致插入記錄出錯。

而Oracle我採用的是Oracle.ManagedDataAccess.Core進行訪問,由於我本地Oracle資料庫偵聽處理有點問題,因此沒有測試成功,暫不予置評。

而對於資料庫的支援問題,導致我重新稽核一下是否採用Dapper.Contrib還是其他Dapper方式來構建資料庫訪問基類的問題,我需要相容多種資料庫的資訊,並且能夠儘可能的封裝常規的增刪改查等操作,其中目前的基類還沒有加入更加複雜的查詢操作,分頁操作等功能,在解決這些困惑問題,才會繼續考慮把底層支援的介面全部完善。