找回密码
 立即注册
首页 业界区 业界 在Oracle+NHibernate环境下使用Guid字段

在Oracle+NHibernate环境下使用Guid字段

公西颖初 2025-5-29 20:02:27
      项目环境:VS2008+Castle 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”的类库中:
1.gif
2.gif
DawnGuid
3.gif
using System;
4.gif
using System.Collections.Generic;
5.gif
using System.Linq;
6.gif
using System.Text;
7.gif
using NHibernate;
8.gif
using System.Data;
9.gif
using NHibernate.Type;
10.gif
using NHibernate.SqlTypes;
11.gif

12.gif
namespace GuidTest.CustomType
13.gif
14.gif
15.png
{
16.gif
    public class DawnGuid : ValueTypeType, IDiscriminatorType
17.gif
18.gif
    
19.png
{
20.gif
        Guid _guidValue = Guid.Empty;
21.gif

22.gif
        public Guid GuidValue
23.gif
24.gif
        
25.png
{
26.gif
27.gif
            get 
28.png
{ return _guidValue; }
29.gif
        }
30.gif

31.gif
        public DawnGuid()
32.gif
            : base(SqlTypeFactory.Guid)
33.gif
34.gif
        
35.png
{
36.gif
        }
37.gif

38.gif
        public DawnGuid(string arg) : base(SqlTypeFactory.Guid)
39.gif
40.gif
        
41.png
{
42.gif
            _guidValue = new Guid(arg);
43.gif
        }
44.gif

45.gif
        public override string ObjectToSQLString(object val)
46.gif
47.gif
        
48.png
{
49.gif
            return "'" + val.ToString().ToUpper() + "'"; 
50.gif
        }
51.gif

52.gif
        public override void Set(IDbCommand cmd, object value, int index)
53.gif
54.gif
        
55.png
{
56.gif
            IDataParameter parm = cmd.Parameters[index] as IDataParameter;
57.gif

58.gif
            bool oracle = (cmd.GetType().FullName == "System.Data.OracleClient.OracleCommand");
59.gif
            parm.Value = oracle ? ((DawnGuid)value).ToString().ToUpper() : value;
60.gif
            if (oracle)
61.gif
62.gif
            
63.png
{
64.gif
                parm.DbType = DbType.AnsiStringFixedLength;
65.gif
            }
66.gif
        }
67.gif

68.gif
        public override object Get(IDataReader rs, int index)
69.gif
70.gif
        
71.png
{
72.gif
            return new DawnGuid(Convert.ToString(rs[index]));
73.gif
        }
74.gif

75.gif
        public override object Get(IDataReader rs, string name)
76.gif
77.gif
        
78.png
{
79.gif
            return new DawnGuid(Convert.ToString(rs[name]));
80.gif
        }
81.gif

82.gif
        public override object FromStringValue(string xml)
83.gif
84.gif
        
85.png
{
86.gif
            return new DawnGuid(xml);
87.gif
        }
88.gif

89.gif
        public override string Name
90.gif
91.gif
        
92.png
{
93.gif
94.gif
            get 
95.png
{ return "DawnGuid"; }
96.gif
        }
97.gif

98.gif
        public override Type ReturnedClass
99.gif
100.gif
        
101.png
{
102.gif
103.gif
            get 
104.png
{ return typeof(DawnGuid); }
105.gif
        }
106.gif

107.gif
108.gif
        IIdentifierType 成员#region IIdentifierType 成员
109.gif

110.gif
        public object StringToObject(string xml)
111.gif
112.gif
        
113.png
{
114.gif
            return new DawnGuid(xml);
115.gif
        }
116.gif

117.gif
        #endregion
118.gif

119.gif
        public override string ToString()
120.gif
121.gif
        
122.png
{
123.gif
            return _guidValue.ToString().ToUpper();
124.gif
        }
125.gif
    }
126.gif
}注意 上面的代码并没有包括模仿Guid的全部构造函数,和必要的对“==”、“Equals”、“ToString()”等函数的重载,如果你要使用这个方案,一定要实现它们。
然后,把实体和Client代码中的Guid全部换成DawnGuid:
127.gif
128.gif
实体
129.gif
using System;
130.gif
using System.Collections.Generic;
131.gif
using System.Text;
132.gif
using Castle.ActiveRecord;
133.gif
using GuidTest.CustomType;
134.gif

135.gif
namespace Test.DataEntity
136.gif
137.gif
138.png
{
139.gif
140.gif
    /**//// 
141.gif
    /// Use Custom Type Demo
142.gif
    /// 
143.gif
    [ActiveRecord("GUIDTEST2")]
144.gif
    public class GuidTest2Entity : ActiveRecordBase
145.gif
146.gif
    
147.png
{
148.gif
        private DawnGuid _stuId;
149.gif
        private DawnGuid _teacherId;
150.gif
        private System.String _stuName;
151.gif

152.gif
        [PrimaryKey(PrimaryKeyType.Assigned, ColumnType = "GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
153.gif
        public DawnGuid StuId
154.gif
155.gif
        
156.png
{
157.gif
158.gif
            get 
159.png
{ return this._stuId; }
160.gif
161.gif
            set 
162.png
{ this._stuId = value; }
163.gif
        }
164.gif

165.gif
        [Property(ColumnType = "GuidTest.CustomType.DawnGuid,GuidTest.CustomType")]
166.gif
        public DawnGuid TeacherId
167.gif
168.gif
        
169.png
{
170.gif
171.gif
            get 
172.png
{ return this._teacherId; }
173.gif
174.gif
            set 
175.png
{ this._teacherId = value; }
176.gif
        }
177.gif

178.gif
        [Property]
179.gif
        public System.String StuName
180.gif
181.gif
        
182.png
{
183.gif
184.gif
            get 
185.png
{ return this._stuName; }
186.gif
187.gif
            set 
188.png
{ this._stuName = value; }
189.gif
        }
190.gif
    }
191.gif
}
如果想用Oralce中想用raw类型,自定义类型的写法就与方案1差不多,不再赘述。

192.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册