В базе данных есть столбцы, которые можно обнулять, и мне сказали, что можно с уверенностью предположить, что в них никогда не будет нулевых данных. Код структуры сущности был смоделирован в соответствии с этим предположением. Однако я обнаружил, что некоторые базы данных клиентов в этих столбцах иногда имеют нулевое значение. Entity Framework продолжает разрушаться при использовании SqlReader для чтения этих столбцов, например, вызывая getDateTime со значением NULL.
Схема базы данных была разработана давно, и в настоящее время не может быть обновлена. Короче говоря, столбцы, которые не должны быть нулевыми, иногда бывают нулевыми. Программное обеспечение обязывает эти столбцы хранить соответствующие значения, а не базу данных.
Наш код Entity Framework был смоделирован при условии, что программное обеспечение правильно выполнило свою работу, и что эти столбцы, которые не должны были быть нулевыми, просто не были нулевыми.
В ходе тестирования я обнаружил две клиентские базы данных, которые каким-то образом имеют нулевые значения в столбцах, которых не должно быть, и структура сущностей взорвалась внутренне при попытке разобрать ошибки. Это исторические базы данных, которые все еще загружаются, но, вероятно, были вызваны тем, что программное обеспечение было более глючным 10+ лет назад. Все эти ошибки синтаксического анализа являются ошибками SqlReader при невозможности вызова getDateTime для нулевого значения для объекта DateTime, который не может иметь значение NULL, например.
Я могу использовать метод проб и ошибок там, где оно взорвется, и вручную изменить каждое свойство на обнуляемое (а затем изменить код, чтобы теперь обрабатывать вероятность его обнуления), однако я не хочу делать каждое свойство обнуляемым. Каждое нулевое значение, которое я видел до сих пор, могло быть установлено по умолчанию на соответствующее значение.
Я попытался изменить рефакторинг свойства на «PropertySafe» (чтобы не нарушать существующий код), затем создал новое «Свойство», которое было подключено, и разрешил получателю «PropertySafe» вызвать свойство и вернуть соответствующее значение по умолчанию, равное нулю. , Это лучше, так как остальная часть кода не должна знать об этом недостатке схемы базы данных, однако это громоздко делать снова и снова, и я нахожу это немного уродливым. Я также боюсь, что некоторые запросы EFContext, которые захватывают одно из этих «безопасных» свойств, могут запутать EF и заставить его запускать их на стороне клиента, что будет гораздо менее производительным.
Я нырнул в RelationalTypeMapping и ValueConverter, подумав, что, возможно, я мог бы обработать переход от столбцов sql "datetime not null" к свойству CLR DateTime, по умолчанию, когда мне нужно, однако это не работает, потому что база данных уже была проанализирована в таком случае. Мне нужно было бы переопределить, где бы он ни вызывал getDateTime в SqlReader, и перехватить его там.
Пример сбоя сущности / свойства таблицы / столбца практически такой же, как в следующем примере.
public class Entity {
public int Id { get; set; }
public DateTime DueDate { get; set; }
public class Configuration : BaseConfiguation<Entity> {
protected override string TableName => "ENTITY_DUE_DATE";
protected override void DoConfigure( EntityTypeBuilder<Entity> builder ) {
builder.HasKey( x => new { x.Id } );
builder.Property( x => x.Id ).HasColumnName( "ID" );
builder.Property( x => x.DueDate ).HasColumnName( "DUEDATE" );
}
}
}
со схемой SQL
CREATE TABLE ENTITY_DUE_DATE (
ID INT NOT NULL,
DUEDATE DATETIME NULL
);
Где наше отображение DateTime выглядит как
public class SqlServerDateTimeMapping : DateTimeTypeMapping {
public SqlServerDateTimeMapping() : base( "DateTime", System.Data.DbType.DateTime ) { }
protected SqlServerDateTimeMapping( RelationalTypeMappingParameters parameters ) : base( parameters ) { }
public override string GenerateSqlLiteral( object value ) {
if ( value is DateTime date ) {
if ( date.Date != date ) {
System.Diagnostics.Debug.Fail( "Time portions are not supported" );
}
return date.ToString( "yyyy-MM-dd" );
}
return base.GenerateSqlLiteral( value );
}
public override RelationalTypeMapping Clone( in RelationalTypeMappingInfo mappingInfo ) => new SqlServerDateTimeMapping();
public override RelationalTypeMapping Clone( string storeType, int? size ) => new SqlServerDateTimeMapping();
public override CoreTypeMapping Clone( ValueConverter converter ) => new SqlServerDateTimeMapping();
}
Как я уже говорил, я пытался создать конвертер для подключения к переопределению конвертера SqlServerDateTimeMapping, но я не мог понять, как с этим справиться ДО / В ТЕЧЕНИЕ парсинга Sql. Похоже, что конвертер позволяет вам конвертировать между двумя типами CLR, так что пост-разбор.
Ради полноты, это мой конвертер в настоящее время. Хотя это совершенно неправильно.
public class NullableDateTimeToDateTimeConverter : ValueConverter<DateTime?, DateTime> {
public NullableDateTimeToDateTimeConverter() : base(s => s ?? DateTime.MaxValue, x => x ) { }
}
Я ожидаю, что свойство моего класса Entity сможет быть необнуляемым, оставить столбец базы данных обнуляемым и обработчик обработчика структуры сущностей будет возвращать значение по умолчанию при обнаружении нуля, в то время как в настоящее время он разрушается при разборе null (в частности, в SqlBuffer.GetDateTime (), вызываемом SqlReader.GetDateTime () внутри любого запроса контекста, независимо от его context.EntityDueDate.ToList () или context.Set ()).
Каким бы ни было решение, было бы идеально, если бы мои запросы могли обрабатываться на стороне базы данных, а не на стороне клиента. Так что, если я сделал
context.EntityDueDates.Where(x => x.DueDate > DateTime.UtcNow).ToList()
Лучше всего иметь возможность выполнить этот запрос к базе данных и не нужно возвращать его на стороне клиента для выполнения
DueDate ?? DEFAULT_VALUE
логика, однако она вписывается туда.
Я должен уточнить, что решение должно работать больше, чем просто DateTime. Я просто выбираю DateTime, потому что тот, который я исправил вручную, был DateTime, но изучение баз данных показывает мне, что этот же сценарий, скорее всего, произойдет для нескольких int / int? различия также. Хотелось бы надеяться, что данное решение может быть применено между любыми типами данных, которые могут обнуляться / не обнуляться.
UPDATE:
У меня новое руководство. В классе SqlServerDateTimeMapping я могу переопределить метод:
public override MethodInfo GetDataReaderMethod() {
var methodInfo = base.GetDataReaderMethod();
return methodInfo;
}
и проверьте methodInfo. Конечно же, это метод «GetDateTime», тот самый, который падает при разборе. Мой мыслительный процесс, может быть, я могу каким-то образом вернуть свой собственный метод, который может быть вызван на SqlDataReader, который обрабатывает нулевую проверку, или подкласс SqlDataReader, чтобы переопределить этот метод. Теперь я заметил, что GetDateTime принимает в качестве параметра int, ссылаясь на какой столбец. Внутренне он вызывает IsDBNull перед вызовом, чтобы убедиться, что он не нулевой. Я надеялся, что передается строка, которую я могу переопределить, но похоже, что она просто берет столбец и использует SqlBuffer для get_dateTime on, то есть кто выполняет синтаксический анализ