2015-01-05 49 views
8

当创建一个定制的聚合函数需要指定的enumeration format什么是IBinarySerialize接口方法用于?

格式枚举由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)); 
    } 
} 
+1

你用'Write' *写的全部内容必须能够被'Read'读取(不会再少一些,因为一般读者会坐在不属于你的基础流上),而是*什么*正是你放入这个界面的客户端(通常SQL Server代码)是非常不透明的。 –

+1

我会从'Write'方法中拿出'Sort'。在这些数据对调用者来说实际上是可见的之前,它将通过调用Read来重新处理,然后(可能在一些'Merge's之后)会调用Terminate来获得实际结果。 –

回答

6

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

需要序列化的值是将UDA的新实例返回到确切的状态所需的任何事情,它是在它被序列化之前。这意味着:不要依赖运行的Init()方法(它每个组运行一次​​!)或变量初始值设定项(它们每个实例运行一次,并且UDA可以在不重新创建的情况下重复用于多个组)。所以你需要序列化所有的基值,即使它们与最终输出没有直接关系。


这就是说,你应该,最起码,做@ Damien_The_Unbeliever的回答指出,优化:

  • 不要做那种在Write方法。你已经在Terminate方法(适当的地方)中做了它,所以两次都没用,更不用说非常低效了。

  • 商店收集的计数,然后将各个元素

除此之外:

  • 当你说你的UDA“串接个不同的数字,”如果你真的意味着“不同“那么你需要检查每个数字,看它是否已经在列表中。我怀疑这是你的愿望,因为你有IsInvariantToDuplicates设置为true。 (并行时涉及的称呼)

    if (!intermediateResult.Contains(value.Value)) 
    { 
        intermediateResult.Add(value.Value); 
    } 
    

    ,并在Merge方法:你可以这样做无论是在Accumulate方法

    foreach (byte _NewValue in other.intermediateResult) 
    { 
        if (!intermediateResult.Contains(_NewValue)) 
        { 
        intermediateResult.Add(_NewValue); 
        } 
    }  
    

    请注意,我改变了你的施法 - (byte)value - 中Accumulate方法转化为使用Value属性。所有SqlTypes(例如SqlByte,SqlString,SqlInt32等)都有一个Value属性,该属性返回您期望的.NET类型。这意味着没有必要像SqlString那样呼叫ToString(),因为很多人似乎都这样做。

  • 我会小心处理的1024,值得关注MaxByteSize将通过实施@达米安的给出建议,部分地减轻,拯救“165; 207”在一个字符串(电流法)在技术上是14个字节(7个字符* 2字节),而保存计数和单个字节只有6个字节(Int32存储count + 2个字节需要4个字节)。这种差异仅仅用于存储2个值。存储200? Yeesh!

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

  • 用于处理null集合的Terminate方法中的额外逻辑可能不是必需的。该集合在InitRead方法中初始化,所以我不确定到Terminate被调用的时候它会如何成为null


如果你想创建一个用户定义的聚合与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

我已经阅读并投票了你的文章,我正在等待系列文章的其余部分。那么,使用'value'属性更快?例如,总是使用'value.Value'而不是'(byte)value'。感谢您的其他提示。等待下一篇文章:-) – gotqn

+0

@gotqn谢谢:)。不知道是否使用'价值'必然_faster_,但肯定更加一致和可靠。 –

3

你总可以被序列化和删除的,而它是通过处理它运行在行一半。然后数据库引擎可以创建一个新的实例并反序列化以返回它离开的位置。

因为这样的Write方法需要能够存储聚合的状态,当只有一些记录已被传递到Accumulate。方法Read需要能够设置聚合备份准备好更多的呼叫AccumulateMerge

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

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

正如我在评论中建议,我也从Write方法去除Sort因为总会有最后Sort调用Terminate,然后将构建的数据传递给您的聚合用户。


我们存储Count第一个数据让我们知道有多少次调用ReadByteRead方法。这也允许(可能是毫无意义的)优化,我们可以告诉构造函数它需要多少空间。

+1

有关如何更好地存储收藏的绝佳捕获/推荐。我将这个答案记入我的。 +1 –

1

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

因此,Write方法应该保存到重建对象在其当前状态(数据)所需的一切和​​Read方法应该采取什么被写入输出并设置对象的状态(所以它符合原件)。

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

相关问题