I have a SQL CLR aggregate function which sporadically throws a System.NullReferenceException
exception when executed against the same dataset. The purpose of the custom aggregate is to:
Return latest(x, y) where x is a
DATETIME
column and y is anINTEGER
column.The value for column y for the most recent value of column
x
will be returned.
The dataset being hit by the query is a subset of 142,145 rows of a 2,931,563 row table, with the aggregation resulting in (when it runs) 141,654 rows being returned.
The code for the CLR aggregate function is as follows:
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;
}
};
As far as I can tell there's nowhere in the function that can result in a null reference, assuming that SQL server is following the contract outlined on MSDN (though it's much more likely I'm wrong than SQL Server!). So in short, what am I missing here?
To clarify:
- I believe I've met the requirements of the contract for a SqlUserDefinedAggregate (all required methods implemented)
- The code initialises all member variables in the
Init
method (again part of the contract implementation) to ensure that if SQL re-uses (as it's permitted to) an instance of the aggregate for a different group it's cleaned down and non-null - Clearly I've missed a nuance of the contract that I'm expected to meet as I can see no reason for the NullReferenceException to be thrown. What have I missed?