2013-12-06 6 views
2

Как проверить нулевые значения db в прилагаемом коде? Пожалуйста, поймите, что я новый конвертер C# ...Проверка нулей на картографировании записей DB

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

конвертер

internal class Converter<T> where T : new() 
{ 
    // Declare our _converter delegate 
    readonly Func<IDataReader, T> _converter; 
    // Declare our internal dataReader 
    readonly IDataReader dataReader; 

    // Build our mapping based on the properties in the class/type we've passed in to the class 
    private Func<IDataReader, T> GetMapFunc() 
    { 
     // declare our field count 
     int _fc = dataReader.FieldCount; 
     // declare our expression list 
     List<Expression> exps = new List<Expression>(); 
     // build our parameters for the expression tree 
     ParameterExpression paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); 
     ParameterExpression targetExp = Expression.Variable(typeof(T)); 
     // Add our expression tree assignment to the exp list 
     exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type))); 
     //does int based lookup 
     PropertyInfo indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) }); 
     // grab a collection of column names from our data reader 
     var columnNames = Enumerable.Range(0, _fc).Select(i => new { i, name = dataReader.GetName(i) }).AsParallel(); 
     // loop through all our columns and map them properly 
     foreach (var column in columnNames) 
     { 
      // grab our column property 
      PropertyInfo property = targetExp.Type.GetProperty(column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); 
      // check if it's null or not 
      if (property != null) 
      { 
       // build our expression tree to map the column to the T 
       ConstantExpression columnNameExp = Expression.Constant(column.i); 
       IndexExpression propertyExp = Expression.MakeIndex(paramExp, indexerInfo, new[] { columnNameExp }); 
       UnaryExpression convertExp = Expression.Convert(propertyExp, property.PropertyType); 
       BinaryExpression bindExp = Expression.Assign(Expression.Property(targetExp, property), convertExp); 
       // add it to our expression list 
       exps.Add(bindExp); 
      } 
     } 
     // add the originating map to our expression list 
     exps.Add(targetExp); 
     // return a compiled cached map 
     return Expression.Lambda<Func<IDataReader, T>>(Expression.Block(new[] { targetExp }, exps), paramExp).Compile(); 
    } 

    // initialize 
    internal Converter(IDataReader dataReader) 
    { 
     // initialize the internal datareader 
     this.dataReader = dataReader; 
     // build our map 
     _converter = GetMapFunc(); 
    } 

    // create and map each column to it's respective object 
    internal T CreateItemFromRow() 
    { 
     return _converter(dataReader); 
    } 
} 

Mapper

private static IList<T> Map<T>(DbDataReader dr) where T : new() 
    { 
     try 
     { 
      // initialize our returnable list 
      List<T> list = new List<T>(); 
      // fire up the lamda mapping 
      var converter = new Converter<T>(dr); 
      while (dr.Read()) 
      { 
       // read in each row, and properly map it to our T object 
       var obj = converter.CreateItemFromRow(); 
       // add it to our list 
       list.Add(obj); 
      } 
      // reutrn it 
      return list; 
     } 
     catch (Exception ex) 
     { 
      // make sure this method returns a default List 
      return default(List<T>); 
     } 
    } 

Я просто не совсем понимаю, где столбец напечатал объект происходит здесь, так что я бы попытаться сделать это сам ... но Я просто не знаю, где это.

Я знаю, что это, вероятно, не поможет, но ошибка я получаю:

Unable to cast object of type 'System.DBNull' to type 'System.String'.

, и это происходит на

internal T CreateItemFromRow() 
    { 
     return _converter(dataReader); //<-- Here 
    } 

Примечание

Это делает не произойдет, если я обернут столбцы в самом запросе ISNULL (столбец, ''), но я уверен, что вы можете понять, что это, безусловно, не решение

+1

Sure. Легко обойти для ссылочных типов. Но как это делается для типов значений? Как следует обрабатывать DbNulls? Может быть, вы хотите, чтобы значение по умолчанию возвращалось по типу, но как вы можете быть уверены, что так оно и должно быть обработано каждый раз? Что делать, если вы не хотите значения по умолчанию, а что-то еще? В таких случаях мы можем передать определенные правила, нужно ли нам добавить условие для перегрузки или что-то еще? – nawfal

+1

Ну ... Я думаю, если бы я понял это лучше, я, вероятно, мог бы найти лучший способ справиться с ними, но теперь я думаю, что «значение по умолчанию для типа» было бы лучшим способом с этим ... на по крайней мере, это мои мысли об этом на данный момент :) Как я уже сказал, как только я смогу это понять, я всегда мог бы построить расширение для этого, чтобы обработать потенциал для указанных значений 'default' ... но я не знаю, где посмотрите LOL – Kevin

+1

справедливая точка! дайте мне некоторое время, я добавлю ответ в течение дня или около того. Просто слишком занят работой на данный момент :) Проблема здесь: 'convertExp = Expression.Convert (propertyExp, property.PropertyType)'. Вы не можете ожидать преобразования DbNull в типы фреймов. Перед этим вам нужна функция GetValueOrDefault, чтобы проверить значение. – nawfal

ответ

2

Проблема лежит в строке convertExp = Expression.Convert(propertyExp, property.PropertyType). Вы не можете рассчитывать преобразовать значение DbNull в его эквивалент в виде рамки. Это особенно неприятно, когда ваш тип является типом значений. Один из вариантов - проверить, является ли значение чтения из db DbNull.Value, и в случае да, вам нужно найти совместимое значение самостоятельно. В некоторые случаях люди в порядке со значениями по умолчанию этих типов в C#.Если вам нужно сделать, это

property = value == DBNull.Value ? default(T): value; 

родовой реализация будет выглядеть (насколько foreach в классе преобразователя идет):

foreach (var column in columns) 
{ 
    var property = targetExp.Type.GetProperty(
     column.name, 
     BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); 
    if (property == null) 
     continue; 

    var columnIndexExp = Expression.Constant(column.i); 
    var propertyExp = Expression.MakeIndex(
     paramExp, indexerInfo, new[] { columnIndexExp }); 
    var convertExp = Expression.Condition(
     Expression.Equal(
      propertyExp, 
      Expression.Constant(DBNull.Value)), 
     Expression.Default(property.PropertyType), 
     Expression.Convert(propertyExp, property.PropertyType)); 
    var bindExp = Expression.Assign(
     Expression.Property(targetExp, property), convertExp); 
    exps.Add(bindExp); 
} 

Теперь это делает эквивалент

property = reader[index] == DBNull.Value ? default(T): reader[index]; 

Вы можете избежать двойного поиска читателя, назначив его переменной и используя ее значение в условной проверке. Таким образом, это должно быть немного лучше, но лил»сложнее:

foreach (var column in columns) 
{ 
    var property = targetExp.Type.GetProperty(
     column.name, 
     BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); 
    if (property == null) 
     continue; 

    var columnIndexExp = Expression.Constant(column.i); 
    var cellExp = Expression.MakeIndex(
     paramExp, indexerInfo, new[] { columnIndexExp }); 
    var cellValueExp = Expression.Variable(typeof(object), "o7thPropValue"); 
    var convertExp = Expression.Condition(
     Expression.Equal(
      cellValueExp, 
      Expression.Constant(DBNull.Value)), 
     Expression.Default(property.PropertyType), 
     Expression.Convert(cellValueExp, property.PropertyType)); 
    var cellValueReadExp = Expression.Block(new[] { cellValueExp }, 
     Expression.Assign(cellValueExp, cellExp), convertExp); 
    var bindExp = Expression.Assign(
     Expression.Property(targetExp, property), cellValueReadExp); 
    exps.Add(bindExp); 
} 

Это делает проверку условий таким образом:

value = reader[index]; 
property = value == DBNull.Value ? default(T): value; 
+1

Предостережение: выражение 'Convert' просто ставит оператор литья, чтобы придать тип boxed указанному типу (т. Е. Нашему типу свойств). Он взорвется, если приведение не соответствует его оригинальному типу. Например, если базовый тип db - 'byte', а тип свойства -' int', сбой выполняется. В таких случаях следует использовать более мощные методы преобразования, такие как методы в классе System.Convert (что будет немного медленнее, конечно). Это проблема из-за несоответствия импеданса между типами db и типами классов. Но если программисты могут гарантировать, что это должно работать нормально. – nawfal

+1

Кроме этого, я не тестировал это широко, возможно, я что-то пропустил. Я обновлю его, как только будет указана некоторая ошибка. – nawfal

+1

Я еще не тестировал, но мой первоначальный тест показал, что он действительно работал так, как я ожидал. Я не уверен, насколько я заинтересован в том, что вы упомянули выше. Я ожидаю, что любой другой разработчик, используя это, правильно напечатает свои свойства :) – Kevin

2

Это одна из самых неприятных проблем при работе с наборами данных в целом.

Способ, которым я обычно обходится, заключается в том, чтобы преобразовать значение DBNull в нечто более полезное, например, в случае нулевой или даже пустой строки. Это можно сделать несколькими способами, но совсем недавно я использовал методы расширения.

public static T? GetValueOrNull<T>(this object value) where T : struct 
     { 
      return value == null || value == DBNull.Value ? (T?) null : (T) Convert.ChangeType(value, typeof (T)); 
     } 

Удобный метод расширения для обнуляемых типов, так, например:

int? myInt = DataSet.Tables[0].Rows[0]["DBNullInt"].GetValueOrNull<int>(); 

Или более общий один просто преобразовать DBNull, чтобы нуль:

public static object GetValueOrNull(this object value) 
     { 
      return value == DBNull.Value ? null : value; 
     } 

string myString DataSet.Tables[0].Rows[0]["DBNullString"].GetValueOrNull(); 

You» ll затем получите пустую строку, вместо того, чтобы ставить в строку строку DBNull.

Надеюсь, это может вам помочь.

+0

Спасибо за редактирование. Думаю, я получил это сразу после вас, и это не позволило мне обновить его. :) –

+0

Без проблем .. Хороший ответ !! –

+1

Это определенно хорошая идея, и при нормальных условиях сопоставления я бы определенно делал это так, но в этом случае, когда это общее создание карты, это делает ее немного сложнее ... Наверное, мне действительно нужно следует указать, где происходят полевые данные -> сильный тип, я, вероятно, могу это понять ... Я просто не знаю, где это происходит из-за отсутствия понимания C# LOL – Kevin

 Смежные вопросы

  • Нет связанных вопросов^_^