项目环境VS2008Castle ActiveRecord1.0.3基于NHibernate1.2.0 SQLServer2005。我们这个项目要求既可以支持SQL Server2005数据库也可以支持Oracle10g数据库所以现在需要把SQLServer2005中的所有表和存储过程迁移到Oracle10g里。这个项目的每个表的主键都是Guid类型在Oracle里面是应该使用char(38)还是raw(16)来保存Guid类型数据呢事实上无论使用char(38)还是raw(16)Nhibernate都会抛出无法进行类型转换的异常。究其原因要从OracleParameter的DbType和OracleType的对应关系说起。OracleParameter的DbType和OracleType的对应关系执行System.Data.OracleClient.OracleParameter parm new System.Data.OracleClient.OracleParameter();parm.DbType DbType.Guid;会发现当执行了“parm.DbType DbType.Guid; ”之后“parm.OracleType”的值会自动变为“Raw”如果执行的是“parm.DbType DbType.AnsiStringFixedLength”“parm.OracleType”的值就会自动变为“Char”。也就是说OracleParameter 对象会自动维护DbType和OracleType之间的对应关系。那么SqlParameter的对应关系是怎样的呢SqlParameter的DbType和SqlDbType的对应关系执行System.Data.SqlClient.SqlParameter parm new System.Data.SqlClient.SqlParameter();parm.DbType DbType.Guid;会发现当执行了“parm.DbType DbType.Guid; ”之后“parm.SqlDbType”的值会自动变为“UniqueIdentifier”如果执行的是“parm.DbType DbType.AnsiStringFixedLength”“parm.SqlDbType”的值也会自动变为“Char”。抛出异常的原因也就是说同样是DbType.Guid在SqlParameter和OracleParameter里面对应的SqlDbType/OracleType的值是不同的OracleType里面压根没有UniqueIdentifier类型。而NHibernate\Type\GuidType.cs里的Set()函数直接把parm.Value赋值为一个Guid对象public override void Set(IDbCommand cmd, object value, int index){IDataParameter parm cmd.Parameters[index] as IDataParameter;parm.Value value;}在OracleClient组件里定义了“LONGRAW”和“RAW”类型对应的数据类型都是“byte[]”。当NHibernate调用OracleCommand.Execute()时OracleCommand.Execute()又会调用OracleParameter.CoerceValue()OracleParameter.CoerceValue会调用System.Convert.ChangeType(aGuidObject, typeof(Byte[]), null)而Guid又没有实现IConvertible接口这时就会抛出“InvalidCastException: 对象必须实现 IConvertible。”的异常了。解决方案1在Oracle数据库中使用raw(16)类型保存Guid修改NHibernate源代码修改NHibernate\Type\GuidType.cs里的Set()和Get()函数public override void Set(IDbCommand cmd, object value, int index){IDataParameter parm cmd.Parameters[index] as IDataParameter;bool oracle (cmd.GetType().FullName System.Data.OracleClient.OracleCommand);parm.Value oracle ? ((Guid)value).g.ToByteArray() : value;}public override object Get(IDataReader rs, string name){System.Type type value.GetType();if (type typeof(string)) return new Guid((string)value);if (type typeof(Guid)) return value;if (type typeof(byte[])) return new Guid((byte[])value);return null;}在Oracle里用raw(16)存储Guid数据有一个缺点在.net里Guid.ToByteArray()并不是简单地把Guid字符串里的“-”去掉而是会把前几位进行一系列移位运算例如Guid id new Guid(dfd94f82-b680-44a5-be14-4b4a4350bf43);byte[] b id.ToByteArray();b的值将会是“824FD9DF80B6A544BE144B4A4350BF43”保存到数据库里的也是这个数据这样会对开发时的调试、排错工作造成很多困扰。再加上我们项目的.net源代码和SQLServer存储过程里面还有一些硬编码的Guid字符串常量如果raw字段与Guid的字符串看上去不一样会是很麻烦的事儿。解决方案2在Oracle里用char(38)保持Guid数据修改NHibernate源代码只需修改NHibernate\Type\GuidType.cs里的Set()函数Get()函数不要修改public override void Set(IDbCommand cmd, object value, int index){IDataParameter parm cmd.Parameters[index] as IDataParameter;bool oracle (cmd.GetType().FullName System.Data.OracleClient.OracleCommand);parm.Value oracle ? ((Guid)value).ToString().ToUpper() : value;if (oracle){parm.DbType DbType.AnsiStringFixedLength;}}public override object Get(IDataReader rs, string name){return new Guid(Convert.ToString(rs[name]));}缺点使用char(38)要比raw(16)多占用存储空间。更严重的是由于Oracle的字符串比较是区分大小写的一旦不小心把字段值与一个小写的Guid字符串比较就会匹配不了了更为不爽的是Guid.ToString()出来的字符串就是小写的。即使这样我还是比较喜欢这个方案。解决方案3在Oracle里使用char(38)不修改NHibernate源代码使用自定义类型如果不想修改NHibernate源代码可以用自定义类型。首先定义一个自定义类型“DawnGuid”放到名为“GuidTest.CustomType”的类库中DawnGuid注意上面的代码并没有包括模仿Guid的全部构造函数和必要的对“”、“Equals”、“ToString()”等函数的重载如果你要使用这个方案一定要实现它们。然后把实体和Client代码中的Guid全部换成DawnGuid实体