Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Microsoft SQL Server Новый топик    Ответить
 Странное поведение класса-реализации CLR aggregate function  [new]
CLR Aggregate
Guest
Здравствуйте. Решил освоить тему агрегатов на CLR - есть вероятность, что они понадобятся в некотором будущем. Взял для начала и для тренировки стандартный пример, который приводится много где - конкатенация строк. Всё в порядке, всё получилось. Далее я решил немного усложнить пример - сделать функцию с двумя параметрами, второй параметр - разделитель произвольной длины - соответственно, ввел в сигнатуру метода Accumulate второй параметр SqlString delimiter. Для того, чтобы знать, сколько от полученной в результате строки мне нужно отрезать, чтобы получить строчку без разделителя в конце, ввел в класс поле, запоминающее переданный разделитель, и в методе Terminate просто попробовал сделать
output = _intermediateResult.ToString(0, intermediateResult.Length - _delimiter.Length);

- и словил NullPointerException. В результате плясок с бубном выяснилось, что к моменту выполнения Terminate поле, в которое заносится разделитель, инициализируется заново. Причем во время выполнения Accumulate это поле заполняется разделителем, это проверено. Такое впечатление, что класс реализации функции может держать только одно поле StringBuilder'а, а для всех остальных полей к моменту выполнения Terminate CLR производит переинициализацию, что для меня является очень странным поведением.
Если что - вот код класса:
+
using System;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;

[Serializable]
[SqlUserDefinedAggregate(
  Format.UserDefined,
  IsInvariantToNulls = true,
  IsInvariantToDuplicates = false,
  IsInvariantToOrder = false,
  MaxByteSize = 8000)
]
public struct AggregateTest : IBinarySerialize
{
  private StringBuilder _intermediateResult;
  private String _delimiter;

  public void Init()
  {
    _intermediateResult = new StringBuilder();
    _delimiter=null;
  }

  public void Accumulate(SqlString value, SqlString delimiter)
  {
    if (value.IsNull) return;
    if (_delimiter==null)
      _delimiter=delimiter.Value;
    _intermediateResult.Append(value.Value).Append(_delimiter);
  }

  public void Merge(AggregateTest other)
  {
    _intermediateResult.Append(other._intermediateResult);
  }

  public SqlString Terminate()
  {
    var output = string.Empty;
    if (_intermediateResult != null && _intermediateResult.Length > 0)
      output = _intermediateResult.ToString()+(_delimiter==null); // тут видно, что поле с разделителем переинициализировано
    return new SqlString(output);
  }

  public void Read(BinaryReader r)
  {
    _intermediateResult = new StringBuilder(r.ReadString());
  }

  public void Write(BinaryWriter w)
  {
    w.Write(_intermediateResult.ToString());
  }
}

Проверка:
select dbo.uf_AggregateTest(convert(varchar(2),number),'###')
  from master..spt_values where type='P' and number between 1 and 10

Результат:

1###2###3###4###5###6###7###8###9###10###True

Пока сделал так:
    output = Regex.Replace(_intermediateResult.ToString(), @"^(?<word>.*)(.+)\k'word'$", "$1");

но это решение мне не нравится - хотя бы потому, что в случае, скажем, последовательности чисел от 1 до 11 и с пустым разделителем мы получим 23456789101, а по логике должно быть 1234567891011. Да и вообще хотелось бы разобраться, почему класс ведет себя так странно.
16 фев 12, 08:32    [12100370]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
CLR Aggregate
Решил освоить тему агрегатов на CLR - есть вероятность, что они понадобятся в некотором будущем.
Типун вам на язык.

Занялись бы более полезным делом. Вот к примеру поведение оптимизатора для вас абсолютно предсказуемо и логично?
Лучше разберитесь сами, а если там есть что-то интересное (что сомневаюсь) выложите в местном блоге.
И ничего я вам не навязываю.

А ещё у вас Merge корявый, про _delimiter ни строчки, а в вашем случае у other приоритет выше.
И можно подойти с другой стороны:
Init() { _intermediateResult = new StringBuilder(); _delimiter = string.Empty; }
Accumulate() { _intermediateResult.Append(_delimiter).Append(value.Value); _delimiter = delimiter.Value ?? string.Empty; }
Merge() { _intermediateResult.Append(_delimiter).Append(other._intermediateResult); _delimiter = other._delimiter; }
Terminate() { return new SqlString(_intermediateResult.ToString()); }
17 фев 12, 01:17    [12106731]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
CLR Aggregate
Guest
Mnior
Типун вам на язык.

И вам не хворать :)
Mnior
Занялись бы более полезным делом.

Ну, хочется вот иногда развлечь себя какой-то экзотикой.
Mnior
А ещё у вас Merge корявый, про _delimiter ни строчки, а в вашем случае у other приоритет выше.

Я подумал об этом; после того, как запостил старт-постинг, написал что-то примерно такое, что вы посоветовали - увы, и с моей реализацией Merge, и с предложенной вами, в Terminate поле _delimiter всё равно оказывается переинициализированным.
Mnior
И можно подойти с другой стороны

Спасибо, так всё работает нормально. Однако вопрос о поведении класса остается открытым.
В общем, основная проблема в данном случае - практически никакое документирование касательно написания CLR для MSSQL - несколько поверхностных how-to, и всё. Где, например, можно прочитать внятное описание контракта того же Merge? Контракта класса агрегирующей UDF? Описание жизненного цикла класса? Я всего этого найти не смог.
17 фев 12, 07:02    [12107013]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
CLR Aggregate
с моей реализацией Merge, и с предложенной вами, в Terminate поле _delimiter всё равно оказывается переинициализированным.
Неверю. ©
Никаой привилегии в полях и типах нет. Это бред.

Лично я списываю это на то, что вы где-то проффтыкали в какой-то банальной мелочи. Лучше дали бы итоговый код.
CLR Aggregate
практически никакое документирование
M$ такой M$. Но и требовать доки по экзотике бесполезно.
Подождите с годик. Говнокодеров всегда тенят на изварт, поэтому массовость потребует документашку, если конечно к тому моменту это всё не загнётся.
17 фев 12, 12:52    [12109011]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Antoshka
Member

Откуда:
Сообщений: 828
В процедурах сериализации / десериализации разделитель тоже надо сохранять
17 фев 12, 14:26    [12110081]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
CLR Aggregate
Guest
Mnior
Неверю. ©

Силь ву пле:
+
using System;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;

[Serializable]
[SqlUserDefinedAggregate(
  Format.UserDefined,
  IsInvariantToNulls = true,
  IsInvariantToDuplicates = false,
  IsInvariantToOrder = false,
  MaxByteSize = 8000)
]
public struct AggregateTest : IBinarySerialize
{
  private StringBuilder _intermediateResult;
  private String _delimiter;

  public void Init()
  {
    _intermediateResult = new StringBuilder();
    _delimiter=string.Empty;
  }

  public void Accumulate(SqlString value, SqlString delimiter)
  {
    if (value.IsNull) return;
    _intermediateResult.Append(_delimiter).Append(value.Value);
    _delimiter = delimiter.Value ?? string.Empty;
  }

  public void Merge(AggregateTest other)
  {
    _intermediateResult.Append(_delimiter).Append(other._intermediateResult);
    _delimiter = other._delimiter;
  }

  public SqlString Terminate()
  {
    var output = string.Empty;
    if (_intermediateResult != null && _intermediateResult.Length > 0)
      output = _intermediateResult.ToString()+(_delimiter==null);
    return new SqlString(output);
  }

  public void Read(BinaryReader r)
  {
    _intermediateResult = new StringBuilder(r.ReadString());
  }

  public void Write(BinaryWriter w)
  {
    w.Write(_intermediateResult.ToString());
  }
}

Mnior
Никаой привилегии в полях и типах нет. Это бред.

Да я как бы с дотнетом/сишарпом далеко уже не первый год дело имею, и сам прекрасно знаю, что бред. Однако факт имеет место быть:
drop AGGREGATE uf_AggregateTest
go
drop assembly AggregateTest
go
create assembly AggregateTest from 'D:\Program Files\Microsoft SQL Server\100\CLR\AggregateTest.dll'
with permission_set = safe
GO
create AGGREGATE uf_AggregateTest (@input nvarchar(200), @delimeter nvarchar(20)) RETURNS nvarchar(max)
external name AggregateTest.AggregateTest
go

select dbo.uf_AggregateTest(number,'##') from master..spt_values where type='P' and number between 1 and 5

Результат:

1##2##3##4##5True

(да, разумеется, я проверил, что создавал ассамблею из той длл-ки, которая есть результат компиляции вышеприведенного).
17 фев 12, 17:00    [12111732]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Antoshka
Member

Откуда:
Сообщений: 828
output = _intermediateResult.ToString()+(_delimiter==null);


Как думаете, чему равно значение выражения (_delimiter==null) при приведении к типу string?
17 фев 12, 21:02    [12113160]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Antoshka
Member

Откуда:
Сообщений: 828
output = _intermediateResult.ToString()+(_delimiter==null);


Как думаете, чему равно значение выражения (_delimiter==null) при приведении к типу string?
17 фев 12, 21:24    [12113306]     Ответить | Цитировать Сообщить модератору
 Re: Странное поведение класса-реализации CLR aggregate function  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
CLR Aggregate,
Ну у вас ещё Read(BinaryReader) и Write(BinaryWriter) косяковые. Они теряют _delimiter при передаче.
Вы должны нормально написать сериализацию/десериализацию. пример

Как я понимаю формат должен быть такой Format.UserDefined, иначе (Native и без IBinarySerialize) вам руганётся на StringBuilder. Так?
Если нет, тады незачем вводить сервер в заблуждение.
17 фев 12, 21:36    [12113369]     Ответить | Цитировать Сообщить модератору
Все форумы / Microsoft SQL Server Ответить