我有一个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更可能错误!)。 简而言之,我在这里缺少什么?
澄清:
Init
方法中的所有成员变量(同样是合同实现的一部分),以确保如果SQL重新使用(因为它被允许)另一个组的聚合实例,它将被清除down和non-null 答案 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
为<
时,澄清>
如何处理比较运算符(即.IsNull
和true
)的一些问题:它是处理得当,如果这些运营商的任何一方有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;
}
}
}
}