什么是IBinarySerialize接口方法用于?

got*_*tqn 9 .net c# sql sql-server sqlclr

创建自定义聚合函数时,需要指定枚举格式:

SqlUserDefinedTypeAttribute和SqlUserDefinedAggregateAttribute使用Format Enumeration来指示用户定义类型(UDT)或聚合的序列化格式.

当使用UserDefinedformat 时,您的类需要实现IBinarySerialize接口并覆盖它readwrite方法.

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

看一下这些例子,我猜他们应该能够读/写聚合结果吗?

例如,我正在尝试创建一个连接不同数字的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));
    }
}
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 7

用户定义聚合(UDA)的任何特定实例都不保证在查询的整个生命周期中都存在.它需要有一个可存储的代表.当您只使用值类型时(如问题中的"枚举格式"链接所述),提供的ReadWrite方法了解如何序列化和反序列化UDA,在这种情况下您将使用Format.Native.但是当你开始使用引用类型(字符串,集合,自定义类型等)时,由你来定义这些值如何被序列化和反序列化,在这种情况下你需要使用Format.UserDefined和覆盖ReadWrite方法,这样你就可以进入控制那些行动.

需要序列化的值是将UDA的新实例恢复到序列化之前所处的确切状态所需的任何值.这意味着:不要依赖于运行的Init()方法(它每组运行一次​​!)或变量初始化器(它们每次实例化运行一次,并且UDA可以重用于多个组而无需重新创建!).因此,您需要序列化所有基值,即使它们与最终输出没有直接关系.


也就是说,你至少应该在@ Damien_The_Unbeliever的回答中做出优化:

  • 不要在Write方法中进行排序.你已经在Terminate方法(适当的地方)做了,所以做两次是没用的,更不用说效率很低了.

  • 存储集合的计数,然后存储各个元素

除此之外:

  • 当你说你的UDA"连接不同的数字"时,如果你真的意味着"不同",那么你需要检查每个数字,看它是否已经在列表中.我怀疑这是你的愿望,因为你已经IsInvariantToDuplicates设定为真.您可以在Accumulate方法中执行此操作:

    if (!intermediateResult.Contains(value.Value))
    {
      intermediateResult.Add(value.Value);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    Merge方法中(涉及并行性时称为):

    foreach (byte _NewValue in other.intermediateResult)
    {
      if (!intermediateResult.Contains(_NewValue))
      {
        intermediateResult.Add(_NewValue);
      }
    }    
    
    Run Code Online (Sandbox Code Playgroud)

    请注意我改变你的演员(byte)value- 在Accumulate方法中使用该Value属性.所有的SqlTypes(如SqlByte,SqlString,SqlInt32,等)有Value返回,你会期望在.NET类型属性.这意味着,没有必要呼吁ToString()SqlString尽可能多的人似乎做.

  • 我会谨慎对待MaxByteSize1024.由于在一个字符串(当前方法)中保存"165; 207"在技术上是14个字节(每个字符7个字符*2个字节)而保存,因此实​​施@ Damien的建议可以部分减轻这种担忧.计数和单个字节只有6个字节(Int32用于存储count+ 2个字节的4个字节).而这种差异只是用于存储2个值.存200?Yeesh!

  • 您没有指定IsNullIfEmpty属性.您需要指定它,特别是考虑到您的Terminate方法在内部集合时返回空字符串null.您应该添加,IsNullIfEmpty = false因为NULL如果永远不会调用它,您不想返回.

  • Terminate处理null集合的方法中的额外逻辑可能不是必需的.该集合在InitRead方法中初始化,因此我不确定nullTerminate调用时它是如何成为的.


如果您想要一个创建用户定义聚合的示例Format.UserDefined,请查看充分利用SQL Server 2005 UDT和UDA(需要免费注册).我在SQL Server 2008问世之前编写过,它允许超过8000个字节被序列化,因此您可以忽略(目前)有关压缩数据以进行序列化的方面.

此外,如果您想了解更多关于SQLCLR的信息,我正在为SQL Server Central编写一系列文章:SQLCLR的阶梯(与第一个链接文章相同的网站).