Как выполнить арифметику временных меток в ядре EF, если NodaTime использует длительность, а Postgres использует ИНТЕРВАЛ / период?

#c# #postgresql #entity-framework-core #npgsql #nodatime

#c# #postgresql #entity-framework-core #npgsql #nodatime

Вопрос:

В моих проектах .NET Core 3.1 я в настоящее время переношу свой код, связанный со временем, в NodaTime.

У меня возникли проблемы с переводом некоторых запросов, потому что разница между двумя моментами времени в NodaTime дает длительность, в то время как в Postgres разница между двумя временными метками дает ИНТЕРВАЛ.

Npgsql сопоставляет мгновение с МЕТКОЙ ВРЕМЕНИ, но ИНТЕРВАЛ — это период.

Допустим, у меня есть объект с именем «Визит», который имеет идентификатор и два раза: «прибыл» и «ушел». (Это не мой фактический вариант использования, у меня есть несколько объектов с этой проблемой, так что это минимальный пример)

 public class Visit
{
    public int Id { get; set; }
    public Instant Arrived { get; set; }
    public Instant Left { get; set; }
}
  

Теперь я хочу найти посещения, в которых посетитель оставался меньше заданного промежутка времени:

 public async Task<List<Visit>> FindVisitsShorterThan(DbSet<Visit> dbSet, Duration duration)
{
    var visits =
        from visit in dbSet
        where (visit.Left - visit.Arrived) < duration
        select visit;
    return await visits.ToListAsync();
}
  

Если я это сделаю, Npgsql пожалуется на что-то вроде System.InvalidCastException: Can't write CLR type NodaTime.Duration with handler type TimestampHandler — потому что длительности не поддерживаются.

С другой стороны, если я преобразую длительность в период, NodaTime жалуется, потому что, с его точки зрения, эта разница является длительностью, и ее нельзя сравнить с < с периодом.

Я мог бы сделать пару вещей, но ни одна из них не кажется идеальной:

  • Измените сущность и замените «Instant Left» на «Period Stay», чтобы мне не приходилось выполнять это вычисление в запросе. Но это будет только для одного конкретного запроса — что, если мне нужно «Оставить» для другого запроса?
  • Используйте только LocalDateTime вместо Instant . Разница заключается в периоде, так что это будет работать как в NodaTime, так и в Postgres. Но таким образом я меняю семантику своей сущности — я хотел, чтобы эти времена были временными метками UTC по какой-то причине. LocalDateTime по сути означает «TZ неизвестно или хранится в другом месте», что просто неверно.
  • Вернитесь к типам BCL и снова начните бороться с часовыми поясами.
  • Разделите мои сущности на типы доменов с мгновенными и специфичными для Npgsql типами DTO с LocalTime и выполните некоторое сопоставление между ними. Вероятно, лучшее решение, но кажется ненужным.

Есть ли лучший способ?

Комментарии:

1. Хм. Я ничего не знаю о том, как Npgsql преобразует запрос… Я подозреваю, что лучшим решением было бы использовать это преобразование для обработки вашего существующего запроса путем преобразования Duration в Postgres Interval , где это необходимо. Я подозреваю , что вы не сможете легко получить хороший код без изменения Npgsql.

2. Даже если бы существовал способ преобразовать выражение в запрос, результирующий запрос был бы неаргументируемым. Вам было бы намного лучше предварительно вычислить это значение и сохранить его в отдельном столбце.

Ответ №1:

Это действительно досадное ограничение в текущей поддержке Npgsql EF для типов NodaTime.

Хорошей новостью является то, что я только что реализовал вышеупомянутое вместе с некоторыми другими арифметическими переводами NodaTime — см. Эту проблему с отслеживанием . Это будет включено в предстоящий выпуск EF Core 5.0 на следующей неделе — просто подождите несколько дней, и все должно быть хорошо.

Комментарии:

1. Итак … это мой первый вопрос о stackoverflow. Я не ожидал многого. Но в течение двух дней я получил комментарий от Джона Скита, и моя проблема была решена самим владельцем Npgsql. Большое вам спасибо! Я все равно готовился к обновлению до .NET 5, так что это тоже не могло прийти в лучшее время.