Подтвердить что ты не робот

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

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

Посмотрим на пример:

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"]);.

Проблема с номером 1-го варианта заключается в том, что поля DBNull бросают исключения, когда сбрасывается сбой (даже с нулевыми типами как String), ant проблема с номером два заключается в том, что она не сохраняет нулевые значения (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);
        }
    }
}

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

Вопрос: Я что-то упустил? Я делаю WOMBAT ( "Отходы денег, мозг и время" ), так как есть более быстрый и чистый способ сделать это? Все правильно? Прибыль?

4b9b3361

Ответ 1

Если поле допускает null, не используйте обычные примитивные типы. Используйте тип С# nullable и as ключевое слово.

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

Добавление ? к любому непустому числу С# делает его "nullable". Использование ключевого слова as будет пытаться применить объект к указанному типу. Если приведение не выполняется (например, если тип DBNull), то оператор возвращает null.

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

Ответ 2

Разве вы не хотите использовать методы 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
}

Ответ 3

Вот как я общался с ним в прошлом:

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

Ответ 4

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

    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 решила ту же проблему для DataSets с помощью метода Field<T>, который в целом обрабатывает несколько типов данных, и может превратить DBNull в Nullable.

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

Ответ 5

Общий код hanlding, размещенный здесь, классный, но поскольку название вопроса включает слово "эффективно", я опубликую свой более общий, но (надеюсь) более эффективный ответ.

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

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

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

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

string phoneNumber;
if (Convert.IsDBNull(sqlDataReader[(int)readerFields.PhoneNumber]) {
  phoneNumber = string.Empty;
}
else {
  phoneNumber = sqlDataReader.getString((int)readerFields.PhoneNumber);
}