LINQ Включить замедление производительности при поиске - PullRequest
0 голосов
/ 30 мая 2018

У нас есть следующий метод, который позволяет нам искать таблицу DataGrid в таблице проектов:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString)
{
    var projects = _context.Projects.Where(p => p.Current);

    projects.Include(p => p.Client);
    projects.Include(p => p.Architect);
    projects.Include(p => p.ProjectManager);

    if (!string.IsNullOrEmpty(searchString))
    {
        projects = projects
            .Where(p => p.NormalizedFullProjectName.Contains(searchString)
                    || p.Client.NormalizedName.Contains(searchString)
                    || p.Architect.NormalizedFullName.Contains(searchString)
                    || p.ProjectManager.NormalizedFullName.Contains(searchString));
    }

    projects = projects.OrderBy(p => p.Name).Take(10);

    return await projects.ToListAsync();
}

Если мы не используем Include в проектах, тогда поиск происходит мгновенно.Однако после добавления их в поиск может потребоваться более 3 секунд.

Нам необходимо включить другие сущности, чтобы пользователи могли выполнять поиск по ним, если они захотят.

Как мы можемчтобы повысить производительность, но при этом оставить Include, чтобы разрешить поиск по ним?

Без Incldue метод выглядит так:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString)
{
    var projects = _context.Projects.Where(p => p.Current);

    if (!string.IsNullOrEmpty(searchString))
    {
        projects = projects
            .Where(p => p.Name.Contains(searchString));
    }

    projects = projects.OrderBy(p => p.Name).Take(10);

    return await projects.ToListAsync();
}

Без Include производительность выглядит так:

enter image description here

С Include:

enter image description here

1 Ответ

0 голосов
/ 30 мая 2018

Короткий ответ таков: включение всех дополнительных объектов требует времени и усилий, что увеличивает время загрузки.

Однако в вашем предположении есть недостаток:

Мынеобходимо включить другие сущности, чтобы пользователи могли осуществлять поиск по ним, если они этого захотят.

Это (не обязательно) правильно.Фильтрация происходит на уровне базы данных.Include указывает Entity Framework загружать записи из базы данных.Это две разные вещи.

Посмотрите на следующие примеры:

_context.Projects
        .Include(p => p.Architect)
        .Where(p => p.Architect.Name == "Bob")
        .ToList()

Это даст вам список проектов (и их архитекторов), у которых есть архитектор по имени Боб.

_context.Projects
        .Where(p => p.Architect.Name == "Bob")
        .ToList()

Это даст вам список проектов (без архитекторов), у которых есть архитектор по имени Боб;но на самом деле он не загружает объект Architect в память.

_context.Projects
        .Include(p => p.Architect)
        .ToList()

Это даст вам список проектов (и их архитекторов).Он будет содержать каждый проект, список не фильтруется.


Вам нужно использовать Include, только если вы хотите в памяти фильтрацияв коллекции, которая уже была загружена из базы данных.

Если это так, зависит от этой части:

    projects = projects
        .Where(p => p.NormalizedFullProjectName.Contains(searchString)
                || p.Client.NormalizedName.Contains(searchString)
                || p.Architect.NormalizedFullName.Contains(searchString)
                || p.ProjectManager.NormalizedFullName.Contains(searchString));

Если NormalizedFullProjectName (и другие свойства) являются столбцами базы данных, тогда EF может выполнить фильтрацию вуровень базы данных.Вам не нужно Include для фильтрации предметов.

Если NormalizedFullProjectName (и другие свойства) не являются столбцами базы данных, то EF сначала должен загрузить элементы в память, прежде чем он сможет применить фильтр.В этом случае вам нужен Include, потому что архитекторы (и другие) должны быть загружены в память.


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

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

Например:

_context.Projects
        .Select(p => new { Project = p, ArchitectName = p.Architect.Name })
        .ToList()

Это загрузит объекты проекта (полностью), но только имя архитектора.и ни одно из других свойств.Это может значительно повысить производительность, если у связанных объектов есть много свойств, которые вам в данный момент не нужны.

Примечание. В текущем примере используется анонимный тип.Я обычно выступаю за создание собственного типа для этого;но это не связано с проблемой производительности, о которой мы здесь говорим.


Обновление

Судя по вашему обновлению, вы подразумеваете, что предполагаемая фильтрацияпроисходит после загрузки объектов из базы данных.

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

Здесь есть отдельные аргументы для производительности:

  • Загрузка всего один раз - Загрузите все данные один раз (что может занять много времени), но затем разрешите пользователю фильтровать загруженные данные (что очень быстро)
  • Загрузить фрагменты - Загружать только данныечто соответствует применяемым фильтрам.Если пользователь меняет фильтры, вы снова загружаете данные.Первая загрузка будет намного быстрее, но последующие действия фильтрации будут занимать больше времени по сравнению с фильтрацией в памяти.

То, что вы должны сделать здесь, не мое решение.Это вопрос приоритетов.Некоторые клиенты предпочитают одного другому.Я бы сказал, что в большинстве случаев второй вариант (загрузка фрагментов) является лучшим вариантом здесь, так как он предотвращает ненужную загрузку массивного набора данных, если пользователь никогда не просматривает 90% его.Это пустая трата производительности и сетевой нагрузки.

Ответ, который я дал, относится к подходу «куски нагрузки».

Если вы решите использовать подход «загрузить все один раз», то вам придется принять снижение производительности этой начальной загрузки.Лучшее, что вы можете сделать, - строго ограничить столбцы возвращаемых данных (как я показал с Select), чтобы минимизировать производительность / сетевые затраты.

Я не вижу разумного аргумента для объединения этих двух подходов.Вы получите оба недостатка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...