Неверное значение по умолчанию для типа дата-сервер SQL Server - PullRequest
1 голос
/ 16 мая 2019

Предположим, что отношение Category n-m Blog:

CREATE TABLE Categories(
    Id INT IDENTITY(1,1) NOT NULL,
    Title NVARCHAR(MAX) NOT NULL,
    Created datetime NOT NULL,
    CONSTRAINT PK_Categories PRIMARY KEY(Id)
)
CREATE TABLE Posts(
    Id INT IDENTITY(1,1) NOT NULL,
    Subject NVARCHAR(MAX) NOT NULL,
    Body NVARCHAR(MAX) NULL,
    CONSTRAINT PK_Posts PRIMARY KEY (Id)
)
CREATE TABLE PostCategory(
    CategoryId INT NOT NULL,
    PostId INT NOT NULL,
    CONSTRAINT PK_PostCategory PRIMARY KEY (CategoryId, PostId),
    FOREIGN KEY ([CategoryId]) REFERENCES [Categories]([Id]),
    FOREIGN KEY ([PostId]) REFERENCES [Posts]([Id])
)

После создания базы данных (см. https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db#reverse-engineer-your-model) Я начал свой первый запрос:

var postsWithLatestCategoryTimestamp = (
        from post in db.Posts
        let latestCategoryTimestamp =
            (
                from relation in post.PostCategory
                orderby relation.Category.Created descending
                select relation.Category.Created
            )
            .FirstOrDefault()
        select new
        {
            post.Id, post.Subject, latestCategoryTimestamp
        }
    ).ToList();

Я получил

System.Data.SqlClient.SqlException : 'Ошибка преобразования при преобразовании даты и / или времени из символьной строки.'

Причина: Ядро EntityFramework использует неправильное значение по умолчанию ('0001-01-01T00:00:00.0000000'. вместо '1753-01-01T00:00:00.000'):

SELECT [post].[Id], [post].[Subject], COALESCE((
    SELECT TOP(1) [relation.Category].[Created]
    FROM [PostCategory] AS [relation]
    INNER JOIN [Categories] AS [relation.Category] ON [relation].[CategoryId] = [relation.Category].[Id]
    WHERE [post].[Id] = [relation].[PostId]
    ORDER BY [relation.Category].[Created] DESC
), '0001-01-01T00:00:00.0000000') AS [latestCategoryTimestamp]
FROM [Posts] AS [post]

Как это исправить, не касаясь существующей базы данных?


Кстати, OnModelCreating() хорошо подмостки:

modelBuilder.Entity<Categories>(entity =>
{
    entity.Property(e => e.Created).HasColumnType("datetime");
    //...
});

1 Ответ

3 голосов
/ 16 мая 2019

Это может выглядеть как ошибка EF Core, но для меня это больше похоже на неправильный запрос.

Представьте, что EF Core каким-то образом "правильно" переводит CLR DateTime по умолчанию на минимально поддерживаемую дату SqlServer. Когда вы получите материализованный результат, как вы узнаете, является ли latestCategoryTimestamp значением по умолчанию? Проверка на минимальную дату SqlServer? Что если вы выполняете запрос к базе данных в памяти или какой-либо другой базе данных? Может быть, вы бы сказали, что они должны преобразовать его обратно в память? Почему минимальное значение базы данных должно быть сопоставлено с default(DateTime)? Как насчет DateTime.MinValue тогда?

Вывод состоит в том, что нет правильного отображения default(DateTime). Вопрос в том, зачем использовать «магическое» значение для представления отсутствующего значения? Базы данных предоставляют NULL для этого и C # (CLR) - обнуляемые типы.

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

select (DateTime?)relation.Category.Created

Но поскольку все, что вам здесь нужно, это дата максимум , было бы достаточно использовать метод Max (но опять-таки с перегрузкой, допускающей значение NULL):

let latestCategoryTimestamp = post.PostCategory.Max(link => (DateTime?)link.Category.Created)

Но какой бы подход вы ни выбрали, все, что вам нужно, это учесть свойство Nullable<DateTime> материализованного объекта. Хорошо, что null будет указывать пропущенную категорию сообщений независимо от типа базы данных.

...