Pull to refresh

Linq-to-Sql: Узнаем nullable поля из метаданных (или рассказ о небольшом баге)

Reading time 3 min
Views 1.4K
Итак, перед нами Linq-to-Sql. Перед нами стоит задача узнать какие поля могут иметь значения null, а какие нет — решение данной задачи может, например, помогать в подсветки обязательных полей на форме, либо просто для валидации данных, перед их установкой в свойства объекта.
Шаг первый — создаем просту таблицу следующего вида:

Шаг второй — создаем проект (для примера хватит и Console Application). Добавляем в него Model Linq-To-Sql, на которую добавляем маппинг на таблицу LinqBugTable, обращаю внимание, что Nullable property у price выставлено в False, так и должно быть, ведь в базе данное поле not nullable:

Не буду пока показывать код для property price, а покажу для number:
[Column(Storage="_number", DbType="VarChar(50) NOT NULL", CanBeNull=false)]
public string number
{ get { .... } set { .... } }


* This source code was highlighted with Source Code Highlighter.
Отсюда видно, что из ColumnAttribute данного property мы и сможем узнать о том, когда наше поле может иметь пустое значение. Создаем partial класс для LinqBugTable, который и будет нам выводить информацию о его свойствах:
partial class LinqBugTable
{
  public void WriteNullableInfo()
  {
    foreach (PropertyInfo property in GetType().GetProperties())
    {
      foreach (ColumnAttribute columnAttribute in property.GetCustomAttributes(typeof(ColumnAttribute), true))
      {
        Console.WriteLine("Property: '{0}', CanBeNull: '{1}'", property.Name, columnAttribute.CanBeNull);
      }
    }
  }
}


* This source code was highlighted with Source Code Highlighter.
Вроде все готово, но результат будет следующий:

Property: 'id', CanBeNull: 'True'
Property: 'price', CanBeNull: 'True'
Property: 'number', CanBeNull: 'False'


Как видите для id и price у нас возвращаются True, хотя это, само собой, не так. Посмотрим теперь на описание свойства price в классе:
[Column(Storage="_price", DbType="Money NOT NULL")]
public decimal price
{ get { .... } set { .... } }


* This source code was highlighted with Source Code Highlighter.
Как видим у атрибута Column свойство CanBeNull не выставлено, но, вообще bool переменные определяются значение false, так что, может, это и не страшно (так я подумал сначала). Хорошо, что существует прекрастная программа Reflector, с помощью которой можно посмотреть, что происходит в коде ColumnAttribute:

Видим, что в конструкторе они инициализируют field значением true. Вообще у ColumnAttribute есть еще свойство CanBeNullSet, которое к несчастью internal, при помощи которого можно было бы узнавать выставлено ли свойство или нет.
Поразмыслив, я понял логику: свойство price возвращает тип decimal, который просто не может иметь значение null, а если бы я выставил свойство Nullable=True в Model Designer у данного поля, тогда оно имело бы тип Nullable<decimal>. В общем для полей с типами ValueType (структуры) Linq-To-Sql не генерирует описание, связанное с CanBeNull. Итог, переписываем немного наш метод:
public void WriteNullableInfo()
{
  foreach (PropertyInfo property in GetType().GetProperties())
  {
    foreach (ColumnAttribute columnAttribute in property.GetCustomAttributes(typeof(ColumnAttribute), true))
    {
      bool canBeNull = (Nullable.GetUnderlyingType(property.PropertyType) != null)
        || (columnAttribute.CanBeNull && !property.PropertyType.IsValueType);
      Console.WriteLine("Property: '{0}', CanBeNull: '{1}'", property.Name, canBeNull);
    }
  }
}


* This source code was highlighted with Source Code Highlighter.
Теперь мы проверяем, что если свойство у нас типа Nullable<T>, то возвращаем True, в противном случае возвратим значение CanBeNull с учетом того, что тип поля не struct. Ну и надеемся, что с типами свойств ValueType — это единственные неприятности которые были в этой схеме. ;)
Tags:
Hubs:
+10
Comments 7
Comments Comments 7

Articles