创建自定义聚合函数时,需要指定enumeration format:
格式枚举由SqlUserDefinedTypeAttribute和。使用 SqlUserDefinedAggregateAttribute指示序列化格式 用户定义类型(UDT)或聚合。
并且在使用UserDefined
格式时,您的班级需要实施IBinarySerialize Interface并覆盖其read
和write
方法。
我的问题是这些方法到底需要做什么?
查看examples,我猜他们应该能够读取/写入聚合结果吗?
例如,我正在尝试创建一个连接不同数字的SQL CLR函数。在T-SQL中,我可以有1到255个不同的数字(TINYINT值)。我需要从它们创建一个字符串(使用分隔符),但也要对数字进行排序。该函数似乎有效,但我不确定我是否按预期覆盖了这些方法:
[Serializable]
[
Microsoft.SqlServer.Server.SqlUserDefinedAggregate
(
Microsoft.SqlServer.Server.Format.UserDefined,
IsInvariantToNulls = true,
IsInvariantToDuplicates = true,
IsInvariantToOrder = false,
MaxByteSize = 1024
)
]
public class ConcatenateAnswersPos : Microsoft.SqlServer.Server.IBinarySerialize
{
private List<byte> intermediateResult;
public void Init()
{
intermediateResult = new List<byte>();
}
public void Accumulate(SqlByte value)
{
intermediateResult.Add((byte)value);
}
public void Merge(ConcatenateAnswersPos other)
{
intermediateResult.AddRange(other.intermediateResult);
}
public SqlString Terminate()
{
if (intermediateResult != null)
{
intermediateResult.Sort();
return new SqlString(string.Join(";", intermediateResult));
}
else
{
return new SqlString("");
}
}
public void Read(BinaryReader r)
{
if (r == null) throw new ArgumentNullException("r");
intermediateResult = new List<byte>();
string[] answers = r.ReadString().Split(';');
foreach (string answer in answers)
{
intermediateResult.Add(Convert.ToByte(answer));
}
}
public void Write(BinaryWriter w)
{
if (w == null) throw new ArgumentNullException("w");
intermediateResult.Sort();
w.Write(string.Join(";", intermediateResult));
}
}
答案 0 :(得分:7)
无法保证用户定义聚合(UDA)的任何特定实例在查询的整个生命周期中都存在。它需要有一个可存储的代表。当您仅使用值类型时(如问题中的&#34;枚举格式&#34;链接中所述),提供的Read
和Write
方法了解如何序列化和反序列化UDA,在哪种情况下你会使用Format.Native
。但是当你开始使用引用类型(字符串,集合,自定义类型等)时,由你来定义如何序列化和反序列化这些值,在这种情况下你需要使用Format.UserDefined
并覆盖{{ 1}}和Read
方法,因此您可以控制这些操作。
需要序列化的值是将新的UDA实例恢复到序列化之前所处的完全状态所需的任何值。这意味着:不要依赖于运行的Init()方法(它每组运行一次!)或变量初始化器(它们每次实例化运行一次,并且UDA可以重用于多个组而无需重新创建!)。因此,您需要序列化所有基值,即使它们与最终输出没有直接关系。
那就是说,你至少应该在@ Damien_The_Unbeliever的答案中注明优化:
不要在Write
方法中进行排序。你已经在Write
方法(适当的地方)进行了这样做,所以做两次是没用的,更不用说非常低效了。
存储集合的计数,然后存储各个元素
除此之外:
当你说你的UDA&#34;连接不同的数字&#34;如果你真的意味着&#34; distinct&#34;那么你需要检查每个数字,看它是否已经在列表中。我怀疑这是你的愿望,因为你Terminate
设置为真。您可以在IsInvariantToDuplicates
方法中执行此操作:
Accumulate
和if (!intermediateResult.Contains(value.Value))
{
intermediateResult.Add(value.Value);
}
方法(涉及并行性时调用):
Merge
请注意,我已使用foreach (byte _NewValue in other.intermediateResult)
{
if (!intermediateResult.Contains(_NewValue))
{
intermediateResult.Add(_NewValue);
}
}
方法将您的广告素材(byte)value
- 更改为使用Accumulate
属性。所有Value
(例如SqlTypes
,SqlByte
,SqlString
等)都有SqlInt32
属性,可返回您期望的.NET类型。这意味着您无需像Value
那样致电ToString()
。
我会对1024的SqlString
保持谨慎。考虑到保存&#34; 165; 207&#34;可以通过实施@Damien的建议来部分缓解这种担忧。在字符串(当前方法)中技术上是14个字节(7个字符*每个字符2个字节),而保存计数和单个字节只有6个字节(Int32存储MaxByteSize
+ 2个字节的4个字节) 。而这种差异只是用于存储2个值。存200? Yeesh!
您没有指定IsNullIfEmpty属性。您需要指定此项,尤其是考虑到如果内部集合为count
,则Terminate
方法返回空字符串。您应该添加null
,因为如果永远不会调用IsNullIfEmpty = false
,则您不想返回NULL
。
处理Terminate
集合的null
方法中的额外逻辑可能不是必需的。该集合已在Init
和Read
方法中初始化,因此我不确定在null
被调用时它是如何Terminate
的。
如果您想要一个使用Format.UserDefined
创建用户定义聚合的示例,请查看Getting The Most Out of SQL Server 2005 UDTs and UDAs(需要免费注册)。我在SQL Server 2008问世之前编写过,它允许超过8000个字节被序列化,因此您可以忽略(目前)有关压缩数据以进行序列化的方面。
另外,如果您想了解有关SQLCLR的更多信息,我正在为SQL Server Central撰写一系列文章:Stairway to SQLCLR(与第一个链接文章相同的网站)。
答案 1 :(得分:4)
我会说你在你的方法上做的工作比你需要的多。您需要做的只是在Write
方法中写得足够多,这样您的Read
方法就可以重建您的内部状态。由于您的内部状态只是List<byte>
,因此无需将所有内容视为字符串:
public void Read(BinaryReader r)
{
if (r == null) throw new ArgumentNullException("r");
var count= r.ReadInt32();
intermediateResult = new List<byte>(count);
for (int i=0;i<count;i++)
{
intermediateResult.Add(r.ReadByte());
}
}
public void Write(BinaryWriter w)
{
if (w == null) throw new ArgumentNullException("w");
w.Write(intermediateResult.Count);
foreach(byte b in intermediateResult)
{
w.Write(b);
}
}
正如我在评论中所建议的那样,我还从Sort
方法中移除了Write
,因为Sort
中始终会有最终Terminate
来电在您构建的数据传递给聚合的消费者之前。
我们先将Count
存储在数据中,以便我们知道在ReadByte
方法中调用Read
的次数。这也允许(可能毫无意义的)优化,我们可以告诉List<byte>
构造函数确切需要多少项目空间。
答案 2 :(得分:3)
您的聚合可以序列化并删除,同时处理它所操作的行的一半。然后,数据库引擎可以创建一个新实例并反序列化以返回到它停止的位置。
因此,当只有一些记录传递给Write
时,Accumulate
方法需要能够存储聚合的状态。 Read
方法需要能够为Accumulate
或Merge
上的更多来电设置备用数据。
因此我会说你已经正确实现了这些。
答案 3 :(得分:1)
IBianarySerialize
方法用于保存对象并在需要将其写入磁盘时进行恢复。
因此,Write
方法应保存在当前状态(数据)中重新创建对象所需的所有内容,Read
方法应采用Write输出的内容并设置对象的状态(所以它与原版相符。
其他答案似乎很擅长解决此过程中的问题,但我建议尽可能小而快地使用这些方法保存您正在阅读/写入的数据。