以前提到的一个项目使用到了ASP.NET 1.1+Firebird embedded数据库,数据库使用了2.0.1,数据连接适配器使用了Firebird Net Provider 1.7 RC3。
但最近将程序加入嵌入式Web服务器之后,却出现了无法连接数据库的问题,经过排查,发现,以前使用IIS做Web服务时,路径中没有中文;而当使用中文路径存放整个执行程序时,就会无法连接数据库的问题,且只有使用嵌入式服务器时才会出现。
为了缩小问题的排查范围,我将同一项目组的一个Delphi项目配置为嵌入式数据库,并放入中文目录中,测试没有问题,于是可以基本排除嵌入式数据库版本问题。于是我到网上搜索Firebird Net Provider的相关内容,发现,确实有人提到了此问题,但不知道是不是被解决了。与此同时,我发现了Firebird Net Provider 1.7 RC4,眼前一亮,说不定,这个版本已经解决了整个问题。
一阵折腾,拿下了最新的RC4,安装、运行…心又凉了半截――外甥打灯笼,照旧。看了看发布时间,2006年,看来,还真是没人管这事。不过值得庆幸的是,这次的错误提示比较精确了,提示是那个路径找不到…等等!数据库文件路径后面咋少了仨字符?难道是因为中文的存在,被截掉了?希望再次燃烧起来,因为,刚刚下载驱动的时候,也看到了源码的下载链接。
看来这次得自己动手丰衣足食了。又一阵折腾,下载下来这个工程的源码,解压、编…咦?貌似是NANT的工程,看来只能用文本编辑器来修改代码。不过还好,平时用的东西杂,家什设备齐全,逐步跟踪,最终发现了问题所在:
在将连接字符串connectionString解析成database字符串时,使用了length属性来返回字符串的长度。将其传给数据库引擎的相关函数。但.NET中这个length默认是字长度,而嵌入式Firebird引擎是非托管代码,需要的是字节长度,由此造成了字符串被截断的错误。
经过跟踪发现,具体的BUG在FirebirdSql.Data.Embedded/FesDatabase.cs文件中,FesDatabase类的Attach函数,将database字符串以及其长度传递给嵌入式数据库的非托管dll,其关键代码如下:
FbClient.isc_attach_database(
statusVector,
(short)database.Length, //问题在这
database,
ref dbHandle,
(short)dpb.Length,
dpb.ToArray());
statusVector,
(short)database.Length, //问题在这
database,
ref dbHandle,
(short)dpb.Length,
dpb.ToArray());
代码中, (short)database.Length用来计算字符串的长度,而.NET默认环境将中英文全部视为Unicode编码,因此该属性得到的是字符串的字长,而并非字节长度。但.NET并没有直接提供获取字节长度的代码,只能手动创建函数:
//获取字符串真实长度(中文为2字节,英文为1字节)
private int getTrueLength(string str)
…{
int lenTotal = 0;
int n = str.Length;
string strWord = "";
int asc;
for(int i=0;i<n;i++)
…{
strWord = str.Substring(i,1);
asc = Convert.ToChar(strWord);
if ( asc < 0 || asc > 127 )
lenTotal = lenTotal + 2;
else
lenTotal = lenTotal + 1;
}
return lenTotal;
}
private int getTrueLength(string str)
…{
int lenTotal = 0;
int n = str.Length;
string strWord = "";
int asc;
for(int i=0;i<n;i++)
…{
strWord = str.Substring(i,1);
asc = Convert.ToChar(strWord);
if ( asc < 0 || asc > 127 )
lenTotal = lenTotal + 2;
else
lenTotal = lenTotal + 1;
}
return lenTotal;
}
并将有问题代码改为:
FbClient.isc_attach_database(
statusVector,
(short)getTrueLength(database),
database,
ref dbHandle,
(short)dpb.Length,
dpb.ToArray());
statusVector,
(short)getTrueLength(database),
database,
ref dbHandle,
(short)dpb.Length,
dpb.ToArray());
保存、编译、运行,问题解决。
注意事项:
1、 对于计算字节长度的函数,网上还有另一种解决方法,就是使用GB2312编码来转换,但由于编码的针对性,这个方法会有潜在的错误,不建议使用。
2、 如果你的驱动文件是安装版本,请在替换原有文件之前,删除系统assembly目录中的索引,以免系统缓存的存在使修改无效。
后记:已编译完成的DLL文件我已经放在:http://www.qihangsoft.net/download/Firebird_NET_Provider_1.7_fix_nose.rar,你可以免费下载使用。