Получение всех дат между двумя датами с использованием DatePickers и Entity Framework 6 - PullRequest
0 голосов
/ 11 декабря 2018

У меня есть два сборщика даты и времени в моей форме.Я хочу функцию, которая будет возвращать все datetime из определенной таблицы (которые являются значениями определенного столбца) между этими двумя датами.

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

public DateTime[] GetAllArchiveDates(string username = null)
{
    var result = new DateTime[0];

    if (username != null)
    {
        result = this._context.archive.OrderBy(s => s.IssuingDate).Where(s => s.insertedBy == username).Select(s => s.issuing_date).Distinct().ToArray();
    }
    else
    {
        result = this._context.archive.OrderBy(s => s.IssuingDate).Select(s => s.issuing_date).Distinct().ToArray();
    }

    return result;
}

Но я получаю эту ошибку:

System.NotSupportedException: 'Указанный член типа' IssuingDate 'являетсяне поддерживается в LINQ to Entities.Поддерживаются только инициализаторы, элементы сущностей и свойства навигации сущностей. '

Как это сделать?

1 Ответ

0 голосов
/ 11 декабря 2018

Причина вашего сообщения об ошибке

Вы должны знать о различиях между IEnumerable и IQueryable.

Объект класса, который реализует IEnumerable, содержит все дляперечислите последовательность элементов, которые она представляет.Вы можете запросить первый элемент последовательности, и как только вы его получите, вы можете запросить следующий элемент, пока не останется больше элементов.

С другой стороны, объект классакоторый реализует IQueryable содержит все, чтобы попросить другой процесс предоставить данные для создания последовательности IEnumerable.Для этого он содержит Expression и Provider.

. Expression - это общее представление того, какой тип IEnumerable должен быть создан после того, как вы начнете перечислять IQueryable.

Provider знает, кто должен выполнить запрос, и знает, как преобразовать Expression в формат, понятный исполнителю, например, в SQL.

Существует два вида операторов LINQ.Те, которые используют отложенное выполнение, и те, которые не используют.Отложенные функции могут быть распознаны, потому что они возвращают IQueryable<TResult> (или IEnumerable).Примерами являются Where, Select, GroupBy и т. Д.

Неотложенные функции возвращают TResult: ToList, ToDictionary, FirstOrDefault, Max.

Пока вы объединяете отложенные функции LINQ, запрос не выполняется, изменяется только Expression.Как только вы начинаете перечисление, либо явно используя GetEnumerator и MoveNext, либо неявно используя foreach, ToList, Max и т. Д., Expression отправляется Provider, который преобразует его в SQL и выполняет запрос.Результат представляется в виде IEnumerable, в котором выполняется GetEnumerator.

Какое это имеет отношение к моему вопросу?

Поскольку Expression должен быть переведен на SQL, он не может содержать ничего, что вы изобрели.В конце концов, SQL не знает ваших функций.На самом деле, существует множество стандартных функций, которые нельзя использовать в IQueryable.См. Поддерживаемые и неподдерживаемые функции LINQ

Увы, вы забыли дать нам определение класса archive, но я думаю, что это не POCO: он содержит функции и свойства, которые делают больше, чемпросто получить / установить.Я думаю, что IssuingDate - это не просто get / set.

Для IQueryables вы должны сохранять свои классы простыми: во время запроса используйте только {get; set;}, ничего более.Другие функции могут быть вызваны после того, как вы материализовали IQueryable во что-то IEnumerable, которое должно быть выполнено в вашем локальном процессе

Вернуться к вашему вопросу

Итак, у вас есть база данных стаблица Archive с хотя бы столбцами IssuingDate и InsertedBy.Кажется, что InsertedBy это просто строка.Это может быть внешний ключ к таблице с пользователями.Это не сильно повлияет на ответ.

После первых соглашений кода платформы сущностей это приводит к следующим классам

class Archive
{
    public int Id {get; set;}
    public DateTime IssuingDate {get; set;}
    public string InsertedBy {get; set;}
    ...
}

public class MyDbContext : DbContext
{
     public DbSet<Archive> Archives {get; set;}
}

Кстати, есть липравильная причина, по которой вы так часто отклоняетесь от стандартов Microsoft в отношении именования идентификаторов, особенно плюрализма и верблюжьего покрова?

В любом случае, ваше требование

У меня есть два сборщика даты и времени в моей форме.Мне нужна функция, которая будет возвращать все datetime из определенной таблицы (которые являются значениями определенного столбца) между этими двумя датами.

Ваш код, кажется, делает намного больше, но давайте сначала напишемфункция расширения, которая отвечает вашим требованиям.Я напишу это как метод расширения вашего архивного класса.Это сохранит ваш архивный класс простым (только {get; set;}), но при этом добавит функциональность этому классу.Запись его в качестве функции расширения также позволяет использовать эти функции, как если бы они были любой другой функцией LINQ.См. Демистифицированные методы расширения

public static IQueryable<Archive> BetweenDates(this IQueryable<Archive> archives,
    DateTime startDate,
    DateTime endDate)
{
     return archives.Where(archive => startDate <= archive.IssuingDate
                                   && archive.IssuingDate <= endDate);
}

Если я посмотрю на ваш код, вы ничего не делаете, выбирая архивы между датами.Вы делаете что-то с userName, упорядочивая, выбирая разные ... Немного странно, что вы сначала заказываете все свои миллионы архивов, а затем решаете сохранить только десять архивов, принадлежащих userName, и если у вас несколько одинаковых дат выпускаВы решили удалить дубликаты.Не будет ли эффективнее сначала ограничить количество дат выпуска, прежде чем вы начнете заказывать их?

public static IQueryable<archive> ToIssuingDatesOfUser(this IQueryable<archive> archives,
    string userName)
{
    // first limit the number of archives, depdning on userName,
    // then select the IssuingDate, remove duplicates, and finally Order
    var archivesOfUser = (userName == null) ? archives :
        archives.Where(archive => archive.InsertedBy == userName);

   return archivesOfUser.Select(archive => archive.IssuingDate)
                        .Distinct()
                        .OrderBy(issuingDate => issuingDate);
}

Примечание: до сих пор я создавал только IQueryables.Таким образом, изменяется только Expression, что довольно эффективно.База данных еще не сообщена.

Пример использования:

Требование: с учетом userName, startDate и endDate, дайте мне уникальные editioningDates всех архивов, выпущенных этим пользователем,в порядке возрастания

public ICollection<string> GetIssuingDatesOfUserBetweenDates(string userName, 
    DateTime startDate,
    DateTime endDate)
{
    using (var dbContext = new MyDbContext(...))
    {
        return dbContext.Archives
               .BetweenDates(startDate, endDate)
               .ToIssuingDatesOfUser(userName)
               .ToList();
    }
}
...