Смекни!
smekni.com

MS SQL Server 9 Yukon. Интеграция с .NET (стр. 4 из 6)

Имя параметра Описание
Format Формат сериализации объектов этого класса. Подробности – в следующем разделе.
MaxByteSize Максимальный размер сериализованного объекта. Подробности – в следующем разделе.
IsInvariantToDuplicates Зависит ли агрегированное значение от наличия дубликатов во входных данных (по умолчанию – да). Например, для функции MIN() совершенно неважно, сколько раз повторяются входные значения, а для функции SUM() – важно. Оптимизатор запросов SQL Server может использовать эту информацию для минимизации количества вызовов метода Accumulate.
IsInvariantToNulls Влияет ли наличие NULL-значений во входных данных на агрегированное значение. Для большинства встроенных агрегирующих функций (кроме COUNT()) это так.
IsNullIfEmpty Означает, что агрегирующая функция возвращает NULL для пустых входных наборов. Например, функция MIN при выполнении на пустом наборе возвращает как раз NULL , а функция COUNT() – 0.
IsInvariantToOrder Данный параметр пока не документирован; судя по названию, он должен определять, влияет ли на результат порядок подачи значений в метод Accumulate(). См. примечание после таблицы

Таблица 5.

ПРЕДУПРЕЖДЕНИЕ Все встроенные агрегирующие функции (а также наш пример) являются коммутативными, что позволяет серверу выбирать порядок сканирования входных данных по своему усмотрению. Однако, например, результат функций типа First() или Last(), (которые должны возвращать соответственно первое или последнее значение в наборе), очевидным образом зависит от порядка входных значений. Тем не менее, пока непонятно, как можно использовать подобные функции – дело в том, что синтаксис SQL не позволяет определять порядок агрегирования записей. Оператор ORDER BY применим только к выходному набору записей, и использовать в нем можно только те поля, по которым выполняется группировка. В обычных вложенных запросах (по результатам которых можно строить запросы с группировкой) применение ORDER BY запрещено. Скорее всего (это только мое предположение!) разработчики MS SQL Server Yukon предполагают использовать свойство SqlUserDefinedAggregateAttribute.IsInvariantToOrder для тех случаев, когда программист каким-либо способом все же может гарантировать определенное упорядочивание входных данных – это свойство должно убедить сервер воздержаться от переупорядочивания записей перед агрегированием. Пока что мне не удалось обнаружить какого-либо влияния этого свойства на поведение сервера.

Для того, чтобы наш объект мог принимать значение NULL, необходимо реализовать интерфейс INullable. Этот интерфейс определяет единственное read-only свойство bool IsNull. Все классы из System.Data.SqlTypes реализуют этот интерфейс. В нашем примере объект принимает значение NULL при инициализации, и перестает быть Null сразу, как только ему будет передано не-NULL значение в метод Accumulate или Merge.

Пользовательские типы данных

Систему типов SQL Server можно расширить с помощью пользовательских типов данных (User-defined Types, UDT). Пользовательские типы реализуются как управляемый класс на любом из CLR-языков и регистрируются в SQL Server. Такой тип можно использовать для определения типа колонки в таблице, или как переменную (параметр процедуры) в выражении Т-SQL. При этом методы объектов можно вызывать прямо из T-SQL.

Создание пользовательского типа данных

В T-SQL пользовательский тип данных регистрируется при помощи оператора CREATE TYPE:

CREATE TYPE [ type_schema_name. ] type_name { [ FROM base_type [ ( precision [ , scale ] ) | ( 'urn:schema-namespace' ) ] [ NULL | NOT NULL ] ] | [ EXTERNAL NAME [ assembly_schema_name. ] assembly_name [ :class_name ] ] }

В операторе указывается имя класса из предварительно загруженной в базу сборки.

Альтернативой прямому использованию T-SQL, как и в других случаях, служит автоматическое развертывание проектов MS Visual Studio .Net Whidbey. Классы, помеченные атрибутом SqlUserDefinedType (мы подробно рассмотрим его чуть позже – при обсуждении сериализации) автоматически регистрируются в качестве пользовательских типов при развертывании проектов типа SQL Server Project.

Для того, чтобы класс .NET можно было использовать в качестве пользовательского типа данных SQL Server, он должен выполнять некоторые обязанности:

Иметь конструктор без параметров. Как правило, он возвращает экземпляр, соответствующий значению NULL (об этом далее).

Поддерживать NULL-значения. Класс должен реализовывать интерфейс INullable, который описан в предыдущем разделе. Также необходима реализация в классе статического свойства Null, которое возвращает NULL-объект этого класса, т.е. должно быть MyClass.Null.IsNull == true. Все методы должны корректно обрабатывать передачу в параметрах как экземпляра Null, так и значения null.

Поддерживать конверсию в строку и обратно: в классе должен быть определен метод Parse(SqlString s) и должным образом перекрыт метод ToString().

Поддерживать сериализацию. Поскольку объекты этого класса будут храниться на диске, необходимо обеспечить преобразование в «плоский» формат (а также восстановление).

Сериализация является настолько существенной частью поведения пользовательских типов, что ей посвящен отдельный подраздел этой статьи. А пока что мы попробуем создать свой несложный тип данных.

В качестве упражнения я реализовал тип «точка на плоскости». Он умеет представлять свои координаты как в декартовых, так и в полярных координатах.

using System; using System.Data.Sql; using System.Data.SqlTypes; using System.Text.RegularExpressions; using System.Runtime.InteropServices; [Serializable] [SqlUserDefinedType(Format.Native)] [StructLayout(LayoutKind.Sequential)] public class SqlPoint: INullable { #region NULLability private bool _isNull = true; public bool IsNull { get { return _isNull; } } public static SqlPoint Null { get { return new SqlPoint(); } } #endregion #region Конверсия в строку и обратно public override string ToString() { return IsNull? "null" : String.Format("X: {0}; Y: {1}", x, y); } public static SqlPoint Parse(SqlString s) { // Я не очень хорошо владею регулярными выражениями, // тем не менее, этот метод в состоянии преобразовать // результат вызова метода ToString обратно в SqlPoint. if (s.IsNull || s.Value.ToLower() == "null") return Null; SqlPoint p = new SqlPoint(); Regex t = new Regex(@"x:(?<x>&bsol;d*(&bsol;.&bsol;d+)?)&bsol;s*&bsol;W*y:(?<y>&bsol;d*(&bsol;.&bsol;d+)?)", RegexOptions.IgnoreCase); Match match = t.Match(s.Value); p.x = SqlDouble.Parse(match.Groups["x"].Value); p.y = SqlDouble.Parse(match.Groups["y"].Value); return p; } #endregion #region Наши данные private double _x=0; private double _y=0; public SqlDouble x { get { return IsNull ? SqlDouble.Null : new SqlDouble(_x); } set { if (!value.IsNull) { _x = value.Value; _isNull = false; } } } public SqlDouble y { get { return IsNull? SqlDouble.Null: new SqlDouble(_y); } set { if (!value.IsNull) { _y = value.Value; _isNull = false; } } } public SqlDouble R // А эти свойства мы будем вычислять. { get { return IsNull ? SqlDouble.Null : new SqlDouble(System.Math.Sqrt(_y*_y +_x*_x)); } set { if (value.IsNull) { _isNull = true; return; } double alpha = Alpha.IsNull? 0 : Alpha.Value; _x = System.Math.Cos(alpha) * value.Value; _y = System.Math.Sin(alpha) * value.Value; } } public SqlDouble Alpha { get { return (IsNull) ? SqlDouble.Null : new SqlDouble(System.Math.Atan2(_y, _x)); } set { if (value.IsNull) { _isNull = true; return; } double r = R.IsNull ? 0 : R.Value; _x = System.Math.Cos(value.Value) * r; _y = System.Math.Sin(value.Value) * r; } } #endregion }

После регистрации данного класса как пользовательского типа выполнение вот таких запросов:

declare @p SqlPoint set @p::x = 3 set @p::y = 4 select @p::x, @p::y, @p::R, @p::ToString() set @p::R = 10 select @p::x, @p::y, @p::R, @p::ToString() set @p::Alpha = 0 select @p::x, @p::y, @p::R, @p::ToString() set @p = convert(SqlPoint, ' x:6; y:5.00') select @p::x, @p::y, @p::R, @p::ToString()

Приводит к результатам, приведенным в таблице 6:

@p::x @p::y @p::R @p::ToString()
3.0 4.0 5.0 X: 3; Y: 4
6.0000000000000009 7.9999999999999991 10.0 X: 6; Y: 8
10.0 0.0 10.0 X: 10; Y: 0
6.0 5.0 7.810249675906654 X: 6; Y: 5

Таблица 6

В приведенном выше примере продемонстрирована работа с членами объектов пользовательских типов. Синтаксис достаточно прост – оператором выбора члена объекта выступает двойное двоеточие.

Свойства

Свойства ведут себя очень похоже на переменные с такими «странными» именами. В соответствии с наличием в декларации свойства get и set его можно использовать в присваиваниях как справа, так и слева. Все свойства нашего класса поддерживают и чтение, и запись.

ПРЕДУПРЕЖДЕНИЕ Изменять состояние объекта в геттере свойства крайне не рекомендуется! Дело в том, что SQL Server предполагает, что чтение свойства не приведет к изменению состояния. Если вы прочитаете значение такого свойства у переменной, то новое состояние объекта доживет ровно до окончания выполнения геттера, а затем он будет возвращен в исходное состояние.

Методы

Все публичные методы класса будут доступны в T-SQL. Реализация методов предоставляет значительно большую гибкость, чем реализация свойств, но также накладывает на разработчика некоторую ответственность.

Константные методы

По умолчанию считается, что все методы являются константными, т.е. не изменяют состояния объекта. Это позволяет использовать их в операторах select – идеология SQL запрещает какие-либо изменения данных в читающих запросах. Иначе бы невозможно было обеспечить необходимые свойства изоляции транзакций. При этом так же, как и для геттеров свойств, сервер принудительно обеспечивает константность – изменения, произведенные в «обычном» методе будут отменены сразу после выполнения метода, даже для переменных T-SQL.