之所以会去了解这个,是因为在最近的一个项目中需要用BizTalk来处理业务数据,而每一笔业务数据又对应着表中的很多条记录,发现做BizTalk的同事在实现中,每条记录都要调用一次存储过程,而且主表数据在每一次调用时都要传入,感觉有点不太好。于是想着是不是能用table类型作为存储过程的参数。研究了一下,似乎不行。但是可以用自定义的数据类型。

      定义最普通的自定义类型很简单,见MSDN示例:     



CREATE 
    TYPE SSN
 
   FROM 
     
   varchar 
   ( 
   11 
   )  
   NOT 
     
   NULL 
    ;



 

     

      详细的到帮助文档上看吧。这里主要说一下怎么把用C#定义的类引入到SQL中作为自定的数据类型。

      首先,在VS中定义一个类,这个类必须满足以下几点:

      1、添加Serializable特性以标记为是可序列化的;

      2、添加SqlUserDefinedType特性,注意设置该特性的一些属性值,如Format, Name, MaxByteSize,注意若Format设置为UserDefined的话一定要指定MaxByteSize值;

      3、实现IBinarySerialize接口,该接口中声明了Read和Write两个方法,分别是用于从BinaryReader对象中读取数据到类中的属性和把类中的属性写入到BinaryReader对象;

      4、实现INullable接口,该接口中声明了IsNull的get访问器,用于在sql中判断该类型的变量是否为null;

      5、实现静态的返回类型为当前类类型的Null只读属性,返回一个在sql中认为为null的实例;

      6、实现静态的Parse方法。该方法只有一个SqlString类型参数;

      7、重载基类的ToString方法;

      暂且只知道这一些,来看看我测试用的C#类吧:     

  



using 
   System;
 
  using 
   System.Data;
 
  using 
   System.Data.SqlClient;
 
  using 
   System.Data.SqlTypes;
 
  using 
   Microsoft.SqlServer.Server;

 
  namespace 
   LfxSqlType
{
    [Serializable]
    [SqlUserDefinedType(Format.UserDefined, Name 
  = 
  " 
  Person 
  " 
  , MaxByteSize 
  = 
  100 
  )]
     
  public 
    
  class 
   Person : IBinarySerialize, INullable
    {
         
  public 
    
  string 
   Name;
         
  public 
    
  int 
   Age;
         
  public 
    
  char 
   Sex;

         
  #region 
   IBinarySerialize 成员 
  

         
  public 
    
  void 
   Read(System.IO.BinaryReader r)
        {
             
  string 
   s  
  = 
   r.ReadString();
             
  string 
  [] values  
  = 
   s.Split( 
  ' 
  | 
  ' 
  );
            Name  
  = 
   values[ 
  0 
  ];
            Age  
  = 
   Convert.ToInt32(values[ 
  1 
  ]);
            Sex  
  = 
   Convert.ToChar(values[ 
  2 
  ]);
        }

         
  public 
    
  void 
   Write(System.IO.BinaryWriter w)
        {
            w.Write( 
  string 
  .Format( 
  " 
  {0}|{1}|{2} 
  " 
  , Name, Age.ToString(), Sex));
        }

         
  #endregion 
  

         
  #region 
   INullable 成员 
  

         
  public 
    
  bool 
   IsNull
        {
             
  get 
   {  
  return 
    
  string 
  .IsNullOrEmpty(Name); }
        }

         
  #endregion 
  

         
  public 
    
  static 
   Person Null
        {
             
  get 
  {
                Person p  
  = 
    
  new 
   Person();
                p.Name  
  = 
    
  string 
  .Empty;
                 
  return 
   p;
            }
        }

         
  public 
    
  static 
   Person Parse(SqlString str)
        {
            Person p  
  = 
    
  new 
   Person();
             
  string 
  [] values  
  = 
   str.Value.Split( 
  ' 
  | 
  ' 
  );
            p.Name  
  = 
   values[ 
  0 
  ];
            p.Age  
  = 
   Convert.ToInt32(values[ 
  1 
  ]);
            p.Sex  
  = 
   Convert.ToChar(values[ 
  2 
  ]);
             
  return 
   p;
        }

         
  public 
    
  override 
    
  string 
   ToString()
        {
             
  if 
   ( 
  this 
  .IsNull)
                 
  return 
    
  " 
  NULL 
  " 
  ;
             
  else 
  
                 
  return 
    
  string 
  .Format( 
  " 
  {0}|{1}|{2} 
  " 
  , Name, Age.ToString(), Sex);
        }
    }
} 
 
      再来看看SQL中如何根据这个类来创建自定义类型。首先,得引用该类所在的程序集:      
 
 
create 
   assembly lfxtype
 
  from 
    
  ' 
  E:\NetSample\WinForm\SqlCustomerType\LfxSqlType\bin\Debug\LfxSqlType.dll 
  ' 
 
      语法是create assembly sql中的程序集名 from 程序集路径。具体参见MSDN。下一步,创建具体的类型:   
 
 
create 
   type person
external name lfxtype. 
  [ 
  LfxSqlType.Person 
  ] 
 
      语法跟上边的也很类似了。create type 类型名 external name sql中程序集名.[C#类完全限定名]
      接下来就可以做调用测试了:
 
 
-- 
  创建以自定义类型为参数的存储过程 
  
 
  create 
    
  proc 
   test
     
  @p 
   person
 
  as 
  
     
  print 
    
  @p 
  .Name
 
  go 
  
 
  -- 
  定义变量 
  
 
  declare 
    
  @p 
   person
 
  -- 
  赋值 
  
 
  set 
    
  @p 
    
  = 
    
  convert 
  (person ,N 
  ' 
  lfx|2|y 
  ' 
  )
 
  -- 
  执行存储过程 
  
 
  exec 
   test  
  @p 
  
 
  -- 
  弄个应该为null的值 
  
 
  set 
    
  @p 
    
  = 
    
  convert 
  (person,  
  ' 
  |2|y 
  ' 
  )
 
  -- 
  判断是不是真为null 
  
 
  if 
    
  @p 
    
  is 
    
  null 
  
     
  print 
    
  ' 
  null 
  '



     是不是很简单?呵呵