什么是IBinarySerialize接口方法用于?

时间:2015-01-05 14:47:59

标签: c# sql .net sql-server sqlclr

创建自定义聚合函数时,需要指定enumeration format

  

格式枚举由SqlUserDefinedTypeAttribute和。使用   SqlUserDefinedAggregateAttribute指示序列化格式   用户定义类型(UDT)或聚合。

并且在使用UserDefined格式时,您的班级需要实施IBinarySerialize Interface并覆盖其readwrite方法。

我的问题是这些方法到底需要做什么?

查看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));
    }
}

4 个答案:

答案 0 :(得分:7)

无法保证用户定义聚合(UDA)的任何特定实例在查询的整个生命周期中都存在。它需要有一个可存储的代表。当您仅使用值类型时(如问题中的&#34;枚举格式&#34;链接中所述),提供的ReadWrite方法了解如何序列化和反序列化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(例如SqlTypesSqlByteSqlString等)都有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方法中的额外逻辑可能不是必需的。该集合已在InitRead方法中初始化,因此我不确定在null被调用时它是如何Terminate的。

    < / LI>

如果您想要一个使用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方法需要能够为AccumulateMerge上的更多来电设置备用数据。

因此我会说你已经正确实现了这些。

答案 3 :(得分:1)

IBianarySerialize方法用于保存对象并在需要将其写入磁盘时进行恢复。

因此,Write方法应保存在当前状态(数据)中重新创建对象所需的所有内容,Read方法应采用Write输出的内容并设置对象的状态(所以它与原版相符。

其他答案似乎很擅长解决此过程中的问题,但我建议尽可能小而快地使用这些方法保存您正在阅读/写入的数据。