Страницы

пятница, 25 декабря 2015 г.

Реализация объектно-ориентированных типов данных

В Transact-SQL можно реализовать разнообразные пользовательские типы данных, основанные на clr-сборках. У этих типов могут быть свои полноценные методы и свойства (в том числе статические). Рассмотрим пример реализации списка. Потребуется реализовать структуру, реализующую интерфейс INullable. Также требуется обеспечить собственную сериализацию, поскольку работать придется со ссылочным типом, списком. Для этого потребуется реализовать интерфейс IBinarySerializer, с методами Read и Write. Обязательно также реализовать методы ToString, Parse, Null, свойство IsNull:

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)

select @lst.GetCount














Можно наполнить второй список и проверить работу метода Equals:

set @lst1 = dbo.List::ParseDelim('a,b,c', ',')
set @lst1 = @lst1.AddMember('d')
select @lst.Equal(@lst1)