SQL CLR 聚合在应用于大量数据时未正确终止

SQL CLR aggregate not terminating correctly when applied over huge amount of data

我创建并使用了很多次 SQL CLR 聚合,它连接值 - 它也按指定的数字对值进行排序,并使用用户输入分隔符连接它们。

我对大量数据使用了相同的聚合,并注意到未使用分隔符 - 值被连接但没有分隔符。

经过大量测试,发现在Terminate方法中,分隔符是missing/not读取的。我使用硬编码分隔符仔细检查了这一点 - 一切正常。

我猜想我的 ReadWrite 方法有问题(在处理大量数据时使用)但无法理解。

函数代码如下:

[Serializable]
[
    Microsoft.SqlServer.Server.SqlUserDefinedAggregate
    (
        Microsoft.SqlServer.Server.Format.UserDefined,
        IsInvariantToNulls = true,
        IsInvariantToDuplicates = false,
        IsInvariantToOrder = false,
        IsNullIfEmpty = false,
        MaxByteSize = -1
    )
]
/// <summary>
/// Concatenates <int, string, string> values defining order using the specified number and using the given delimiter
/// </summary>
public class ConcatenateWithOrderAndDelimiter : Microsoft.SqlServer.Server.IBinarySerialize
{
    private List<Tuple<int, string>> intermediateResult;
    private string delimiter;
    private bool isDelimiterNotDefined;

    public void Init()
    {
        this.delimiter = ",";
        this.isDelimiterNotDefined = true;
        this.intermediateResult = new List<Tuple<int, string>>();
    }

    public void Accumulate(SqlInt32 position, SqlString text, SqlString delimiter)
    {
        if (this.isDelimiterNotDefined)
        {
            this.delimiter = delimiter.IsNull ? "," : delimiter.Value;
            this.isDelimiterNotDefined = false;
        }

        if (!(position.IsNull || text.IsNull))
        {
            this.intermediateResult.Add(new Tuple<int, string>(position.Value, text.Value));
        }
    }

    public void Merge(ConcatenateWithOrderAndDelimiter other)
    {
        this.intermediateResult.AddRange(other.intermediateResult);
    }

    public SqlString Terminate()
    {
        this.intermediateResult.Sort();
        return new SqlString(String.Join(this.delimiter, this.intermediateResult.Select(tuple => tuple.Item2)));
    }

    public void Read(BinaryReader r)
    {
        if (r == null) throw new ArgumentNullException("r");

        int count = r.ReadInt32();
        this.intermediateResult = new List<Tuple<int, string>>(count);

        for (int i = 0; i < count; i++)
        {
            this.intermediateResult.Add(new Tuple<int, string>(r.ReadInt32(), r.ReadString()));
        }

        this.delimiter = r.ReadString();
    }

    public void Write(BinaryWriter w)
    {
        if (w == null) throw new ArgumentNullException("w");

        w.Write(this.intermediateResult.Count);

        foreach (Tuple<int, string> record in this.intermediateResult)
        {
            w.Write(record.Item1);
            w.Write(record.Item2);
        }

        w.Write(this.delimiter);
    }
}

我找到问题了。它在 Merge 方法中。它是:

public void Merge(ConcatenateWithOrderAndDelimiter other)
{
    this.intermediateResult.AddRange(other.intermediateResult);
}

然后我将其更改为:

public void Merge(ConcatenateWithOrderAndDelimiter other)
{
    this.intermediateResult.AddRange(other.intermediateResult);
    this.delimiter = other.delimiter;
}

好像data是merge的时候,分隔符没有初始化。我想在上面的上下文中,所有 this 属性都是空的。

无论如何,我不会接受这个作为答案,因为如果有人能够解释内部发生的事情,那将会很有帮助。

Merge() 方法仅在使用并行性并且特定组分布在多个线程时才会被调用。在这种情况下,Init() 已被调用,Accumulate().

的 0 个或多个实例

因此,在并行的情况下,如果 Init() 已被调用但尚未调用 Accumulate(),则 delimiter 中的值将是Init() 方法。问题中的代码显示它被设置为 ,,但我怀疑这是后来在试图解决这个问题时添加的。当然,这假设将逗号作为分隔符传入 Accumulate()。或者也许逗号总是被设置为 Init() 中的默认值,但另一个字符是通过 Accumulate() 传入的,并且没有通过最终输出(对 UDA 的特定调用未显示在问题,也不是错误的输出,所以这里有些歧义。

虽然另一个答案中显示的修复似乎有效,但它不是通用修复,因为可能存在当前对象至少调用过一次 Accumulate() 的情况,但 "other" 合并到这个对象中的对象仍然是空的(可能没有匹配的行,或者调用 Accumulate() 时值未存储在本地的其他一些原因)。在这种情况下,当前对象将具有所需的分隔符,但 "other" 对象仍将具有默认值。理想的解决方案是在 Write() 方法中也存储 isDelimiterNotDefined 的值,在 Read() 方法中再次将其取回,并将本地值与 other.isDelimiterNotDefined 进行比较在 Merge() 方法中,以便您可以确定是否应该保留 delimiter 的本地值或其他值(取决于设置/定义的值)。