Во-первых, позвольте мне объяснить текущую ситуацию: я читаю записи из базы данных и помещаю их в объект для более позднего использования; сегодня вопрос о типе БД к преобразованию типов 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);
}
}
}
Таким образом, я должен обработать каждый случай.
Вопрос: я пропускаю что-то? Я делаю ВОМБАТА (Пустая трата Денег, Мозга И Время), поскольку существует более быстрый и более чистый способ сделать это? Это все корректно? Прибыль?
Если поле позволяет NULL, не используйте обычные примитивные типы. Используйте C # Nullable
Тип и в виде
ключевое слово .
int? field_a = reader["field_a"] as int?;
string field_b = reader["field_a"] as string;
Добавление ?
на любой ненутренний тип C # делает его «Nullable». Использование по ключевому слову
будет пытаться отбросить объект к указанному типу. Если толкание не удается (например, если тип DBNULL
), то оператор возвращает NULL
.
Примечание: Другая небольшая выгода от использования как
заключается в том, что он немного быстрее , чем обычное литье. Поскольку он также может иметь некоторые недостатки, такие как сложнее отслеживать ошибки, если вы попытаетесь лить в качестве неправильного типа, это не следует рассматривать причину всегда использовать как
по сравнению с традиционным литьем. Регулярное литье уже довольно дешевая операция.
Разве вы не хотите использовать 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
}
Вот как я сталкивался с этим в прошлом:
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"));
Можно сделать набор методов расширения, по одной паре на тип данных:
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), чтобы эффективно выполнять собственно преобразование типов. Я не уверен в правомерности распространения этого заимствованного класса, поэтому, наверное, не стоит делиться кодом, но искать его самостоятельно довольно легко.
Учебный код ганлдии, размещенного здесь, - это круто, но поскольку на заголовок вопроса включает слово «эффективно», я опубликую мой менее общий, но (надеюсь) более эффективный ответ.
Я предлагаю вам использовать методы 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);
}