В Transact-SQL можно реализовать разнообразные пользовательские типы данных, основанные на clr-сборках. У этих типов могут быть свои полноценные методы и свойства (в том числе статические). Рассмотрим пример реализации списка. Потребуется реализовать структуру, реализующую интерфейс INullable. Также требуется обеспечить собственную сериализацию, поскольку работать придется со ссылочным типом, списком. Для этого потребуется реализовать интерфейс IBinarySerializer, с методами Read и Write. Обязательно также реализовать методы ToString, Parse, Null, свойство IsNull:
Добавим также статический метод для получения списка из строки с произвольным разделителем, и методы для добавления и удаления элементов в список, очистки и сортировки списка:
Для удобства добавим метод для извлечения определенного члена списка по номеру, метод для проверки равенства двух экземпляров и свойство для определения числа элементов:
Остается реализовать методы Read, Write;
Теперь можно перейти к развертыванию сборки и тестированию типа:
Проверим работу методов, создадим экземпляры структуры с помощью метода ParseDelim, вставим в список несколько строковых элементов, отсортируем их и извлечем:
select @lst.GetCount
Можно наполнить второй список и проверить работу метода Equals:
using
System;
using
System.Data;
using
System.Data.SqlClient;
using
System.Data.SqlTypes;
using
Microsoft.SqlServer.Server;
using
System.Collections.Generic;
using
System.Linq;
using
System.IO;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined,
MaxByteSize=-1)]
public
struct SqlList
: INullable, IBinarySerialize
{
private bool m_Null;
private List<string>
m_lst;
public override string
ToString()
{
if (this.IsNull)
{
return
"null";
}
return string.Join(",",
m_lst);
}
public bool IsNull
{
get
{
return
m_Null;
}
}
public static SqlList Null
{
get
{
SqlList
lst = new SqlList();
lst.m_Null = true;
return
lst;
}
}
public static SqlList
Parse(SqlString lst)
{
if
(lst.IsNull)
return
Null;
SqlList
_lst = new SqlList();
char[]
_delim = {','};
_lst.m_lst =
lst.ToString().Split(_delim).ToList();
return _lst;
}Добавим также статический метод для получения списка из строки с произвольным разделителем, и методы для добавления и удаления элементов в список, очистки и сортировки списка:
public static SqlList
ParseDelim(SqlString lst, SqlString delim)
{
if
(lst.IsNull)
return
Null;
SqlList
_lst = new SqlList();
char[]
_delim = delim.ToString().ToCharArray();
_lst.m_lst =
lst.ToString().Split(_delim).ToList();
return _lst;
}
public SqlList AddMember(SqlString
vcNewMember)
{
SqlList
_lst = new SqlList();
_lst.m_lst = this.m_lst;
_lst.m_lst.Add(vcNewMember.ToString());
return
_lst;
}
public SqlList RemoveMember(SqlString
vcMember)
{
SqlList
_lst = new SqlList();
_lst.m_lst = this.m_lst;
_lst.m_lst.Remove(vcMember.ToString());
return
_lst;
}
public SqlList Clear()
{
SqlList
_lst = new SqlList();
_lst.m_lst = this.m_lst;
_lst.m_lst.Clear();
return
_lst;
}
public SqlList Sort()
{
SqlList
_lst = new SqlList();
_lst.m_lst = this.m_lst;
_lst.m_lst.Sort();
return
_lst;
}Для удобства добавим метод для извлечения определенного члена списка по номеру, метод для проверки равенства двух экземпляров и свойство для определения числа элементов:
public SqlString GetItem(SqlInt32
iItem)
{
return
(SqlString)this.m_lst.ElementAt((int)iItem);
}
public SqlBoolean Equal(SqlList
lst)
{
if (this.m_lst == null ||
lst.m_lst == null)
{
return
false;
}
if(this.m_lst.Count != lst.m_lst.Count)
{
return
false;
}
for (int i = 0; i < this.m_lst.Count
- 1; i++)
{
if
(this.m_lst.ElementAt(i) !=
lst.m_lst.ElementAt(i))
{
return
false;
}
}
return true;
}
public SqlInt32 GetCount
{
get
{
return
this.m_lst.Count;
}
}Остается реализовать методы Read, Write;
public void Read(BinaryReader
r)
{
if (r
== null) throw new ArgumentNullException("r");
var
count = r.ReadInt32();
m_lst = new
List<string>(count);
for (int i = 0; i < count; i++)
{
m_lst.Add(r.ReadString());
}
}
public void Write(BinaryWriter
w)
{
if (w
== null) throw new ArgumentNullException("w");
w.Write(m_lst.Count);
foreach
(string b in
m_lst)
{
w.Write(b);
}
}
}Теперь можно перейти к развертыванию сборки и тестированию типа:
if
not exists
(
select *
from sys.assemblies
where name = N'Lists'
)
begin
create assembly Lists
from 'C:\ListLib.dll'
end
else
if
(
select clr.binFile
from openrowset ( bulk 'C:\ListLib.dll', single_blob ) clr ( binFile )
)
<>
(
select asmfile.content
from
sys.assemblies asm
inner
join
sys.assembly_files asmfile on asm.assembly_id = asmfile.assembly_id
where asm.name = N'Lists'
)
begin
alter assembly Lists
from 'C:\ListLib.dll'
end
go
if
type_id ( N'dbo.List' ) is null
begin
create type List
external name Lists.SqlList
end
goПроверим работу методов, создадим экземпляры структуры с помощью метода ParseDelim, вставим в список несколько строковых элементов, отсортируем их и извлечем:
declare
@lst dbo.List, @lst1 dbo.List, @lst2 dbo.List
set
@lst = dbo.List::ParseDelim(N'd,c,b', ',')
set
@lst = @lst.AddMember(N'a')
select
@lst.GetItem(0), @lst.GetItem(1), @lst.GetItem(2), @lst.GetItem(3)
set
@lst = @lst.Sort()
select
@lst.GetItem(0), @lst.GetItem(1), @lst.GetItem(2), @lst.GetItem(3)
Можно наполнить второй список и проверить работу метода Equals:
set
@lst1 = dbo.List::ParseDelim('a,b,c', ',')
set
@lst1 = @lst1.AddMember('d')
select @lst.Equal(@lst1)