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

    • .NET
    • C#
    • Linq-To-SQL
  • modified:
  • reading: 3 minutes

Итак, перед нами 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 { .... } }

Отсюда видно, что из 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);
      }
    }
  }
}

Вроде все готово, но результат будет следующий:

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 { .... } }

Как видим у атрибута 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);
    }
  }
}

Теперь мы проверяем, что если свойство у нас типа Nullable<T>, то возвращаем True, в противном случае возвратим значение CanBeNull с учетом того, что тип поля не struct. Ну и надеемся, что с типами свойств ValueType - это единственные неприятности которые были в этой схеме. ;)

See Also