Aujourd’hui mon patron m’interpelle concernant un comportement étrange d’une procédure stockée avec Linq to SQL. La procédure stockée est un simple « SELECT field1, COUNT(*) » et pourtant impossible de récupérer un résultat correct.

Histoire de pouvoir vous montrer des exemples concrets, je vais transposer la situation avec l’utilisation de la base de données Northwind.

Comme je vous le disais, la procédure stockée récalcitrante consiste à récupérer la liste des clients avec le nombre de commandes qu’ils ont passé :

CREATE PROCEDURE GetCustomersWithOrderCount
AS
BEGIN
    SELECT Customers.ContactName, COUNT(*)
    FROM Customers INNER JOIN Orders
    ON Customers.CustomerID = Orders.CustomerID
    GROUP BY Customers.ContactName
END

Au niveau du projet, un modèle Linq to SQL Classes auquel on a ajouté la procédure stockée « GetCustomersWithOrderCount ».

Comme on peut le voir dans le code généré, Linq to SQL a déduit la classe correspondant au résultat que la procédure stockée va renvoyer. En l’absence d’alias sur le champ « COUNT(*) », le nom « Column1″ a été donné par défaut.

public partial class GetCustomersWithOrderCountResult
{
    private string _ContactName;
    private System.Nullable<int> _Column1;
    public GetCustomersWithOrderCountResult()
    {
    }
    [Column(Storage= »_ContactName », DbType= »NVarChar(30) »)]
    public string ContactName
    {
        get
        {
            return this._ContactName;
        }
        set
        {
            if ((this._ContactName != value))
            {
                this._ContactName = value;
            }
        }
    }
    [Column(Storage= »_Column1″, DbType= »Int »)]
    public System.Nullable<int> Column1
    {
        get
        {
            return this._Column1;
        }
        set
        {
            if ((this._Column1 != value))
            {
                this._Column1 = value;
            }
        }
    }
}

Tout semble correct et on peut donc écrire le code qui va récupérer le résultat de la procédure stockée que nous avons précédemment créé :

using (ModelDataContext ctx = new ModelDataContext())
{
    foreach (var item in ctx.GetCustomersWithOrderCount().ToList())
    {
        Console.WriteLine(« {0} – {1} », item.ContactName, item.Column1);
    }
}

Pourtant, lorsqu’on lance le programme, on obtient un résultat plus qu’étrange :

On récupère bien nos clients mais par contre aucune valeur pour le nombre de commandes et ce pour tous les clients ! Après une heure de recherche, je trouve enfin la solution via MSDN (une fois de plus ;)).

Ce qui se passe, c’est que Linq to SQL ne sait pas comment mapper le champ « COUNT(*) » de la procédure stockée à la propriété « Column1″ de la classe « GetCustomersWithOrderCountResult » générée automatiquement.

La solution est donc simple : il suffit de donner un alias à notre champ dans la procédure stockée :

ALTER PROCEDURE GetCustomersWithOrderCount
AS
BEGIN
    SELECT Customers.ContactName, COUNT(*) as OrderCount
    FROM Customers INNER JOIN Orders
    ON Customers.CustomerID = Orders.CustomerID
    GROUP BY Customers.ContactName
END

Après une mise à jour du schéma Linq to SQL Classes, on peut remarquer que le changement a été pris en compte dans la classe « GetCustomersWithOrderCountResult ». La propriété « Column1″ a bien été renommée en « OrderCount ».

public partial class GetCustomersWithOrderCountResult
{
    private string _ContactName;
    private System.Nullable<int> _OrderCount;
    public GetCustomersWithOrderCountResult()
    {
    }
    [Column(Storage= »_ContactName », DbType= »NVarChar(30) »)]
    public string ContactName
    {
        get
        {
            return this._ContactName;
        }
        set
        {
            if ((this._ContactName != value))
            {
                this._ContactName = value;
            }
        }
    }
    [Column(Storage= »_OrderCount », DbType= »Int »)]
    public System.Nullable<int> OrderCount
    {
        get
        {
            return this._OrderCount;
        }
        set
        {
            if ((this._OrderCount != value))
            {
                this._OrderCount = value;
            }
        }
    }
}

Et lorsqu’on exécute à nouveau notre code, on a enfin notre résultat tant attendu :

Tout ça pour ça vous allez me dire mais c’est bon de le savoir quand même car au final, je peux vous assurer que l’on a perdu beaucoup de temps avant de comprendre ce qui n’allait pas.