自定义聚合函数间歇性地抛出NullReferenceException

时间:2015-08-05 13:15:48

标签: c# sql-server sqlclr

我有一个SQL CLR聚合函数,当针对同一数据集执行时偶尔抛出System.NullReferenceException异常。自定义聚合的目的是:

  

返回最新(x,y),其中 x DATETIME列, y INTEGER列。

     

将返回 y 列的值,用于列x的最新值。

查询命中的数据集是2,931,563行表的142,145行的子集,其聚合结果(运行时)返回141,654行。

CLR聚合函数的代码如下:

using System.Data.SqlTypes;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.Server;

[StructLayout(LayoutKind.Sequential)]
[SqlUserDefinedAggregate(Format.Native, IsInvariantToDuplicates = true, IsInvariantToNulls = true, IsInvariantToOrder = true, IsNullIfEmpty = true, Name = "Latest")]
public class Latest
{
    private SqlDateTime latestDateTime;
    private SqlInt32 latestValue;

    public void Init()
    {
        latestDateTime = SqlDateTime.Null;
        latestValue = SqlInt32.Null;
    }

    public void Accumulate(SqlDateTime recordDateTime, SqlInt32 value)
    {
        if (latestDateTime.IsNull)
        {
            latestDateTime = recordDateTime;
            latestValue = value;
        }
        else
        {
            if (recordDateTime > latestDateTime)
            {
                latestDateTime = recordDateTime;
                latestValue = value;
            }
        }
    }

    public void Merge(Latest value)
    {
        if ((value.latestDateTime < latestDateTime) || (latestDateTime.IsNull))
        {
            latestValue = value.latestValue;
            latestDateTime = value.latestDateTime;
        }
    }

    public SqlInt32 Terminate()
    {
        return latestValue;
    }
};

据我所知,函数中没有任何地方可以导致空引用,假设SQL服务器跟随contract outlined on MSDN(虽然它比SQL Server更可能错误!)。 简而言之,我在这里缺少什么?

澄清:

  • 我相信我已经满足了SqlUserDefinedAggregate合同的要求(实现了所有必需的方法)
  • 代码初始化Init方法中的所有成员变量(同样是合同实现的一部分),以确保如果SQL重新使用(因为它被允许)另一个组的聚合实例,它将被清除down和non-null
  • 显然,我错过了我期望遇到的合同的细微差别,因为我看不出抛出NullReferenceException的原因。 我错过了什么?

3 个答案:

答案 0 :(得分:1)

这可能不是答案,但由于我需要包含代码,我不会将其作为评论发布。

为value.latestDateTime设置NULL值不会导致此错误。当OBJECT为null时,您只获得NULLReferenceException,并尝试引用它(例如,通过访问它的属性)。

我看到您引用对象的代码中唯一的位置是Merge void。对象为value,其类型为Latest

我知道你说理论上理论上可能永远不会为null,但是由于Merge是公开的,因此无法在您发布的代码中证明或证明,因此可以从其他应用程序访问。

我在OOP中发现,在引用它之前始终对任何对象进行空检查是最安全的。您所要做的就是将合并代码更改为此(如果我的c#内存服务...如果我的语法关闭,我相信你会明白这一点):

public void Merge(Latest value)
{
    if (value != null)
    {
        if ((value.latestDateTime < latestDateTime) || (latestDateTime.IsNull))
        {
            latestValue = value.latestValue;
            latestDateTime = value.latestDateTime;
        }
    }
}

如果您想在值IS为null时执行其他操作,则由您决定。但这使得这段代码绝对安全,而不是信任“理论上应该不可能”,这几乎总是意味着“可能”; )

答案 1 :(得分:1)

一切似乎都是正确的。除了可能有一件事。两个输入参数的源列可以是NULL吗?代码本身似乎处理NULL。但是,如果数据中有NULL个,那么我认为您IsInvariantToNulls属性的SqlUserDefinedAggregate属性设置不正确,因为它应该是false NULL任何一个字段都可能影响结果。

此外:

  • 用于recordDateTime的字段的数据类型为DATETIME而不是DATETIME2,是吗?

  • 在添加OPTION(MAXDOP 1)作为查询提示后尝试运行查询,因为这样会阻止调用Merge方法,这有助于缩小问题范围。如果OPTION(MAXDOP 1)永远不会发生异常,则很可能与Merge方法有关。但如果它仍然存在,则很可能与Merge方法无关。

  • SqlDateTime<时,澄清>如何处理比较运算符(即.IsNulltrue)的一些问题:它是处理得当,如果这些运营商的任何一方有false,则会返回.IsNull == true

  • 我还会删除[StructLayout(LayoutKind.Sequential)]装饰器。默认的.NET处理应该没问题。

答案 2 :(得分:0)

我会为NULL添加显式检查:

public void Accumulate(SqlDateTime recordDateTime, SqlInt32 value)
{
    if (latestDateTime.IsNull)
    {
        latestDateTime = recordDateTime;
        latestValue = value;
    }
    else
    {
        if (!recordDateTime.IsNull)
        {
            if (recordDateTime > latestDateTime)
            {
                latestDateTime = recordDateTime;
                latestValue = value;
            }
        }
    }
}

此外,Merge中的逻辑似乎错了......我会重复与Accumulate中相同的代码:

public void Merge(Latest value)
{
    if (latestDateTime.IsNull)
    {
        latestDateTime = value.latestDateTime;
        latestValue = value.latestValue;
    }
    else
    {
        if (!value.latestDateTime.IsNull)
        {
            if (value.latestDateTime > latestDateTime)
            {
                latestDateTime = value.latestDateTime;
                latestValue = value.latestValue;
            }
        }
    }
}