SOD框架使用金仓数据库“踩坑记”

SOD框架使用金仓数据库"踩坑记",严格来说是使用金仓数据库过程的踩坑记,并不是使用SOD框架来访问金仓数据库才会发生的问题,SOD框架的网友多年前就封装了人大金仓(现在已经改名为"电科金仓")和达梦数据库的SOD框架数据提供程序,对应的Nuget包名字分别是 PDF.NET.SOD.Dameng.Provider, PDF.NET.SOD.Kingbase.Provider ,所以当我第一次使用金仓数据库遇到问题时候疑惑为什么使用SOD框架会有问题,而别的ORM框架似乎没有这样的问题?

第一个问题:神秘的sys_stat_scan_tables 角色

SOD框架决定全面支持.NET6的时候,对金仓数据访问提供程序也进行了升级,使用的是Kdbndp 的Nuget包 8.0版本,正好它的说明也是只支持.NET6以上版本。升级后的SOD框架金仓数据访问提供程序Nuget包ID为PWMIS.SOD.Kingbase.Provider ,使用它访问最金仓V9数据库的时候出现下面的问题:

"22P02: invalid input value for enum information_schema.table_type_enum: \"FOREIGN\"\r\n\r\nPOSITION: 135"

经排查这个问题发生在调用ADO.NET的DbConnection的抽象方法GetSchema 方法有关,可以使用下面的代码进行测试:

复制代码
using Kdbndp;
using System.Data;

namespace KingbaseTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Kingbase For .NET6 Access Test.");
            string connStr = System.Configuration.ConfigurationManager.ConnectionStrings[1].ConnectionString;
            Console.WriteLine(connStr);
            using (KdbndpConnection conn = new KdbndpConnection(connStr))
            {
                conn.Open();
                Console.WriteLine("Database Connected OK.");
                DataTable dt = conn.GetSchema();
                Console.WriteLine("GetScheme OK,Database has Schemas count:{0}",dt.Rows.Count);
                DataTable dt2 = conn.GetSchema("Tables");
                Console.WriteLine("Database has tables count:{0}", dt2.Rows.Count);
                conn.Close();
            }
            Console.WriteLine("Test OK");
        }
    }
}

与金仓技术人员进行多次沟通后,终于发现问题在金仓的MySQL数据库兼容模式下,非System用户访问金仓数据库的时候对于 information_schema.tables 对象没有访问权限:

select table_schema FROM information_schema.tables group by table_schema;

提示用户没有访问 sys_freespace 函数的权限:

42501: permission denied for function sys_freespace

出现这个问题的时候必须授权当前访问金仓数据库的用户有 sys_stat_scan_tables 角色。

划重点:这个问题很很重要,当你需要使用SOD框架的Code First编码方式使用金仓数据库的时候必须要确保用户有 sys_stat_scan_tables 角色权限。

划重点 :这个问题仅出现在MySQL兼容模式中。

第二个问题:令人迷惑的金仓驱动程序版本

当程序运行在金仓为客户定制的某V8版本的MySQL兼容模式数据库上的时候,运行上面这个测试程序又出现了下面的问题:

复制代码
System.ArgumentException
  HResult=0x80070057
  Message=A KingbaseES type with the name int16 was not found in the database
  Source=Kdbndp
  StackTrace:
   在 Kdbndp.Internal.KdbndpDatabaseInfo.GetKingbaseTypeByName(String pgName)
   在 Kdbndp.TypeMapping.BuiltInTypeHandlerResolver..ctor(KdbndpConnector connector)
   在 Kdbndp.TypeMapping.BuiltInTypeHandlerResolverFactory.Create(KdbndpConnector connector)
   在 Kdbndp.TypeMapping.ConnectorTypeMapper.Reset()
   在 Kdbndp.Internal.KdbndpConnector.<LoadDatabaseInfo>d__199.MoveNext()
   在 Kdbndp.Internal.KdbndpConnector.<Open>d__198.MoveNext()
   在 Kdbndp.ConnectorPool.<OpenNewConnector>d__34.MoveNext()
   在 Kdbndp.ConnectorPool.<<Get>g__RentAsync|31_0>d.MoveNext()
   在 Kdbndp.KdbndpConnection.<<Open>g__OpenAsync|47_0>d.MoveNext()
   在 Kdbndp.KdbndpConnection.Open()

又是一个复杂的排查过程,在反复确认了数据库的版本之后,金仓技术人员找到了问题原因:驱动程序版本不正确。使用金仓公司指定的,对应的Nuget包名字为 Kdbndp_V9 这个.NET驱动程序后问题果然解决。于是,我不得不用这个驱动程序重新封装了一个SOD框架访问金仓数据库的数据访问提供程序,这就是为什么有PWMIS.SOD.Kingbase.Provider 了之后,还需要 PWMIS.SOD.Kingbase.Provider.Net6V9,PWMIS.SOD.Kingbase.Provider.Net8V9 两个Nuget包的原因。简单总结一下:

  1. PDF.NET.SOD.Kingbase.Provider --SOD框架适用于.NET 4.x 版本的金仓数据访问提供程序;
  2. PWMIS.SOD.Kingbase.Provider --SOD框架适用于.NET 6以上,采用Kdbndp 8.0版本驱动程序,可以访问金仓V8以及V9版本的数据库;
  3. PWMIS.SOD.Kingbase.Provider.Net6V9 --SOD框架使用.NET 6版本的,采用Kdbndp_V9 版本驱动程序,专为金仓V9版本和某些定制的V8版本数据库访问而定制的驱动程序,当然也可以访问V8版数据库。
  4. PWMIS.SOD.Kingbase.Provider.Net8V9 --SOD框架使用.NET 8版本的,采用Kdbndp_V9 版本驱动程序,专为金仓V9版本和某些定制的V8版本数据库访问而定制的驱动程序,当然也可以访问V8版数据库。

综上,你在具体使用金仓数据库的时候需要使用哪个版本的SOD框架提供程序,可以根据情况测试后使用。

划重点: 如果你默认使用金仓的Oracle兼容模式,就不会遇到上面两个问题,直接使用PWMIS.SOD.Kingbase.Provider 驱动程序即可。

为了方便在SOD框架中使用上述不同的驱动程序,可以添加一个应用程序配置文件 app.config文件,在connectionStrings 配置节添加如下配置内容:

复制代码
 <connectionStrings>
  <!--
    <add name="local1" 
         connectionString="Server=127.0.0.1;User Id=system;Password=system;Database=test;Port=54321" 
         providerName="PWMIS.DataProvider.Data.Kingbase,PWMIS.KingbaseClient"/>
    <add name="local2"
         connectionString="Server=127.0.0.1;User Id=system;Password=system;Database=test;Port=54321"
         providerName="PWMIS.DataProvider.Data.Kingbase,PWMIS.KingbaseClient.Net6V9"/>
-->
   <add name="default"
         connectionString="Server=127.0.0.1;User Id=system;Password=system;Database=test;Port=54321"
         providerName="PWMIS.DataProvider.Data.Kingbase,PWMIS.KingbaseClient.Net8V9"/>
  </connectionStrings>

然后如下使用金仓数据访问对象:

复制代码
PWMIS.DataProvider.Data.Kingbase kingbase= AdoHelper.CreateHelper("default");
var dataset= kingbase.ExecuteDataSet("select * from [Table1] ");

第三个问题:神秘的SQL_MODE

像金仓、达梦这样的国产化数据库其诞生之初就号称兼容Oracle,SQL Server,MySQL等市场常用的数据库,声称可以无缝兼容基于这些数据库开发的应用,自然在使用金仓数据库的时候会有数据库兼容性的问题,而我们在通常使用中似乎真的没有遇到兼容性问题,直到遇见了SQL-MODE问题。

问题首先发生在应用程序运行在客户定制的某金仓V8版本出现下面类似的错误:

复制代码
 syntax error at or near ""Table1""
CREATE SEQUENCE table1_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;
CREATE TABLE "Table1"(
"ID" integer DEFAULT nextval('table1_id_seq':regclass) NOT NULL PRIMARY KEY
)

很明显,当前数据库不识别双引号,Oracle数据库对象使用双引号进行标识,MySQL数据库使用反单引号进行标识,由于当前数据库设置成MySQL兼容模式,上面创建表的语句自然报错,正确的应该是:

复制代码
CREATE SEQUENCE table1_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;
CREATE TABLE `Table1`(
`ID` integer DEFAULT nextval('table1_id_seq':regclass) NOT NULL PRIMARY KEY
)

为啥之前在测试的V9数据库和V8数据库都没有遇到这个问题,在客户定制的V8版本就出现这个问题了呢?请金仓的技术人员排查后发现,原来两个数据库的SQL_MODE设置不一样。

SQL_MODE设置在数据库运行和SQL语句执行中具有重要作用,它可以规范SQL语句的执行行为,控制SQL语法的兼容性, 优化查询性能和安全性,控制数据完整性约束,影响数据类型和格式处理。SQL_MODE的默认值为:

复制代码
SHOW SQL_MODE;

ONLY_FULL_GROUP_BY,ANSI_QUOTES

在默认情况下,金仓数据库会严格限制GROUP BY子句的使用,要求在SELECT列表中出现的非聚合列必须同时出现在GROUP BY子句中。金仓数据库的SQL_MODE默认值为ONLY_FULL_GROUP_BY。这意味着在默认情况下,金仓数据库会严格限制GROUP BY子句的使用,要求在SELECT列表中出现的非聚合列必须同时出现在GROUP BY子句中SQL_MODE设置为ANSI_QUOTES的作用是改变SQL语句中双引号(")的语义,使其符合ANSI SQL标准。

这也是为何在默认情况下程序运行没有报错的原因,然而客户的数据库中SQL_MODE设置为空,并且数据库兼容模式为MySQL,所以在SQL查询中对于表名字、字段名字必须使用反单引号。

于是,SOD的金仓数据库访问提供程序也开启了设置兼容模式:

复制代码
private string _DataBaseMode = "Oracle";
private char _dbSplitChar = '"';
/// <summary>
/// 获取或者设置数据库兼容模式,可以指定的模式有Oracle,MySQL,SQLServer,PostgreSQL,默认为Oracle
/// </summary>
public string DataBaseMode
{
    get { return _DataBaseMode; }
    set
    {
        string mode = value.ToLower();
        if (mode == "oracle" || mode == "postgresql")
        {
            _DataBaseMode = value;
            _dbSplitChar = '"';
        }
        else if (mode == "mysql")
        {
            _DataBaseMode = value;
            _dbSplitChar = '`';
        }
        else if (mode == "sqlserver")
        {
            _DataBaseMode = value;
            _dbSplitChar = '[';//']'
        }
        else
        {
            throw new Exception("Kingbase Database_mode must one is Oracle,MySQL,SQLServer.");
        }
    }
}

之后,在程序中这样使用:

复制代码
var kingbase= new  PWMIS.DataProvider.Data.Kingbase();
kingbase.ConntctionString="Server=127.0.0.1;User Id=system;Password=system;Database=test;Port=54321";
kingbase.DataBaseMode="MySQL";
var dataset= kingbase.ExecuteDataSet("select * from `Table1` ");

注意:

上面的示例方式是在SOD的金仓数据访问提供程序 PWMIS.SOD.Kingbase.Provider.Net6V9 和 PWMIS.SOD.Kingbase.Provider.Net8V9 V6.0.1版本支持,如果使用 V6.0.0版本,可以全局设置,上面的示例代码修改如下:

复制代码
var kingbase= new  PWMIS.DataProvider.Data.Kingbase();
kingbase.ConntctionString="Server=127.0.0.1;User Id=system;Password=system;Database=test;Port=54321";
//V6.0.0 版本使用静态全局设置数据库兼容模式:
PWMIS.DataProvider.Data.Kingbase.DataBaseMode="MySQL";
var dataset= kingbase.ExecuteDataSet("select * from `Table1` ");

第四个问题:数据类型兼容的问题

金仓V8以及V9各个版本的数据类型并不一定完全兼容,并且它与MySQL的类型也不完全兼容,因此建议在数据库迁移的时候,一定要使用Code First方式,由程序字段创建表结构,否则迁移过程将有无尽的痛苦,这里不做过多表述。

由于时间关系,先到这里。