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包的原因。简单总结一下:
- PDF.NET.SOD.Kingbase.Provider --SOD框架适用于.NET 4.x 版本的金仓数据访问提供程序;
- PWMIS.SOD.Kingbase.Provider --SOD框架适用于.NET 6以上,采用Kdbndp 8.0版本驱动程序,可以访问金仓V8以及V9版本的数据库;
- PWMIS.SOD.Kingbase.Provider.Net6V9 --SOD框架使用.NET 6版本的,采用Kdbndp_V9 版本驱动程序,专为金仓V9版本和某些定制的V8版本数据库访问而定制的驱动程序,当然也可以访问V8版数据库。
- 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方式,由程序字段创建表结构,否则迁移过程将有无尽的痛苦,这里不做过多表述。
由于时间关系,先到这里。