#c# #entity-framework #linq #asp.net-core #.net-core
#c# #entity-framework #linq #asp.net-ядро #.net-ядро
Вопрос:
У меня есть модель, похожая на эту:
Public class Item {
public int Id { get; set; }
public string Name { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public int? TotalTime
{
get
{
return Convert.ToInt32(EndTime.Value - StartTime.Value).TotalMinutes);
}
}
etc...
Затем я получаю список элементов:
IQueryable<Items> items = itemsContext.Items.Where(e => e.Id > 0);
Я делаю некоторые вычисления, запускаю какой-то другой код, затем в конце я хочу вернуть список элементов, упорядоченных по Name ASC и totalTime DESC. Это то, что я пробовал:
items = items.OrderBy(e => e.Name).ThenByDescending(e => e.TotalTime);
Но я получаю сообщение об ошибке: «Выражение LINQ не удалось перевести. Либо перепишите запрос в форме, которую можно преобразовать, либо переключитесь на оценку клиента явно, вставив вызов AsEnumerable() , AsAsyncEnumerable(), ToList() или ToListAsync() .»
Я также пробовал это, но получаю ту же ошибку:
items = items.OrderBy(e => e.Name).ThenByDescending(e => e.TotalTime ?? 0);
Как я могу отсортировать свой набор данных по totalTime?
Комментарии:
1. Не пытайтесь это сделать. Свойство totalTime не может быть вычислено на сервере, потому что EF не видит дерева выражений, только вызов свойства. Какой код находится под средством получения свойств, скрыто. Поэтому EF не может перевести это выражение,
2. кстати, избегайте
Convert.ToInt32
, потому что это не дает вам контроля над обработкой ошибок. Вы можете безопасноTimeSpan.TotalMinutes
выполнять прямое приведениеInt32
.3. Для таких вычислений в записи всегда лучше всего добавить вычисляемый столбец в базу данных и сопоставить его с вашей моделью EF как вычисленный.
Ответ №1:
- As
TotalTime
не является частью вашей базы данных, поэтому вам нужно аннотировать его[NotMapped]
. - Затем вам нужно сначала загрузить элементы, а затем отсортировать по ним.
- Однако, если вы хотите выполнять подкачку или сортировку на своем сервере базы данных, вам необходимо использовать
EndTime
иStartTime
непосредственно в вашем запросе.
Подход 1: сначала загрузка, затем сортировка в коде приложения:
List<Items> loadedItems = await itemsContext.Items
.Where( e => e.Id > 123 )
.ToListAsync()
.ConfigureAwait(false); // `ConfigureAwait(false)` MAY be optional, depending on your application.
List<Items> sortedItems = loadedItems
.OrderBy(e => e.Name)
.ThenByDescending(e => e.TotalTime)
.ToList(); // <-- Note this is NOT `ToListAsync` because the items are already loaded into memory.
Подход 2: сортировка в SQL:
List<Items> loadedSortedItems = await itemsContext.Items
.Where( e => e.Id > 123 )
.OrderBy( e => e.Name )
.ThenByDescending( e => e.EndTime - e.StartTime )
.ToListAsync()
.ConfigureAwait(false);
Изменения в вашем классе
Я бы изменил ваш класс примерно так:
public class Item {
public int Id { get; set; }
public string Name { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
[NotMapped]
public int? TotalTimeMinutes
{
get
{
if( this.EndTime == null || this.StartTime == null ) return null;
TimeSpan diff = this.EndTime.Value - this.StartTime.Value;
return (Int32)Math.Round( diff.TotalMinutes );
}
}
NotMapped
Атрибут дает понять Entity Framework, что он должен игнорировать свойство.- Обратите внимание, как свойство включает имя единицы измерения («Минуты»), в противном случае это неочевидно, если свойство равно миллисекундам, секундам, минутам и т.д.
- Также обратите внимание, как свойство проверяет, являются ли
StartTime
иEndTime
нулевыми, и возвращаетnull
в этом случае. Ваш код этого не сделал, что означает, что ваша программа завершится сбоем, еслиNULL
в базе данных есть значения или если какиеItem
-либо экземпляры объекта инициализированы не полностью. - Если
StartTime
EndTime
значения и не могут бытьNULL
, вам нужно добавитьNOT NULL
в свою базу данных и обновить классы сущностей EF, чтобы свойства былиDateTime
и неDateTime?
были.
Комментарии:
1. ОК. Спасибо. [NotMapped] не требуется, если у вас нет обоих, также getter и setter, спасибо за примечания по вычислению времени. Я хотел написать простой пример, которому легко следовать и который представляет более сложную ситуацию. Я решил свою проблему, сначала вызвав ToList(), прежде чем пытаться выполнить сортировку.
2. второй подход не является правильным подходом
3. @HamedHajiloo Пожалуйста, объясните, почему вы считаете, что это неверно.
Ответ №2:
Вы должны перевести метод SQL DateDiff в ядро EF. для этого подхода вы можете использовать метод ниже.
EF.Functions.DateDiffMinute(c.StartDate, c.EndDate ?? DateTime.Now)