2015-08-05 3 views
1

J'ai une fonction d'agrégation SQL CLR qui déclenche sporadiquement une exception System.NullReferenceException lorsqu'elle est exécutée sur le même ensemble de données. Le but de l'agrégat personnalisé est à:La fonction d'agrégat personnalisée lance par intermittence NullReferenceException

Retour dernière (x, y)x est une colonne DATETIME et y est une colonne INTEGER.

La valeur de la colonne y pour la valeur la plus récente de la colonne x sera renvoyée.

L'ensemble de données d'être touché par la requête est un sous-ensemble de 142,145 lignes d'une table de rangée 2.931.563, à l'agrégation résultant en (quand il fonctionne) 141,654 rangées étant retournés.

Le code de la fonction d'agrégation CLR est la suivante:

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

Pour autant que je peux dire qu'il n'y a nulle part dans la fonction qui peut se traduire par une référence null, en supposant que SQL Server suit le contract outlined on MSDN (même si c'est beaucoup plus probable que je me trompe que SQL Server!). Donc, en bref, qu'est-ce qui me manque ici?

Pour clarifier:

  • Je crois que je l'ai rencontré les exigences du contrat pour une SqlUserDefinedAggregate (toutes les méthodes nécessaires mises en œuvre)
  • Le code initialise toutes les variables membres dans la méthode Init (encore une fois partie de l'implémentation du contrat) pour s'assurer que si SQL réutilise (comme c'est permis) une instance de l'agrégat pour un groupe différent, il est nettoyé et non nul.
  • Il est clair que j'ai manqué une nuance du contrat que j'ai Je suis attendu à rencontrer car je ne vois pas de raison pour l'exception NullReferenceException à lancer. Qu'est-ce que j'ai raté?
+0

Ceci est une classe partielle, y at-il un autre code? Il y a des valeurs nulles sur la colonne x pour le sous-ensemble? – Max

+0

Aucune idée de la raison pour laquelle 'partial' est présent puisqu'il n'y a qu'un seul fichier qui définit cette classe. Bien que cela rende nos commentaires un peu bizarres, je vais supprimer le partial du code pour aider à la clarté :) – Rob

+0

Le seul suspect que l'on puisse voir à distance est value.latestDateTime (dans le void Merge). S'il est possible que la valeur soit nulle, je pense que cela pourrait déclencher l'erreur de référence nulle. –

Répondre

0

Je voudrais ajouter vérification explicite 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; 
      } 
     } 
    } 
} 

Par ailleurs, votre logique Merge semble mal ... Je répète le même code que dans 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; 
      } 
     } 
    } 
} 
1

Cela peut ne pas être la réponse, mais comme je dois inclure du code, je ne le publierai pas comme commentaire. Une valeur NULL pour value.latestDateTime ne provoquerait pas cette erreur.

Vous obtenez seulement le NULLReferenceException quand un objet est nul, et vous essayez de vous y référer (en accédant à ses propriétés par exemple).

Le seul endroit dans votre code où je vois que vous faites référence à un objet se trouve dans le vide de fusion. L'objet est value qui est de type Latest.Je sais que vous dites qu'il n'est théoriquement pas possible que la valeur soit nulle, mais cela ne peut pas être prouvé ou réfuté dans le code que vous avez publié depuis que Merge est public et donc accessible à partir d'autres applications.

J'ai trouvé dans OOP qu'il est plus sûr de vérifier TOUJOURS n'importe quel objet avant de le référencer. Tout ce que vous avez à faire est de changer votre code de fusion pour cela (si mon C# ma mémoire est bonne ... si ma syntaxe est désactivée, je suis sûr que vous aurez l'idée):

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

Il est à vous si vous voulez faire autre chose quand la valeur EST nulle. Mais cela rend cette partie du code absolument sûre de NullReferenceExceptions, plutôt que de faire confiance à «théoriquement ne devrait pas être possible», ce qui signifie presque toujours «EST possible»;

+0

Le paramètre entrant dans 'Merge' ne sera jamais' null', el il n'y avait rien à fusionner. Je n'ai jamais fait une vérification NULL dans la fusion et n'ai jamais eu une erreur ou même entendu parler d'une telle chose se passe. –

1

Tout semble correct. Sauf peut-être une chose. L'une des colonnes source des deux paramètres d'entrée peut-elle être NULL? Le code lui-même semble gérer NULL s. Cependant, s'il y a NULL dans les données alors je pense que vous avez la propriété IsInvariantToNulls de l'attribut SqlUserDefinedAggregate incorrectement défini comme il devrait être false puisqu'une NULL dans n'importe quel champ pourrait affecter le résultat.

aussi:

  • Le type de données du champ utilisé pour recordDateTime est DATETIME et non DATETIME2, correct? Essayez d'exécuter la requête après avoir ajouté OPTION(MAXDOP 1) en tant qu'indicateur de requête car cela devrait empêcher l'appel de la méthode Merge et permettre de réduire le problème. Si l'exception ne se produit jamais avec OPTION(MAXDOP 1) alors il s'agit probablement de la méthode Merge. Mais si cela se produit encore, alors cela n'a probablement rien à voir avec la méthode Merge.

  • Pour clarifier certaines interrogations de la façon dont les poignées SqlDateTime opérateurs de comparaison (à savoir le < et >) lorsque .IsNull est true: il est manipulé correctement, et retourne un false si chaque côté de ces opérateurs a .IsNull == true. Je voudrais également supprimer le décorateur [StructLayout(LayoutKind.Sequential)]. La gestion par défaut de .NET devrait être bien.