在Oracle数据库中,NHibernate环境下如何处理Guid字段类型?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1370个文字,预计阅读时间需要6分钟。
在Oracle和NHibernate环境下,使用Guid字段和Castle ActiveRecord1.0.3(基于Hibernate1.2.0)配合SQL Server 2005数据库。项目需求是支持SQL Server 2005和Oracle 10g数据库。
这个项目的每个表的主键都是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()函数:
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((Guid)value).g.ToByteArray():value;
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
System.Typetype=value.GetType();
if(type==typeof(string))returnnewGuid((string)value);
if(type==typeof(Guid))returnvalue;
if(type==typeof(byte[]))returnnewGuid((byte[])value);
returnnull;
}
在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()函数不要修改:
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((Guid)value).ToString().ToUpper():value;
if(oracle)
{
parm.DbType=DbType.AnsiStringFixedLength;
}
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
returnnewGuid(Convert.ToString(rs[name]));
}
缺点:使用char(38)要比raw(16)多占用存储空间。更严重的是,由于Oracle的字符串比较是区分大小写的,一旦不小心把字段值与一个小写的Guid字符串比较,就会匹配不了了(更为不爽的是,Guid.ToString()出来的字符串就是小写的!)。即使这样,我还是比较喜欢这个方案。
解决方案3:在Oracle里使用char(38),不修改NHibernate源代码,使用自定义类型
如果不想修改NHibernate源代码,可以用自定义类型。首先,定义一个自定义类型“DawnGuid”,放到名为“GuidTest.CustomType”的类库中:
DawnGuid
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingNHibernate;
usingSystem.Data;
usingNHibernate.Type;
usingNHibernate.SqlTypes;
namespaceGuidTest.CustomType
{
publicclassDawnGuid:ValueTypeType,IDiscriminatorType
{
Guid_guidValue=Guid.Empty;
publicGuidGuidValue
{
get{return_guidValue;}
}
publicDawnGuid()
:base(SqlTypeFactory.Guid)
{
}
publicDawnGuid(stringarg):base(SqlTypeFactory.Guid)
{
_guidValue=newGuid(arg);
}
publicoverridestringObjectToSQLString(objectval)
{
return"'"+val.ToString().ToUpper()+"'";
}
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((DawnGuid)value).ToString().ToUpper():value;
if(oracle)
{
parm.DbType=DbType.AnsiStringFixedLength;
}
}
publicoverrideobjectGet(IDataReaderrs,intindex)
{
returnnewDawnGuid(Convert.ToString(rs[index]));
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
returnnewDawnGuid(Convert.ToString(rs[name]));
}
publicoverrideobjectFromStringValue(stringxml)
{
returnnewDawnGuid(xml);
}
publicoverridestringName
{
get{return"DawnGuid";}
}
publicoverrideTypeReturnedClass
{
get{returntypeof(DawnGuid);}
}
IIdentifierType成员#regionIIdentifierType成员
publicobjectStringToObject(stringxml)
{
returnnewDawnGuid(xml);
}
#endregion
publicoverridestringToString()
{
return_guidValue.ToString().ToUpper();
}
}
}注意 上面的代码并没有包括模仿Guid的全部构造函数,和必要的对“==”、“Equals”、“ToString()”等函数的重载,如果你要使用这个方案,一定要实现它们。
然后,把实体和Client代码中的Guid全部换成DawnGuid:
实体
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingCastle.ActiveRecord;
usingGuidTest.CustomType;
namespaceTest.DataEntity
{
/**////<summary>
///UseCustomTypeDemo
///</summary>
[ActiveRecord("GUIDTEST2")]
publicclassGuidTest2Entity:ActiveRecordBase<GuidTest2Entity>
{
privateDawnGuid_stuId;
privateDawnGuid_teacherId;
privateSystem.String_stuName;
[PrimaryKey(PrimaryKeyType.Assigned,ColumnType="GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
publicDawnGuidStuId
{
get{returnthis._stuId;}
set{this._stuId=value;}
}
[Property(ColumnType="GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
publicDawnGuidTeacherId
{
get{returnthis._teacherId;}
set{this._teacherId=value;}
}
[Property]
publicSystem.StringStuName
{
get{returnthis._stuName;}
set{this._stuName=value;}
}
}
}
如果想用Oralce中想用raw类型,自定义类型的写法就与方案1差不多,不再赘述。
本文共计1370个文字,预计阅读时间需要6分钟。
在Oracle和NHibernate环境下,使用Guid字段和Castle ActiveRecord1.0.3(基于Hibernate1.2.0)配合SQL Server 2005数据库。项目需求是支持SQL Server 2005和Oracle 10g数据库。
这个项目的每个表的主键都是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()函数:
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((Guid)value).g.ToByteArray():value;
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
System.Typetype=value.GetType();
if(type==typeof(string))returnnewGuid((string)value);
if(type==typeof(Guid))returnvalue;
if(type==typeof(byte[]))returnnewGuid((byte[])value);
returnnull;
}
在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()函数不要修改:
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((Guid)value).ToString().ToUpper():value;
if(oracle)
{
parm.DbType=DbType.AnsiStringFixedLength;
}
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
returnnewGuid(Convert.ToString(rs[name]));
}
缺点:使用char(38)要比raw(16)多占用存储空间。更严重的是,由于Oracle的字符串比较是区分大小写的,一旦不小心把字段值与一个小写的Guid字符串比较,就会匹配不了了(更为不爽的是,Guid.ToString()出来的字符串就是小写的!)。即使这样,我还是比较喜欢这个方案。
解决方案3:在Oracle里使用char(38),不修改NHibernate源代码,使用自定义类型
如果不想修改NHibernate源代码,可以用自定义类型。首先,定义一个自定义类型“DawnGuid”,放到名为“GuidTest.CustomType”的类库中:
DawnGuid
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingNHibernate;
usingSystem.Data;
usingNHibernate.Type;
usingNHibernate.SqlTypes;
namespaceGuidTest.CustomType
{
publicclassDawnGuid:ValueTypeType,IDiscriminatorType
{
Guid_guidValue=Guid.Empty;
publicGuidGuidValue
{
get{return_guidValue;}
}
publicDawnGuid()
:base(SqlTypeFactory.Guid)
{
}
publicDawnGuid(stringarg):base(SqlTypeFactory.Guid)
{
_guidValue=newGuid(arg);
}
publicoverridestringObjectToSQLString(objectval)
{
return"'"+val.ToString().ToUpper()+"'";
}
publicoverridevoidSet(IDbCommandcmd,objectvalue,intindex)
{
IDataParameterparm=cmd.Parameters[index]asIDataParameter;
booloracle=(cmd.GetType().FullName=="System.Data.OracleClient.OracleCommand");
parm.Value=oracle?((DawnGuid)value).ToString().ToUpper():value;
if(oracle)
{
parm.DbType=DbType.AnsiStringFixedLength;
}
}
publicoverrideobjectGet(IDataReaderrs,intindex)
{
returnnewDawnGuid(Convert.ToString(rs[index]));
}
publicoverrideobjectGet(IDataReaderrs,stringname)
{
returnnewDawnGuid(Convert.ToString(rs[name]));
}
publicoverrideobjectFromStringValue(stringxml)
{
returnnewDawnGuid(xml);
}
publicoverridestringName
{
get{return"DawnGuid";}
}
publicoverrideTypeReturnedClass
{
get{returntypeof(DawnGuid);}
}
IIdentifierType成员#regionIIdentifierType成员
publicobjectStringToObject(stringxml)
{
returnnewDawnGuid(xml);
}
#endregion
publicoverridestringToString()
{
return_guidValue.ToString().ToUpper();
}
}
}注意 上面的代码并没有包括模仿Guid的全部构造函数,和必要的对“==”、“Equals”、“ToString()”等函数的重载,如果你要使用这个方案,一定要实现它们。
然后,把实体和Client代码中的Guid全部换成DawnGuid:
实体
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingCastle.ActiveRecord;
usingGuidTest.CustomType;
namespaceTest.DataEntity
{
/**////<summary>
///UseCustomTypeDemo
///</summary>
[ActiveRecord("GUIDTEST2")]
publicclassGuidTest2Entity:ActiveRecordBase<GuidTest2Entity>
{
privateDawnGuid_stuId;
privateDawnGuid_teacherId;
privateSystem.String_stuName;
[PrimaryKey(PrimaryKeyType.Assigned,ColumnType="GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
publicDawnGuidStuId
{
get{returnthis._stuId;}
set{this._stuId=value;}
}
[Property(ColumnType="GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
publicDawnGuidTeacherId
{
get{returnthis._teacherId;}
set{this._teacherId=value;}
}
[Property]
publicSystem.StringStuName
{
get{returnthis._stuName;}
set{this._stuName=value;}
}
}
}
如果想用Oralce中想用raw类型,自定义类型的写法就与方案1差不多,不再赘述。

