Проверьте, существует ли какое-либо значение в таблице базы данных из списка дат с помощью Entity Framework

#c# #asp.net #.net #entity-framework #entity-framework-core

Вопрос:

У меня есть список таких дат, как эта:

 DateTime now = DateTime.Today;

var expiriesDays = new List<int> { 1, 7, 14 };
List<DateTime> expiriesDates = expiriesDays.Select(x => now.AddDays(x).Date).ToList();
 

Теперь у меня есть одна таблица , и в ней есть поле с именем ComplianceExpiryDate , теперь я хочу проверить, имеет ли это поле какое-либо значение expiriesDates .

Как и любая запись, имеет значение 13-07-2021, 19-07-2021 ИЛИ 26-07-2021 в ComplianceExpiryDate

Я просто хочу сравнить с Датой, а не со временем.

Я пробовал, TruncateTime но, DbFunctions не показывает эту функцию для использования и выдает ошибку.

Ответ №1:

Вы всегда должны быть осторожны при преобразовании значений базы данных перед фильтрацией. Потому что именно это произойдет при применении AddDays ComplianceExpiryDate или усечении его временной части, а затем при сравнении результатов с заданным значением даты. База данных должна преобразовать значения всей таблицы, прежде чем приступать к их сравнению. Это может быть дорого, но еще хуже то, что любой индекс в поле поиска теперь непригоден для использования. Это явление отражено в одной короткой фразе: не поддается сарказму.

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

Короче говоря, создайте интервалы дат, между которыми вы хотите найти ComplianceExpiryDate , и создайте запрос с условиями «между датой начала и датой окончания», объединенными OR . Лучше всего это сделать с помощью конструктора предикатов, например Linqkit или этого.

Затем код выглядит следующим образом:

 DateTime now = DateTime.Today;

var expiryDays = new List<int> { 1, 7, 14 };
var expirationIntervals = expiryDays
    .Select(x => (StartDate: now.AddDays(x - 1), EndDate: now.AddDays(x))).ToList();
    
Expression<Func<MyTable,bool>> predicate = PredicateBuilder.False<MyTable>();

predicate = expirationIntervals.Aggregate (predicate, (pred, tuple) =>
    pred.Or(t => t.ComplianceExpiryDate >= tuple.StartDate
              amp;amp; t.ComplianceExpiryDate < tuple.EndDate));

var result = MyTables.Where(predicate);
 

Этот короткий Or метод является методом расширения в любом построителе предикатов, который объединяет два предиката в один предикат с помощью токена «OrElse».

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

1. Я протестировал это с помощью EF Core 5 и SQLite с некоторыми тестовыми данными. Похоже, вам нужно будет переодеться .Select(x => (StartDate: now.AddDays(x - 1), EndDate: now.AddDays(x))) .Select(x => (StartDate: now.AddDays(x), EndDate: now.AddDays(x 1))) . Пожалуйста, обратите внимание, что PredicateBuilder.False это устарело. Но все же это хорошее решение. br

2. @РевеТ. Правильно, PredicateBuilder.False устарело в Linqkit. Но я всегда использую чистый и простой конструктор предикатов Монти. Что касается интервала, возможно, это просто интерпретация «истекло», возможно, вы правы. В любом случае, ОП кажется счастливым.

Ответ №2:

Это простое решение, которое генерирует SQL с помощью an IN .

Вам также следует протестировать решение @GertArnold, чтобы увидеть, что лучше всего подходит для вашего случая. Ответ от @GertArnold используется OR вместо IN , но, вероятно, будет работать лучше, когда ваша база данных вырастет.

 var today = DateTime.Today;

// create a list of dates without time-part specified
var expiryDays = new List<int> { 1, 7, 14 };
var expirationDates = expiryDays
    .Select(it => today.AddDays(it))
    .ToList(); 

// insert test data into db
var currentId = 1;
foreach (var currentDate in expirationDates)
{
    db.MyTables.Add(new MyTable
    {
        Id = currentId,
        ComplianceExpiryDate = currentDate.AddHours(3) // move time a few hours into the day
    });
    currentId  ;
}

db.SaveChanges();

var numberOfMatchingItems = db.MyTables
    .Count(e => expirationDates.Contains(e.ComplianceExpiryDate.Date));

Console.WriteLine(
    $"Number of matching dates found in db: {numberOfMatchingItems}");
 

Выход:

Количество совпадающих дат, найденных в базе данных: 3

Я использовал это определение для класса сущностей с датой:

 public class MyTable
{
    public int Id { get; set; }
    public DateTime ComplianceExpiryDate { get; set; }
}
 

SQL, созданный EF:

       SELECT COUNT(*)
      FROM "MyTables" AS "m"
      WHERE rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', "m"."ComplianceExpiryDate", 'start of day'), '0'), '.') IN ('2021-07-14 00:00:00', '2021-07-20 00:00:00', '2021-07-27 00:00:00')
 

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

1. Я пытаюсь … позвольте мне проверить это и сообщить вам.

2. @Nic: Пожалуйста, обратите внимание, что я обновил свой ответ из-за неправильного понимания вашего случая в моем первом ответе. br

3. Да, я это проверю.

4. @Nic: Спасибо, пожалуйста, посмотрите мой комментарий к выбранному вами ответу, вероятно, это легко исправляемая ошибка в коде. br