绝大多数时候,我们都是希望大小写不敏感的,大小写敏感反倒会带来很多困惑,查询不出,或者系统中存在同名的用户,一个叫John另一个叫john,MSSQL可以在创建库的时候指定大小写不敏感,而PG似乎没有这样的功能,它需要借助一个额外的组件,叫CITEXT,CI的意思就是Case Insensitive。要使用CITEXT组件,你需要安装postgresql10-contrib包(假设你安装的是PG10,如果不是的话你去找对应的包),再使用以下命令创建CITEXT类型:
CREATE EXTENSION IF NOT EXISTS CITEXT WITH SCHEMA public;
注:一个database只需要执行一次这个命令即可
如果你使用的是psql客户端连上去使用PG的话,这时候已经OK了,你会发现CITEXT的字段已经是大小写不敏感了,但如果你用的是Npgsql用代码去访问PG的话,CITEXT似乎没生效,其实原因是这样的,CITEXT并不是PG的原生类型,你在用查询语句的时候,需要在参数后面加上“::CITEXT”显式地告诉PG,你的参数是CITEXT类型,例子如下:
SELECT * FROM test_table WHERE test_name=@TextName::CITEXT AND category=@Category::CITEXT
嗯,我承认是有点麻烦,但习惯就好,我现在还不知道有什么更佳方法。
使用CITEXT时候出现NotSupportedException这个异常的呈现内容大致如此:
System.NotSupportedException: The field 'application_id' has a type currently unknown to Npgsql (OID 41000). You can retrieve it as a string by marking it as unknown, please see the FAQ.
在 Npgsql.NpgsqlDataReader.GetValue(Int32 ordinal)
在 Npgsql.NpgsqlDataReader.get_Item(Int32 ordinal)
……
这个错误对我们而言,曾经像个幽灵似的,时不时出现,出现的时候重启一下服务程序就好了,不再出现,然后过几个星期或者几个月又出现,有时候一天出现多次也不是没有可能。最后是到github上面求助才最终搞懂了原因。链接:https://github.com/npgsql/npgsql/issues/1635
简单地说,PG对各种数据类型,是有一个内部的ID值的(叫oid),Npgsql在第一次连接数据库的时候,会获取到这些oid值并缓存起来,对于PG的内部类型,如INT什么的,这些oid值是固定的,但对于CITEXT似乎不是这样,因为CITEXT这个类型是我门自己用CREATE EXTENSION命令创建的(请参考本文前面内容),创建的时候确定其oid。我们在还原数据库的时候,也相当于重新创建了CITEXT类型,这样会导致CITEXT的oid发生变化,但Npgsql并不知道,所以就出现了这个异常。我们在开发过程中常常需要做还原数据库的动作,所以导致了这个问题的发生。
解决方法1,当数据库还原了之后,调用NpgsqlConnection.ReloadTypes(),刷新各类型oid,但这个很难,因为还原数据库都是手动操作,做完之后打开网页,在上面点一下通知程序吗?
解决方法2,重启一下程序。这个其实跟解决方法1差不多,只不过不需要写什么额外代码,考虑到还原数据库这个动作其实也不是太频繁,只是在开发环境中做,所以重启就重启吧,我们现在就干,规定还原数据库后自己重启下服务程序。(写个脚本干这个事情很简单)
使用事务进行大量操作时候导致程序崩溃这个问题我同样到github上求助了,链接:https://github.com/npgsql/npgsql/issues/1838
这个问题比前面的问题可能更严重,因为我很可能捕捉不到异常(就是说有时候可以捕捉到,有时候不行),程序直接崩溃了,对于一个.NET程序来说,这是很不应该的事情,即便我没单独写try-catch,程序的最外层异常处理器应该也能捕捉到相关的Exception并log对不?但偏不,没有log,也捕捉不到。所以至今我怀疑这是一个.NET的bug,可能跟Npgsql并没有关系。
问题的原因如github上所描述,是找到了,但却无法从根本上修正,这个问题其实是个简单的“事务超时”问题。
我们的程序在第一次启动的时候会初始化数据库的表,插入大量的初始化数据,由于我们公司的开发环境比较特殊,数据库延迟十分高,所以导致插入速度很慢,每条插入耗时可高达几十毫秒,(生产环境并没有这个问题)这样一万多条数据下来就导致了事务超时(事务超时默认时间是1分钟)。解决方法当然很明显了:初始化的时候,临时增加 TransactionScope的超时值,增加到10分钟,这样总归没问题了。
类似这种问题我们只能通过一些外部的workaround来预防,很难从根本上解决。
55000: 禁用已准备好的事务