Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

CoqBlog

.NET is good :-)
{ Blog de coq }

Actualités

SqlParameter.Value et référence nulle : ce qu'il ne faut pas oublier

Il s'agit d'un point qui peut être facilement oublié, surtout si on ne réalise pas souvent de code d'accès aux données "bas niveau" : affecter une référence nulle à la propriété Value d'une instance de la classe SqlParameter ne provoque PAS l'envoi d'un NULL au serveur SQL.
Dans les faits, affecter une référence nulle à un paramètre sur un appel de procédure stockée équivaut à demander à ce que la valeur par défaut du paramètre soit utilisée : quand on doit envoyer un NULL au serveur, on doit affecter DBNull.Value en tant que valeur du paramètre.

Si on ne veille pas à ce point on risque de rencontrer des comportements non désirés (liste probablement non-exhaustive) :

  • Levée d'une exception SqlException avec ce type de message : "Procedure or function 'sp_DoSomething' expects parameter '@Name', which was not supplied." ("La procédure ou fonction 'sp_DoSomething' attend le paramètre '@Name', qui n'a pas été fourni.").
  • Utilisation non désirée de la valeur par défaut du paramètre de procédure stockée.

Je considère le cas d'utilisation non désirée de la valeur par défaut comme le plus problématique car il peut mener à une corruption de données directe.
Par "corruption de données directe" j'entends le cas où l'action en elle-même corrompt les données, donc l'absence de risque de corruption directe ne veut pas dire qu'il n'y aura pas un de ces cas de corruption indirecte qui peut survenir quand un code étouffe un cas d'erreur non attendu.

A ma connaissance l'oubli d'utilisation de DBNull.Value dans le cas de requêtes INSERT/UPDATE paramétrées ne devrait pas provoquer de corruption de données directe : il devrait toujours entrainer la levée d'une exception avec un message du type "The parameterized query '...' expects the parameter '...', which was not supplied." ("La requête paramétrée '...' attend le paramètre '...', qui n'a pas été fourni.").

 

Je pense qu'il est nécessaire de s'assurer que les équipes de développement sont conscientes de ce genre de détail de fond même si elles utilisent habituellement des frameworks qui masquent la plomberie ADO.NET : ça évitera peut-être quelques problèmes le jour où elles devront travailler sans.
La connaissance de ce genre de détails peut s'avérer utile lors de phases de debug sur du code hérité ou tout simplement sur du code mettant en oeuvre des librairies tierces.

 

Illustrons un peu tout ça avec un exemple de ce qu'il se passe dans le cas d'une procédure stockée.

Prenons ce prototype de procédure stockée :

CREATE PROCEDURE dbo.sp_DoSomething
(
@Id int,
@Name nvarchar(1024)
)
AS
BEGIN

PRINT N'Something';

END

Cette méthode qui l'utilise (à noter que j'utilise les définitions de paramètres "à l'ancienne" mais le comportement de AddWithValue est le même) :

public void DoSomething(Int32 id, String name)
{
using (SqlConnection connection = new SqlConnection(this.ConnectionString))
{
using (SqlCommand command = new SqlCommand("sp_DoSomething", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Id", SqlDbType.Int, 4).Value = id;
command.Parameters.Add("@Name", SqlDbType.NVarChar, 1024)
.Value = name;

connection.Open();
command.ExecuteNonQuery();
}
}
}


Et cet appel de la méthode :

instance.DoSomething(
1, // id
null // name
);

Le résultat de l'exécution sera similaire à celui que nous aurions eu en utilisant le code TSQL suivant dans SQL Server Management Studio :

EXEC sp_DoSomething @Id=1, @Name=DEFAULT; 

Alors que nous espérions probablement ceci :

EXEC sp_DoSomething @Id=1, @Name=NULL; 

Notre procédure stockée ne spécifiant pas de valeur par défaut pour le paramètre @Name, cet appel va échouer avec l'erreur citée plus haut.

 

Le risque de corruption de données peut intervenir quand une valeur par défaut est définie pour le paramètre @Name :

CREATE PROCEDURE [dbo].[sp_DoSomething]
(
@Id int,
@Name nvarchar(1024) = N'lorem ipsum'
)
AS
BEGIN

PRINT @Name;

END

Si on utilise le même code côté .NET, il n'y aura pas d'erreur mais un print de "lorem ipsum".
Si le paramètre est utilisé pour être persisté ou pour prendre des décisions, on a utilisé une valeur "lorem ipsum" à la place d'un NULL, avec toutes les conséquences que cela peut avoir.

 

En général le code que nous voulions produire aurait été quelque chose de ce genre :

public void DoSomething(Int32 id, String name)
{
using (SqlConnection connection = new SqlConnection(this.ConnectionString))
{
using (SqlCommand command = new SqlCommand("sp_DoSomething", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Id", SqlDbType.Int, 4).Value = id;
command.Parameters.Add("@Name", SqlDbType.NVarChar, 1024)
.Value = name ?? (Object)DBNull.Value;

connection.Open();
command.ExecuteNonQuery();
}
}
}

 

On pourrait se dire qu'il suffit que les membres de l'équipe soient au courant du fait que l'affectation d'une référence nulle entraine l'utilisation de la valeur par défaut mais dans les faits je pense que ce serait déstabilisant pour beaucoup de personnes, donc dangereux pour les phases de maintenance et lors de l'intégration de nouvelles personnes à l'équipe.
On s'attend en général plus à un parallèle entre null (C#) et NULL (SQL) qu'à ce genre de comportement donc autant établir le fait que null ne doit jamais être affecté explicitement comme valeur de paramètre, et garder uniquement l'absence d'ajout du paramètre comme déclencheur de l'utilisation de la valeur par défaut.
Ce n'est que mon avis et il est largement influencé par le fait que je ne travaille jamais dans des environnements où la définition d'une valeur par défaut pour un paramètre sert à autre chose qu'à éviter de briser du code existant pendant une phase de transition (ou pour des paramètres de déclenchement d'un mode debug/trace verbeuse qui doit être désactivé par défaut) : il existe peut-être des cas où cet avis n'est pas pertinent.

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: samedi 19 novembre 2011 23:52 par coq
Classé sous : ,

Commentaires

Pas de commentaires

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01