Linq-to-Sql: Узнаем nullable поля из метаданных (или рассказ о небольшом баге)
- 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 - это единственные неприятности которые были в этой схеме. ;)