Сопоставление ADO.NET от SQLDataReader к объекту домена?

У меня есть очень простая функция отображения, называемая "BuildEntity", которая выполняет обычное скучное кодирование "влево / вправо", необходимое для выгрузки данных читателя в мой объект домена. (показано ниже). Мой вопрос заключается в следующем: если я не возвращаю все столбцы в этом отображении как есть, я получаю исключение "System.IndexOutOfRangeException" и хочу знать, есть ли в ado.net что-нибудь, чтобы исправить это, поэтому я не нужно возвращать каждый столбец с каждым вызовом в SQL ...

Что я действительно ищу, так это что-то вроде "IsValidColumn", так что я могу сохранить эту функцию отображения 1 в своем классе DataAccess со всеми определенными левыми / правыми отображениями - и заставить ее работать, даже если sproc не возвращает все перечисленные столбцы...

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

8 ответов

Решение

Хотя connection.GetSchema("Таблицы") действительно возвращает метаданные о таблицах в вашей базе данных, она не вернет все в вашем sproc, если вы определите какие-либо пользовательские столбцы.

Например, если вы добавите какой-то случайный специальный столбец, такой как *SELECT ProductName, "Testing" As ProductTestName FROM dbo.Products", вы не увидите" ProductTestName "в качестве столбца, поскольку его нет в таблице" Схема продуктов ". Чтобы решить эту проблему и запросить каждый столбец, доступный в возвращаемых данных, используйте метод в объекте SqlDataReader "GetSchemaTable()"

Если я добавлю это к существующему примеру кода, который вы перечислили в исходном вопросе, вы заметите, что сразу после объявления читателя я добавляю таблицу данных, чтобы получить метаданные самого читателя. Затем я перебираю эти метаданные и добавляю каждый столбец в другую таблицу, которую я использую в лево-правом коде, чтобы проверить, существует ли каждый столбец.

Обновленный исходный код

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

Честно говоря, это хак, так как вы должны возвращать каждый столбец, чтобы правильно увлажнять ваш объект. Но я подумал включить этот метод чтения, так как он на самом деле захватывает все столбцы, даже если они не определены в вашей схеме таблицы.

Такой подход к отображению ваших реляционных данных в модель вашего домена может вызвать некоторые проблемы, когда вы попадаете в сценарий отложенной загрузки.

Также проверьте этот метод расширения, который я написал для использования в командах данных:

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

Вы можете использовать это так:

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

Где "products" - это IList, который вы хотите заполнить, а "GetProduct" содержит логику для создания экземпляра Product из устройства чтения данных. Это не поможет с этой конкретной проблемой отсутствия всех полей, но если вы делаете много старомодного ADO.NET, как это, это может быть очень удобно.

Использовать GetSchemaTable() метод для получения метаданных DataReader, DataTable возвращаемое значение можно использовать для проверки наличия или отсутствия определенного столбца.

Почему бы не сделать так, чтобы каждый sproc возвращал полный набор столбцов, используя ноль, -1 или допустимые значения, если у вас нет данных. Избегает необходимости перехватывать IndexOutOfRangeException или переписывать все в LinqToSql.

Если вы не хотите использовать ORM, вы также можете использовать отражение для подобных вещей (хотя в этом случае, поскольку ProductID не назван одинаково с обеих сторон, вы не можете сделать это в упрощенном виде, продемонстрированном здесь): Список Провайдер в C#

Я закончил писать свой собственный, но этот картограф довольно хорош (и прост): https://code.google.com/p/dapper-dot-net/

Я бы позвонил reader.GetOrdinal для каждого имени поля перед началом цикла while. к несчастью GetOrdinal бросает IndexOutOfRangeException если поле не существует, то оно не будет очень производительным.

Вы могли бы, вероятно, сохранить результаты в Dictionary<string, int> и использовать его ContainsKey метод, чтобы определить, если поле было предоставлено.

Почему бы вам не использовать LinqToSql - все, что вам нужно, делается автоматически. Ради общего понимания вы можете использовать любой другой инструмент ORM для.NET

Другие вопросы по тегам