Entity Framework Core Включить, затем взять 2 - PullRequest
0 голосов
/ 14 февраля 2020

Для цели обучения Я сделал консольное приложение, используя EFCore 3.1

У меня такие отношения:

Человек принадлежит один Тип

Персона есть много Запросы

Запрос принадлежит одному Персоне

Запрос принадлежит местоположению

это консольное приложение подключено к Mysql DB Scaffolded в Entities с Pomelo.

Теперь мне нужно получить каждые Persons из БД, а затем заказать связанные с ним Type и Last Queries на сегодняшний день у каждого человека есть сотни запросов, а в этой базе данных тысячи людей, поэтому, если я попытаюсь

using (var ctx = new DBContext) 
{
    var persons = ctx.Persons
                  .Include(p => p.Type)
                  .Include(p => p.Queries).ThenInclude(q => q.Location)
}

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

В моей БД у меня есть двойное связанное представление для с этой целью, один для получения max_query_id (person_maxquery) для каждого person_id и один для объединения этих 4 таблиц

SELECT p.*, ty.*, pq.*, loc.* FROM persons p
LEFT JOIN person_maxquery max ON p.id = max.person_id
LEFT JOIN person_queries pq ON pq.id = max.query_id
LEFT JOIN locations loc ON loc.id = pq.location_id
LEFT JOIN types ty ON ty.id = p.type_id

Я хочу добиться того же в EF, но я не знал, возможно ли это, Могу ли я отфильтровать данные, полученные из БД? Должен ли я «сделать» свой объект, используя вид, вручную присваивая каждое значение связанному значению модели?

1 Ответ

3 голосов
/ 14 февраля 2020

Запросы, которые извлекают наборы данных, редко требуют полной информации от соответствующих объектов. Вы можете уменьшить это и отфильтровать данные, проецируя сущности в структуру модели представления, которая отображает, какие данные вам действительно нужны.

using (var ctx = new DBContext) 
{
    var persons = ctx.Persons
        .Select(x => new PersonViewModel
        {
           PersonId = x.PersonId,
           Name = x.Name,
           TypeName = x.Type.Name,
           RecentQueries = x.Queries
               .OrderByDecending(q => q.QueryDate)
               .Select(q => new QueryViewModel
               {
                  QueryId = q.QueryId,
                  Name = q.Name,
                  LocationName = q.Location.Name
                  // ... etc.
               }).Take(2).ToList()
        }).ToList();
    //...
}

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

Изменить: Пример жизненного цикла некоторых данных между сервером и представлением:

Часть 1 ... Список группы людей. Это сводные данные, достаточное количество данных для удовлетворения представления сводного списка.

public IEnumerable<PersonSummaryViewModel> GetPeople(/*criteria*/)
{
    using (var ctx = new DBContext) 
    {
        var people = ctx.Persons
            .Where( /* based on criteria */ )
            .Select(x => new PersonSummaryViewModel
            {
               PersonId = x.PersonId,
               Name = x.Name,
               TypeName = x.Type.Name,
               RecentQueries = x.Queries
                   .OrderByDecending(q => q.QueryDate)
                   .Select(q => new QuerySummaryViewModel
                   {
                      QueryId = q.QueryId,
                      Name = q.Name,
                      LocationName = q.Location.Name
                      // ... etc.
                   }).Take(2).ToList()
            }).ToList();
        //...
        return people;
    }
}

Часть 2. Получение подробной модели человека. Как только пользователь выберет человека, мы захотим получить больше данных. Эта модель представления может иметь намного больше полей и отношений, чтобы удовлетворить представление. Но даже здесь мы можем захотеть исключить данные, которые обычно не просматриваются (например, в расширяемых областях или вкладках), которые могут быть отозваны по требованию с помощью вызовов Ajax, если / когда пользователь хочет.

public PersonDetailViewModel> GetPerson(int personId)
{
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            .Select(x => new PersonDetailViewModel
            {
               PersonId = x.PersonId,
               Name = x.Name,
               TypeName = x.Type.Name,
               // ... Can load all visible properties and initial, important related data...
            }).Single(x => x.PersonId == personId);
        //...
        return person;
    }
}

Часть 3: пример, обновление персоны

public void UpdatePerson(UpdatePersonViewModel updatePersonModel)
{
    if (updatePersonModel == null)
       throw new ArgumentNullException("updatePersionModel");

    // TODO: Validate updateModel, ensure everything is Ok, not tampered/invalid.
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            // can .Include() any related entities that can be updated here...
            .Where( x => x.PersonId == updatePersonModel.PersonId )
            .Single();
        person.Name = updatePersonModel.Name;
        // etc.
        ctx.SaveChanges();
    }
}

Часть 4: Пример, добавление запроса.

public void AddQuery(int personId, AddQueryViewModel queryModel)
{
    if (queryModel == null)
       throw new ArgumentNullException("queryModel");

    // TODO: Validate queryModel, ensure everything is Ok, not tampered/invalid.
    using (var ctx = new DBContext) 
    {
        var person = ctx.Persons
            .Include( x => x.Queries )
            .Where( x => x.PersonId == personId )
            .Single();

        // TODO: Maybe check for duplicate query already on Person, etc.
        var query = new Query
        {
            // copy data from view model
        };
        person.Queries.Add(query);
        ctx.SaveChanges();
    }
}

Модели представления (или DTO) относятся только к передаче данных между сервер и клиент. Когда мы возвращаем данные на сервер и хотим обновить сущности, мы загружаем эти сущности. Используя эти модели представлений, мы уменьшаем объем данных, передаваемых между клиентом и сервером (только необходимые поля, а не целые графы сущностей), что означает более быстрый код и меньшее количество данных по сети. Мы не раскрываем больше о нашей структуре сущностей, чем необходимо знать клиенту, и мы не рискуем просто перезаписать данные, возвращаемые чем-то вроде Attach + EntityState.Modified + SaveChanges, где данные могут быть устаревшими (кто-то иначе изменено, так как эта копия была взята), или возвращающиеся данные могли быть подделаны. Просмотр моделей имеет единственное назначение. Мы могли бы использовать PersonDetailViewModel в операции типа UpdatePerson, но наше обновление может применяться только к нескольким выбранным свойствам, поэтому накладываются накладные расходы на отправку всего на сервер, и нет никаких подразумеваемых ограничений на то, что должно и не должно быть разрешено обновлять. , (Особенно если вы используете Automapper или тому подобное, чтобы помочь копировать поля между сущностями и представлениями моделей)

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

var queryNames = ctx.Persons
    .Where(x => x.PersonId == personId)
    .SelectMany(x => Queries.Select( q => new  
    {
       q.QueryId,
       q.Name
    }).ToList();
var message = string.Join(", ", 
    queryNames.Select(x => string.Format("{0} ({1})", x.Name, x.QueryId)));

Просто как простой пример с анонимным типом для возврата структуры, которую можно использовать по дальнейшему коду без необходимости просмотра модели / DTO. Мы не передаем данные сущности обратно, а, возможно, просто проверяем значения, чтобы определить ход действий, или составляем что-то вроде строки сообщения.

...