CYQ.Data 支持 PostgreSQL 資料庫

来源:https://www.cnblogs.com/cyq1162/archive/2018/12/08/10086488.html
-Advertisement-
Play Games

很久之前,就有同學問我CYQ.Data能不能支持下PostgreSQL,之後小做了下調查,發現這個資料庫用的人少,加上各種因素,就一直沒動手。 前兩天,不小心看了一下Github上的消息:看到這個問題又被重新提了出來了,於是,鬧吧! ...


前言:

很久之前,就有同學問我CYQ.Data能不能支持下PostgreSQL,之後小做了下調查,發現這個資料庫用的人少,加上各種因素,就一直沒動手。

前兩天,不小心看了一下Github上的消息:

看到這個問題又被重新提了出來了,於是,鬧吧!

下麵分享一下支持該資料庫要處理的過程,讓大伙明白CYQ.Data要支持一種新的資料庫,需要花多少功夫。

1、找到資料庫的驅動程式:Npgsql.dll

網上查找了點相關知識,發現.NET 里操作PostgreSQL有兩種提供的dll,一種是正規的收費的,另一種是開源的Npgsql.dll,因此這裡選擇了開源的。

在Nuget上可以搜索Npgsql,不過上面的版本要求依賴的版本很高,於是我找了最早的版本開始支持,畢竟CYQ.Data 是從支持最低2.0及以上的。

這裡是找到的下載低版本支持的網址:http://pgfoundry.org/frs/?group_id=1000140&release_id=1889

同時,下載的兩個2.0和4.0兩個版本,也一併上傳到:https://github.com/cyq1162/cyqdata/tree/master/文檔

2、創建PostgreDal.cs,實現動態載入DLL

添加動態載入的代碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data.Common;
using CYQ.Data.Cache;
using System.IO;

namespace CYQ.Data
{
    internal class PostgreDal : DbBase
    {
        public PostgreDal(ConnObject co)
            : base(co)
        {

        }
        internal static Assembly GetAssembly()
        {
            object ass = CacheManage.LocalInstance.Get("Postgre_Assembly");
            if (ass == null)
            {
                try
                {
                    string name = string.Empty;
                    if (File.Exists(AppConst.RunFolderPath + "Npgsql.dll"))
                    {
                        name = "Npgsql";
                    }
                    else
                    {
                        name = "Can't find the Npgsql.dll";
                        Error.Throw(name);
                    }
                    ass = Assembly.Load(name);
                    CacheManage.LocalInstance.Set("Postgre_Assembly", ass, 10080);
                }
                catch (Exception err)
                {
                    string errMsg = err.Message;
                    Error.Throw(errMsg);
                }
            }
            return ass as Assembly;
        }
        protected override DbProviderFactory GetFactory(string providerName)
        {
            object factory = CacheManage.LocalInstance.Get("Postgre_Factory");
            if (factory == null)
            {
                Assembly ass = GetAssembly();
                factory = ass.GetType("Npgsql.NpgsqlFactory").GetField("Instance").GetValue(null);
               // factory = ass.CreateInstance("Npgsql.NpgsqlFactory.Instance");
                if (factory == null)
                {
                    throw new System.Exception("Can't Create  NpgsqlFactory in Npgsql.dll");
                }
                else
                {
                    CacheManage.LocalInstance.Set("Postgre_Factory", factory, 10080);
                }

            }
            return factory as DbProviderFactory;

        }

        protected override bool IsExistsDbName(string dbName)
        {
            try
            {
                IsAllowRecordSql = false;
                bool result = ExeScalar("select 1 from pg_catalog.pg_database where datname='" + dbName + "'", false) != null;
                IsAllowRecordSql = true;
                return result;
            }
            catch
            {
                return true;
            }
        }
        public override char Pre
        {
            get
            {
                return ':';
            }
        }
        public override void AddReturnPara()
        {

        }
    }
}

幾點說明:

1、GetFactory方法,其它dll框架提供的都是直接實例化,而Npgsql.dll提供卻是單例屬性,所以代碼有點變化。
2、Npgsql操作參數化的符號是“:”號。

3、DalCreate.cs追加PostgreSql類型及資料庫鏈接解析

 

這裡重點發現postgresql和mssql兩者的資料庫鏈接格式都一致:

server=...;uid=xxx;pwd=xxx;database=xxx;

因此從單純的語句上,根本無法判斷從屬於哪種資料庫。

經過小小的思考,解決方案出來了:

else
            {
                //postgre和mssql的鏈接語句一樣,這裡用database=和uid=順序來決定;database寫在後面的,為postgre
                int dbIndex = connString.IndexOf("database=", StringComparison.OrdinalIgnoreCase);
                int uid = connString.IndexOf("uid=", StringComparison.OrdinalIgnoreCase);
                if (uid > 0 && uid < dbIndex && File.Exists(AppConfig.RunPath + "Npgsql.dll"))
                {
                    return PostgreClient;
                }
                return SqlClient;
            }

簡的說:只有滿足引用了npgsql.dll以及database寫在uid之後兩種條件下,判斷為postgresql,其它的都回歸到mssql。

4、處理表結構語句:獲取資料庫表以及表的結構語句:

這一塊花的時間比較多,網上也費了點時間查了不少資料,最後自己寫了語句:

獲取資料庫所有表:

internal static string GetPostgreTables(string dbName)
        {
            return string.Format("select table_name as TableName,cast(obj_description(relfilenode,'pg_class') as varchar) as Description from information_schema.tables t left join  pg_class p on t.table_name=p.relname  where table_schema='public' and table_catalog='{0}'", dbName);
        }

獲取某表的結構:

internal static string GetPostgreColumns()
        {
            return @"select
a.attname AS ColumnName,
case t.typname when 'int4' then 'int' when 'int8' then 'bigint' else t.typname end AS SqlType,
coalesce(character_maximum_length,numeric_precision,-1) as MaxSize,numeric_scale as Scale,
case a.attnotnull when 'true' then 0 else 1 end AS IsNullable,
case  when position('nextval' in column_default)>0 then 1 else 0 end as IsAutoIncrement, 
case when o.conname is null then 0 else 1 end as IsPrimaryKey,
d.description AS Description,
i.column_default as DefaultValue
from pg_class c 
left join pg_attribute a on c.oid=a.attrelid
left join pg_description d on a.attrelid=d.objoid AND a.attnum = d.objsubid
left join pg_type t on a.atttypid = t.oid
left join information_schema.columns i on i.table_schema='public' and i.table_name=c.relname and i.column_name=a.attname
left join pg_constraint o on a.attnum = o.conkey[1] and o.contype='p'
where c.relname =:TableName
and a.attnum > 0 and a.atttypid>0
ORDER BY a.attnum";
        }

5、處理關鍵字元號

由於PostgreSQL的大小寫敏感,而且關鍵字加需要用雙引號包含(這點和SQLite一致):

這裡在原有的基礎上加上case即可。

6、處理差異化的SQL語句:SqlCreate.cs

A、獲取插入後的自增值,這裡可以借用一下自增列產生的預設值:

這裡用預設值,替換一下nextval序列為currval序列即可。

 else if (_action.dalHelper.dalType == DalType.PostgreSQL)
                        {
                            string key = Convert.ToString(primaryCell.Struct.DefaultValue);
                            if (!string.IsNullOrEmpty(key))
                            {
                                key = key.Replace("nextval", "currval");
                                sql = sql + "; select " + key + " as OutPutValue";
                            }
                        }

 B、需要引用關鍵字的地方:

略。。。。

7、處理分頁語句:SqlCreateForPager.cs

這裡PostgreSQL和分頁和sqlite及mysql是一致的,因此只要在相關的地方補上case即可:

public static string GetSql(DalType dalType, string version, int pageIndex, int pageSize, object objWhere, string tableName, int rowCount, string columns, string primaryKey, bool primaryKeyIsIdentity)
        {
            if (string.IsNullOrEmpty(columns))
            {
                columns = "*";
            }
            pageIndex = pageIndex == 0 ? 1 : pageIndex;
            string where = SqlFormat.GetIFieldSql(objWhere);
            if (string.IsNullOrEmpty(where))
            {
                where = "1=1";
            }
            if (pageSize == 0)
            {
                return string.Format(top1Pager, columns, tableName, where);
            }
            if (rowCount > 0)//分頁查詢。
            {
                where = SqlCreate.AddOrderBy(where, primaryKey);
            }
            int topN = pageIndex * pageSize;//Top N 最大數
            int max = (pageIndex - 1) * pageSize;
            int rowStart = (pageIndex - 1) * pageSize + 1;
            int rowEnd = rowStart + pageSize - 1;
            string orderBy = string.Empty;
            if (pageIndex == 1 && dalType != DalType.Oracle)//第一頁(oracle時 rownum 在排序條件為非數字時,和row_number()的不一樣,會導致結果差異,所以分頁統一用row_number()。)
            {
                switch (dalType)
                {
                    case DalType.Access:
                    case DalType.MsSql:
                    case DalType.Sybase:
                        return string.Format(top1Pager, "top " + pageSize + " " + columns, tableName, where);
                    //case DalType.Oracle:
                    //    return string.Format(top1Pager, columns, tableName, "rownum<=" + pageSize + " and " + where);
                    case DalType.SQLite:
                    case DalType.MySql:
                    case DalType.PostgreSQL:
                        return string.Format(top1Pager, columns, tableName, where + " limit " + pageSize);
                }
            }
            else
            {

                switch (dalType)
                {
                    case DalType.Access:
                    case DalType.MsSql:
                    case DalType.Sybase:
                        int leftNum = rowCount % pageSize;
                        int pageCount = leftNum == 0 ? rowCount / pageSize : rowCount / pageSize + 1;//頁數
                        if (pageIndex == pageCount && dalType != DalType.Sybase) // 最後一頁Sybase 不支持雙Top order by
                        {
                            return string.Format(top2Pager, pageSize+" "+columns, "top " + (leftNum == 0 ? pageSize : leftNum) + " * ", tableName, ReverseOrderBy(where, primaryKey), GetOrderBy(where, false, primaryKey));//反序
                        }
                        if ((pageCount > 1000 || rowCount > 100000) && pageIndex > pageCount / 2) // 頁數過後半段,反轉查詢
                        {
                            orderBy = GetOrderBy(where, false, primaryKey);
                            where = ReverseOrderBy(where, primaryKey);//事先反轉一次。
                            topN = rowCount - max;//取後面的
                            int rowStartTemp = rowCount - rowEnd;
                            rowEnd = rowCount - rowStart;
                            rowStart = rowStartTemp;
                        }
                        break;

                }
            }


            switch (dalType)
            {
                case DalType.MsSql:
                case DalType.Oracle:
                    if (version.StartsWith("08"))
                    {
                        goto temtable;
                        // goto top3;//sql 2000
                    }
                    int index = tableName.LastIndexOf(')');
                    if (index > 0)
                    {
                        tableName = tableName.Substring(0, index + 1);
                    }
                    string v = dalType == DalType.Oracle ? "" : " v";
                    string onlyWhere = "where " + SqlCreate.RemoveOrderBy(where);
                    onlyWhere = SqlFormat.RemoveWhereOneEqualsOne(onlyWhere);
                    return string.Format(rowNumberPager, GetOrderBy(where, false, primaryKey), (columns == "*" ? "t.*" : columns), tableName, onlyWhere, v, rowStart, rowEnd);
                case DalType.Sybase:
                temtable:
                    if (primaryKeyIsIdentity)
                    {
                        bool isOk = columns == "*";
                        if (!isOk)
                        {
                            string kv = SqlFormat.NotKeyword(primaryKey);
                            string[] items = columns.Split(',');
                            foreach (string item in items)
                            {
                                if (string.Compare(SqlFormat.NotKeyword(item), kv, StringComparison.OrdinalIgnoreCase) == 0)
                                {
                                    isOk = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            columns = "t.*";
                            index = tableName.LastIndexOf(')');
                            if (index > 0)
                            {
                                tableName = tableName.Substring(0, index + 1);
                            }
                            tableName += " t ";
                        }
                        if (isOk)
                        {

                            return string.Format(tempTablePagerWithIdentity, DateTime.Now.Millisecond, topN, primaryKey, tableName, where, pageSize, columns, rowStart, rowEnd, orderBy);
                        }
                    }
                    return string.Format(tempTablePager, DateTime.Now.Millisecond, pageIndex * pageSize + " " + columns, tableName, where, pageSize, rowStart, rowEnd, orderBy);
                case DalType.Access:
                top3:
                    if (!string.IsNullOrEmpty(orderBy)) // 反轉查詢
                    {
                        return string.Format(top4Pager,columns, (rowCount - max > pageSize ? pageSize : rowCount - max), topN, tableName, where, GetOrderBy(where, true, primaryKey), GetOrderBy(where, false, primaryKey), orderBy);
                    }
                    return string.Format(top3Pager, (rowCount - max > pageSize ? pageSize : rowCount - max),columns, topN, tableName, where, GetOrderBy(where, true, primaryKey), GetOrderBy(where, false, primaryKey));
                case DalType.SQLite:
                case DalType.MySql:
                case DalType.PostgreSQL:
                    if (max > 500000 && primaryKeyIsIdentity && Convert.ToString(objWhere) == "" && !tableName.Contains(" "))//單表大數量時的優化成主鍵訪問。
                    {
                        where = string.Format("{0}>=(select {0} from {1} limit {2}, 1) limit {3}", primaryKey, tableName, max, pageSize);
                        return string.Format(top1Pager, columns, tableName, where);
                    }
                    return string.Format(top1Pager, columns, tableName, where + " limit " + pageSize + " offset " + max);
            }
            return (string)Error.Throw("Pager::No Be Support:" + dalType.ToString());
        }

總結:

一個資料庫的基本支持、寫到這裡就完成了增刪改查及分頁。

當然,對於CYQ.Data而言,還差一些未處理:

1、多種資料庫轉換互通處理:DataType.cs。

2、對錶的創建修改操作:SqlCreateForSchema.cs。

3、支持多資料庫相容性寫法:SqlCompatible.cs。

4、其它細節。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 給出一棵樹,問每一層各有多少葉子節點 dfs遍歷樹 bfs遍歷求樹 ...
  • 對象的創建 虛擬機遇到一條new指令時,首先檢查指令的參數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化過。如果沒有,必須先執行相應的類載入過程。 接下來虛擬機為新生對象分配記憶體。對象所需要的記憶體在類載入完成後可以被完全確定,所以只需要把一塊確定大小的 ...
  • 題意:給出倆個整數a,b(不超過10^9) ,求a+b的值 ,並按照xxx,xxx,xxx的格式輸出 ...
  • 實現分散式鎖目前有三種流行方案,分別為基於資料庫、Redis、Zookeeper的方案,其中前兩種方案網路上有很多資料可以參考,本文不做展開。我們來看下使用Zookeeper如何實現分散式鎖。 什麼是Zookeeper? Zookeeper(業界簡稱zk)是一種提供配置管理、分散式協同以及命名的中心 ...
  • logging 日誌模塊 一,級別的概念 二,修改配置信息 #format的多種格式 三,具體需求的實現 #暫時忽略,要用到面向對象的值是,而且可以通過生成器setlevel過濾 更改需求,實現對多個對象不同需求的實現 重點,四,引用配置模塊 #logging.config 專門用於配置loggin ...
  • 一、函數介面 |介面|參數|返回類型|描述| |: :|: :|: :|: :| |Predicate<T>|T|boolean|用來比較操作| |Consumer<T>|T|void|沒有返回值的函數| |Function|T|R|有返回值的函數| |Supplier< ...
  • #__author:Mifen #date: 2018/12/6 import time ''' 時間戳是一種用於表示時間的方式。從1970年1月1日0時0分0秒0毫秒開始到指定時間的秒數。世間戳也叫做unix時間戳,1970年1月1日成為unix元年。 作用:是為了方便時間的統一運算。 1.從19... ...
  • 開始講解之前,先列出本章的提綱,如下圖所示: 5.1 String類 5.1.1 聲明字元串 5.1.2 創建字元串 輸出結果如下圖所示: 5.2 連接字元串 5.2.1 連接多個字元串 註意事項:Java中一句相連的字元串不能分開在兩行中寫,如下圖所示 可以看出,這種寫法是錯誤,無法編譯通過,如果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...