Как (эффективно) преобразовать (бросок?) поле SqlDataReader к его соответствующему типу c#?

Во-первых, позвольте мне объяснить текущую ситуацию: я читаю записи из базы данных и помещаю их в объект для более позднего использования; сегодня вопрос о типе БД к преобразованию типов C# (бросающий?) возник.

Давайте посмотрим пример:

namespace Test
{
    using System;
    using System.Data;
    using System.Data.SqlClient;

    public enum MyEnum
    {
        FirstValue = 1,
        SecondValue = 2
    }

    public class MyObject
    {
        private String field_a;
        private Byte field_b;
        private MyEnum field_c;

        public MyObject(Int32 object_id)
        {
            using (SqlConnection connection = new SqlConnection("connection_string"))
            {
                connection.Open();

                using (SqlCommand command = connection.CreateCommand())
                {
                    command.CommandText = "sql_query";

                    using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
                    {
                        reader.Read();

                        this.field_a = reader["field_a"];
                        this.field_b = reader["field_b"];
                        this.field_c = reader["field_c"];
                    }
                }
            }
        }
    }
}

Это (очевидно), перестало работать потому что три this.field_x = reader["field_x"]; вызовы бросают Cannot implicitly convert type 'object' to 'xxx'. An explicit conversion exists (are you missing a cast?). ошибка компилятора.

Для исправления этого, я в настоящее время знаю о двух путях (давайте использовать field_b пример): номер один this.field_b = (Byte) reader["field_b"]; и номер два this.field_b = Convert.ToByte(reader["field_b"]);.

Проблема с опцией номер один - это DBNull поля выдают исключения, поскольку состав исполнителей перестал работать (даже с nullable типами как String), муравей, которым проблема с номером два состоит в том, что он не сохраняет нулевые значения ( Convert.ToString(DBNull) урожаи a String.Empty), и я не могу использовать их с перечислениями также.

Так, после нескольких поисков в Интернете и здесь в StackOverflow, что я придумал:

public static class Utilities
{
    public static T FromDatabase<T>(Object value) where T: IConvertible
    {
        if (typeof(T).IsEnum == false)
        {
            if (value == null || Convert.IsDBNull(value) == true)
            {
                return default(T);
            }
            else
            {
                return (T) Convert.ChangeType(value, typeof(T));
            }
        }
        else
        {
            if (Enum.IsDefined(typeof(T), value) == false)
            {
                throw new ArgumentOutOfRangeException();
            }

            return (T) Enum.ToObject(typeof(T), value);
        }
    }
}

Таким образом, я должен обработать каждый случай.

Вопрос: я пропускаю что-то? Я делаю ВОМБАТА (Пустая трата Денег, Мозга И Время), поскольку существует более быстрый и более чистый способ сделать это? Это все корректно? Прибыль?

29
задан Kiquenet 13 April 2015 в 12:01
поделиться

5 ответов

Если поле позволяет NULL, не используйте обычные примитивные типы. Используйте C # Nullable Тип и в виде ключевое слово .

int? field_a = reader["field_a"] as int?;
string field_b = reader["field_a"] as string;

Добавление ? на любой ненутренний тип C # делает его «Nullable». Использование по ключевому слову будет пытаться отбросить объект к указанному типу. Если толкание не удается (например, если тип DBNULL ), то оператор возвращает NULL .

Примечание: Другая небольшая выгода от использования как заключается в том, что он немного быстрее , чем обычное литье. Поскольку он также может иметь некоторые недостатки, такие как сложнее отслеживать ошибки, если вы попытаетесь лить в качестве неправильного типа, это не следует рассматривать причину всегда использовать как по сравнению с традиционным литьем. Регулярное литье уже довольно дешевая операция.

38
ответ дан 28 November 2019 в 01:18
поделиться

Разве вы не хотите использовать Reader.get * Методы ? Единственная раздражающая вещь заключается в том, что они принимают номера столбцов, поэтому вы должны обернуть аксес в вызове Getordinal ()

using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
{
    reader.Read();

    this.field_a = reader.GetString(reader.GetOrdinal("field_a"));
    this.field_a = reader.GetDouble(reader.GetOrdinal("field_b"));
    //etc
}
12
ответ дан 28 November 2019 в 01:18
поделиться

Вот как я сталкивался с этим в прошлом:

    public Nullable<T> GetNullableField<T>(this SqlDataReader reader, Int32 ordinal) where T : struct
    {
        var item = reader[ordinal];

        if (item == null)
        {
            return null;
        }

        if (item == DBNull.Value)
        {
            return null;
        }

        try
        {
            return (T)item;
        }
        catch (InvalidCastException ice)
        {
            throw new InvalidCastException("Data type of Database field does not match the IndexEntry type.", ice);
        }
    }

Использование:

int? myInt = reader.GetNullableField<int>(reader.GetOrdinal("myIntField"));
6
ответ дан 28 November 2019 в 01:18
поделиться

Можно сделать набор методов расширения, по одной паре на тип данных:

    public static int? GetNullableInt32(this IDataRecord dr, string fieldName)
    {
        return GetNullableInt32(dr, dr.GetOrdinal(fieldName));
    }

    public static int? GetNullableInt32(this IDataRecord dr, int ordinal)
    {
        return dr.IsDBNull(ordinal) ? null : (int?)dr.GetInt32(ordinal);
    }

Это немного утомительно, но довольно эффективно. В System.Data.DataSetExtensions.dll компания Microsoft решила такую же проблему для DataSet с помощью Field метода, который, как правило, обрабатывает несколько типов данных и может превратить DBNull в Nullable.

В качестве эксперимента я однажды реализовал эквивалентный метод для DataReaders, но в итоге я использовал Reflector для заимствования внутреннего класса из DataSetExtensions (UnboxT), чтобы эффективно выполнять собственно преобразование типов. Я не уверен в правомерности распространения этого заимствованного класса, поэтому, наверное, не стоит делиться кодом, но искать его самостоятельно довольно легко.

4
ответ дан 28 November 2019 в 01:18
поделиться

Учебный код ганлдии, размещенного здесь, - это круто, но поскольку на заголовок вопроса включает слово «эффективно», я опубликую мой менее общий, но (надеюсь) более эффективный ответ.

Я предлагаю вам использовать методы getXxx, которые упомянули другие. Чтобы справиться с проблемой номер столбца, о том, что Bubop говорит о том, что я использую Enum, как это:

enum ReaderFields { Id, Name, PhoneNumber, ... }
int id = sqlDataReader.getInt32((int)readerFields.Id)

Это немного дополнительной печати, но тогда вам не нужно звонить Getordinal, чтобы найти индекс для каждого столбца. И вместо того, чтобы беспокоиться о именах столбцов, вы беспокоитесь о позициях столбцов.

Чтобы справиться с Nullable Columns, вам нужно проверить DBNull, и, возможно, предоставить значение по умолчанию:

string phoneNumber;
if (Convert.IsDBNull(sqlDataReader[(int)readerFields.PhoneNumber]) {
  phoneNumber = string.Empty;
}
else {
  phoneNumber = sqlDataReader.getString((int)readerFields.PhoneNumber);
}
2
ответ дан 28 November 2019 в 01:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: