\t\t在MSSQL中定義和使用C#自定義型別 SQL Server08表型別引數傳遞
在學習SQL Server 2008的過程中,突然發現SQL Server支援自定義表型別,我們可以輕鬆的將一個SQL Server 2008表型別作為引數傳遞給儲存過程。C#下實現了SQL Server 2008表型別引數傳遞
本示例中用到的型別在資料庫中的位置:
建立一個自定義表型別
CREATE TYPE [dbo].[UserDetailsType] AS TABLE
(
[ID] [varchar](50) NULL,
[Name] [varchar](50) NULL,
[Sex] [varchar](50) NULL,
[Age] [decimal](18, 0) NULL
)
建立一個名為User的表,結構如下:
建立一個儲存過程
CREATE PROCEDURE [dbo].[InsertUserInfo]
@UserInfo [UserDetailsType] readonly--指示不能在過程的主體中更新或修改引數。如果引數型別為使用者定義的表型別,則必須指定 READONLY
AS
BEGIN
insert into [User]
([ID], [Name], [Sex], [Age])
select [ID], [Name], [Sex], [Age]
from @UserInfo;
END
//啟動Visual Studio 2008,建立一個預設的窗體應用程式後,我們需要先在記憶體中建立一個數據庫表DataTable的例項,如下:
private static DataTable PrepareDatatable()
{
DataTable dt = new DataTable("dt");
DataColumn[] dtc = new DataColumn[4];
dtc[0] = new DataColumn("ID", System.Type.GetType("System.String"));
dtc[1] = new DataColumn("Name", System.Type.GetType("System.String"));
dtc[2] = new DataColumn("Sex", System.Type.GetType("System.String"));
dtc[3] = new DataColumn("Age", System.Type.GetType("System.Decimal"));
dt.Columns.AddRange(dtc);
return dt;
}
//然後,通過SqlCommand執行剛才我們建立的Test資料庫儲存過程InsertUserInfo,並傳遞我們在記憶體中建立的DataTable的例項,如下:
private static void SaveUserInfoDetails()
{
DataTable dt = PrepareDatatable();
for (int i = 0; i < = 5; i++)
{
DataRow dr = dt.NewRow();
dr[0] = i.ToString();
dr[1] = "Name" + i.ToString();
dr[2] = "男";
dr[3] = (i*10).ToString();
dt.Rows.Add(dr);
}
using (SqlConnection conn = new SqlConnection("server=Rithia;database=Test;integrated security=SSPI"))
{
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.InsertUserInfo";
SqlParameter param = cmd.Parameters.AddWithValue("@UserInfo", dt);
conn.Open();
cmd.ExecuteNonQuery();
}
}
通過上面的示例,我們可以在程式客戶端先建立好要傳遞的表型別資料,然後傳遞給儲存過程,而儲存過程則將SQL Server 2008表型別引數中的記錄一次性的新增到了資料庫實體表中,這種操作在需要傳遞給儲存過程陣列形式的引數時非常非常方便。
--------------原始做法:---------------------------------------------
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
namespace MyDBType
{
[Serializable]//標記為是可序列化的
[SqlUserDefinedType(Format.UserDefined, Name = "Person", MaxByteSize = 100)]
/*
SqlUserDefinedType特性支援以下屬性:
Format——用語指定如何在SQL Server資料庫中序列化使用者自定義型別。其取值為Native和UserDefined;
IsByteOrdered——用於使該自定義使用者型別按其本身的位元組表示方法排序;
IsFixedLength——用於指定這個型別的所有例項是否都具有相同的長度;
MaxByteSize——用於指定該使用者自定義型別的最大位元組數;
Name——用於為使用者自定義型別指定名稱;
ValidationMethodName——用於指定校驗使用者自定義型別是否有效的方法名稱。
Format屬性最為重要,他自動使用者自定義型別如何進行序列化,選項設定為Native時是讓SQL Server資料庫自動處理所有序列化問題而不需要使用者做任何額外的操作。
但原生序列化方式只能應用到簡單類上。如果類公開了非值型別的屬性(如String),那麼該類將不能使用原生序列化。
*/
public class Person : IBinarySerialize, INullable
{
public string Name;
public int Age;
public char Sex;
/// <summary>
/// 在SQL查詢中對該屬性進行比較操作所必須指定的特性
/// </summary>
[SqlFacet(Precision = 38, Scale = 2)]
public Decimal Account;
#region IBinarySerialize 成員
/// <summary>
/// 用於從BinaryReader物件中讀取資料到類中的屬性
/// </summary>
/// <param name="r"></param>
public void Read(System.IO.BinaryReader r)
{
string s = r.ReadString();
string[] values = s.Split('|');
Name = values[0];
if (values.Length > 1) Int32.TryParse(values[1], out Age);
if (values.Length > 2) Char.TryParse(values[2], out Sex);
if (values.Length > 3) Decimal.TryParse(values[3], out Account);
}
/// <summary>
/// 把類中的屬性寫入到BinaryReader物件
/// </summary>
/// <param name="w"></param>
public void Write(System.IO.BinaryWriter w)
{
w.Write(string.Format("{0}|{1}|{2}|{3}", Name, Age.ToString(), Sex, Account));
}
#endregion
#region INullable 成員
/// <summary>
/// 用於在sql中判斷該型別的變數是否為null
/// </summary>
public bool IsNull
{
get { return string.IsNullOrEmpty(Name); }
}
#endregion
/// <summary>
/// 實現靜態的返回型別為當前類型別的Null只讀屬性,返回一個在sql中認為為null的例項
/// </summary>
public static Person Null
{
get
{
return new Person { Name = string.Empty };
}
}
/// <summary>
/// 型別轉換函式
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static Person Parse(SqlString str)
{
string[] values = str.Value.Split('|');
var p = new Person
{
Name = values[0],
};
if (values.Length > 1) Int32.TryParse(values[1], out p.Age);
if (values.Length > 2) Char.TryParse(values[2], out p.Sex);
if (values.Length > 3) Decimal.TryParse(values[3], out p.Account);
return p;
}
public override string ToString()
{
return this.IsNull ? "NULL" : string.Format("{0}|{1}|{2}|{3}", Name, Age.ToString(), Sex,Account);
}
}
}
---------SQL指令碼
select * from sys.assemblies
--引用該類所在的程式集
CREATE assembly MyType
from 'C:\MyDBType.dll'
go
--建立具體的型別 create type 型別名external name sql中程式集名.[C#類完全限定名]
CREATE TYPE person
external name MyType.[MyDBType.Person]
go
--建立以自定義型別為引數的儲存過程
create proc MyDBTypeTest
@p person
as
print @p.Name
go
--禁止在.Net Framewrok中執行使用者程式碼.啟用"clr enabled"配置選項
--在Sql Server中執行這段程式碼可以開啟CLR
exec sp_configure'show advanced options', '1';
go
reconfigure;
go
exec sp_configure'clr enabled', '1'
go
reconfigure;
exec sp_configure'show advanced options', '1';
go
--定義變數
declare @p person
--賦值Parse(SqlString str) 函式派上用場了
set @p = convert(person ,N'David.Yan|30|n|100000.99')
PRINT @p.Account
--執行儲存過程
exec MyDBTypeTest@p
--弄個應該為null的值
set @p = convert(person, '|2|y')
--判斷是不是真為null;
if @p is null
print 'bool IsNull發揮作用了,static Person Null也發揮作用了.真為null'
/*
清理現場
drop proc MyDBTypeTest
drop type person
drop assembly MyType
*/