LINQ. Можно ли избежать объединения linking .includes в больших наборах данных?

#c# #entity-framework #entity-framework-core #repository-pattern #unit-of-work

#c# #entity-framework #сущность-фреймворк-ядро #репозиторий-шаблон #единица работы

Вопрос:

Я создаю n-уровневое приложение, используя шаблон репозитория / единицы работы, но у меня возникают проблемы с чтением свойств связанных объектов, поскольку они не загружаются. Я определил проблему, вызванную отложенной загрузкой. Упрощенным примером может быть попытка повторного поиска автора книги в приведенном ниже коде, поскольку оба свойства Author и Publisher для Book равны нулю.

В общем, я заинтригован отложенной загрузкой, поскольку это (насколько я понимаю) должно уменьшить объем передаваемых данных. Это особенно интересно для меня, поскольку я не всегда использую все свойства или коллекции на своих страницах Razor. Тем не менее, я не уверен, что это хорошая стратегия, и рассматриваю возможность изменения всей архитектуры от шаблона репозитория / единицы работы. Я еще не решил.

 public Author 
{
    public int Id {get; set;}
    public string Name {get; set;}
    public ICollection<Book> Books {get; set;}
    public ICollection<Publisher> Publishers {get; set;}
}

public Book
{
    public int Id {get; set;}
    public string Title {get; set;}
    public int AuthorId {get; set;}
    public ICollection<Author> Authors {get; set;}
    public Publisher Publisher {get; set;}
}

public Publisher
{
    public int Id {get; set;}
    public string Name {get; set;}
    public ICollection<Book> Books {get; set;}
    public ICollection<Author> Authors {get; set;}
}
 

Вернемся к проблеме. Сначала я попытался применить .Include(p=>p.Author) or .Load(p=>p.Author) , но понял, что это невозможно, после .ToListAsync() чего я вызываю метод `GetAllAsync ()’ в общем репозитории.

Теперь я рассматриваю возможность внедрения DbContext либо непосредственно на мои страницы Razor, либо на уровне сервиса, а затем цепочки Include() вызовов, например.

 _context.DBSetBooks<Book>.Include(p => p.Publisher).Where(p => p.Publisher.Name == "Manning").Include(p => p.Author);
 

Идея, изложенная выше, состоит в том, чтобы уменьшить объем передаваемых данных, или я неправильно понял это полностью?

Я не слишком люблю вводить DBContext на каждой странице Razor — вот почему я в первую очередь выбираю шаблон проектирования репозитория / единицы работы.. Если я все еще хочу отложенную загрузку, должен ли я вместо этого попробовать DDD-шаблон?

Есть идеи, как действовать дальше?

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

1. Include используется только для быстрой загрузки, а не для создания объединений. Они генерируются отношениями между объектами самим ORM. Что касается репозитория и UoW, они вам не нужны. DbSet уже является репозиторием, DbContext уже является единицей работы. Если вы попытаетесь переопределить шаблоны, вы только создадите ошибки и нарушите функциональность UoW

2. Что касается отложенной загрузки, довольно часто это увеличивает загружаемые данные и накладные расходы из-за проблемы N 1. Вместо выполнения 1 запроса, который возвращает все необходимые данные, у вас есть 1 запрос, который возвращает только корневые объекты и N запросов для загрузки каждого объекта по мере доступа к нему. Это плохо

3. Помимо комментариев, которые вы действительно должны понять от @PanagiotisKanavos, эта часть вопроса: «Мне особенно интересно, поскольку я не всегда использую все свойства или коллекции на своих страницах Razor», говорит мне, что вы не должны использовать отложенную загрузку Include , а скорее создавать ViewModels с необходимыми данными иэто Select вместо

4. LINQ позволяет загружать только те свойства, которые вы хотите, не более того. Нет причин загружать целые объекты. Нет причин использовать шаблоны, которые были введены в 2000 году, для загрузки целых объектов в J2EE. Подумайте о DbContext как о службе данных для ограниченного контекста в DDD. Нет причин иметь один DbContext для всего приложения, отображающий всю базу данных. Имеет смысл создать специализированный репозиторий, чтобы облегчить доступ к специализированному DbContext. Однако общий контекст бесполезен. Набор баз данных уже обеспечивает это

Ответ №1:

Чтобы уменьшить объем передаваемых данных, вы должны уменьшить .include , поэтому делайте свои запросы такими (это просто идея):

Сначала создайте класс, содержащий только те поля, которые вам нужны:

 public class BookInfo
{
 public int Id {get; set;}
  public string AuthorName {get; set;}
 public string PublisherName {get; set;}
.... and so on
}
 

Получить список книг, используя запрос, подобный этому:

  var books = await _context.Set<Book>()
.Where(p => p.Publisher.Name == "Manning")
.Select (b =>  new  BookInfo {
Id = b.Id,
AuthorName=b.Author.Name,
PublisherName=b.Publisher.Name,
....and so on

}).ToArrayAsync();
 

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

 public partial class Book 
{
 [NotMapped]
  public string AuthorName {get; set;}
[NotMapped]
 public string PublisherName {get; set;}
}
 

В этом случае вы можете использовать тот же класс Book вместо BookInfo:

 .Select ( b => new  Book {
Id = b.Id,
AuthorName=b.Author.Name,
PublisherName=b.Publisher.Name,
....and so on

}).ToArrayAsync();
}
 

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

1. .Set<>() может использоваться только в том случае, если объект уже настроен. В таком случае, почему бы не использовать Books свойство напрямую? Ничего не получается при использовании Set<>()

2. Да, вы правы. Я не видел ваш полный dbcontext, поэтому я использую Set . Но обычно я бы использовал книги.