Question

J'ai une fonction de mappage très simple appelée "BuildEntity" qui effectue le codage ennuyeux "gauche/droite" habituel requis pour vider les données de mon lecteur dans mon objet de domaine.(illustré ci-dessous) Ma question est la suivante : si je ne ramène pas chaque colonne de ce mappage telle quelle, j'obtiens l'exception "System.IndexOutOfRangeException" et je voulais savoir si ado.net avait quelque chose pour corriger cela, donc je ne le fais pas. Je n'ai pas besoin de ramener chaque colonne à chaque appel dans SQL...

Ce que je recherche vraiment, c'est quelque chose comme "IsValidColumn" afin que je puisse conserver cette fonction de mappage dans toute ma classe DataAccess avec tous les mappages gauche/droite définis - et le faire fonctionner même lorsqu'un sproc ne renvoie pas toutes les colonnes répertoriées. ..

Using reader As SqlDataReader = cmd.ExecuteReader()
  Dim product As Product
  While reader.Read()
    product = New Product()
    product.ID = Convert.ToInt32(reader("ProductID"))
    product.SupplierID = Convert.ToInt32(reader("SupplierID"))
    product.CategoryID = Convert.ToInt32(reader("CategoryID"))
    product.ProductName = Convert.ToString(reader("ProductName"))
    product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
    product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
    product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
    product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
    product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
    productList.Add(product)
  End While
Était-ce utile?

La solution

Bien que connection.GetSchema("Tables") renvoie des métadonnées sur les tables de votre base de données, il ne renverra pas tout dans votre sproc si vous définissez des colonnes personnalisées.

Par exemple, si vous ajoutez une colonne ad hoc aléatoire telle que *SELECT ProductName, 'Testing' As ProductTestName FROM dbo.Products", vous ne verrez pas 'ProductTestName' comme colonne car elle ne figure pas dans le schéma de la table Produits.Pour résoudre ce problème et demander chaque colonne disponible dans les données renvoyées, utilisez une méthode sur l'objet SqlDataReader "GetSchemaTable()"

Si j'ajoute ceci à l'exemple de code existant que vous avez répertorié dans votre question d'origine, vous remarquerez que juste après la déclaration du lecteur, j'ajoute une table de données pour capturer les métadonnées du lecteur lui-même.Ensuite, je parcoure ces métadonnées et j'ajoute chaque colonne à une autre table que j'utilise dans le code gauche-droite pour vérifier si chaque colonne existe.

Code source mis à jour

Using reader As SqlDataReader = cmd.ExecuteReader() 
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
 colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product  While reader.Read()    
product = New Product()  
If Not colNames.Columns("ProductID") Is Nothing Then
  product.ID = Convert.ToInt32(reader("ProductID"))
End If    
product.SupplierID = Convert.ToInt32(reader("SupplierID"))    
product.CategoryID = Convert.ToInt32(reader("CategoryID"))    
product.ProductName = Convert.ToString(reader("ProductName"))    
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))    
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))    
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))    
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))    
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))    
productList.Add(product)  
End While

Pour être honnête, c'est un hack, comme vous devrait renvoyez chaque colonne pour hydrater correctement votre objet.Mais j'ai pensé à inclure cette méthode de lecture car elle récupérerait toutes les colonnes, même si elles ne sont pas définies dans votre schéma de table.

Cette approche consistant à mapper vos données relationnelles dans votre modèle de domaine peut entraîner des problèmes lorsque vous vous retrouvez dans un scénario de chargement paresseux.

Autres conseils

Regarde aussi ceci méthode d'extension que j'ai écrite à utiliser sur les commandes de données :

public static void Fill<T>(this IDbCommand cmd,
    IList<T> list, Func<IDataReader, T> rowConverter)
{
    using (var rdr = cmd.ExecuteReader())
    {
        while (rdr.Read())
        {
            list.Add(rowConverter(rdr));
        }
    }
}

Vous pouvez l'utiliser comme ceci :

cmd.Fill(products, r => r.GetProduct());

Où « produits » est le IList<Product> que vous souhaitez remplir et « GetProduct » contient la logique permettant de créer une instance de produit à partir d'un lecteur de données.Cela ne résoudra pas ce problème spécifique de ne pas avoir tous les champs présents, mais si vous faites beaucoup d'ADO.NET à l'ancienne comme celui-ci, cela peut être très pratique.

Pourquoi ne pas simplement demander à chaque sproc de renvoyer un ensemble complet de colonnes, en utilisant null, -1 ou des valeurs acceptables là où vous n'avez pas les données.Évite d'avoir à intercepter IndexOutOfRangeException ou à tout réécrire dans LinqToSql.

Utilisez le GetSchemaTable() méthode pour récupérer les métadonnées du DataReader.Le DataTable qui est renvoyé peut être utilisé pour vérifier si une colonne spécifique est présente ou non.

Pourquoi n'utilisez-vous pas LinqToSql - tout ce dont vous avez besoin est fait automatiquement.Par souci de généralité, vous pouvez utiliser n'importe quel autre Outil ORM pour .NET

Si vous ne souhaitez pas utiliser un ORM, vous pouvez également utiliser la réflexion pour des choses comme celle-ci (bien que dans ce cas, comme ProductID n'est pas nommé de la même manière des deux côtés, vous ne pouvez pas le faire de la manière simpliste démontrée ici) :Fournisseur de liste en C#

J'appellerais reader.GetOrdinal pour chaque nom de champ avant de démarrer la boucle while.Malheureusement GetOrdinal jette un IndexOutOfRangeException si le champ n'existe pas, il ne sera donc pas très performant.

Vous pourriez probablement stocker les résultats dans un Dictionary<string, int> et utiliser son ContainsKey méthode pour déterminer si le champ a été fourni.

J'ai fini par écrire le mien, mais ce mappeur est plutôt bon (et simple) : https://code.google.com/p/dapper-dot-net/

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top