Ядро EF — множество запросов, отправленных в базу данных для подзапроса

#linq #linq-to-entities #entity-framework-core #ef-core-2.2

#linq #linq-to-entities #entity-framework-core #ef-core-2.2

Вопрос:

Используя ядро EF 2.2.2, у меня в базе данных есть таблица, которая используется для хранения заметок для многих других таблиц. Другими словами, это что-то вроде подробной таблицы в отношениях мастер-деталь, но с несколькими основными таблицами. Рассмотрим эту упрощенную модель EF:

 public class Person
{
  public Guid PersonID { get; set; }
  public string Name { set; set; }
}

public class InvoiceItem
{
  public Guid InvoiceItemID { get; set; }
  public Guid InvoiceID { get; set; }
  public string Description { get; set; }
}

public class Invoice
{
  public Guid InvoiceID { get; set; }
  public int InvoiceNumber { get; set; }

  public List<Item> Items { get; set; }
}

public class Notes
{
  public Guid NoteID { get; set; }
  public Guid NoteParentID { get; set; }
  public DateTime NoteDate { get; set; }
  public string Note { get; set; }
}
  

В этом случае Notes может хранить заметки о персоналиях или заметки о счетах (или заметки InvoiceItem, хотя давайте просто скажем, что пользовательский интерфейс этого не поддерживает).

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

 public IQueryable<PersonDTO> GetPersonQuery()
{
  return from p in Context.People
             select new PersonDTO
             {
               PersonID = p.PersonID,
               Name = p.Name
             };
}

public List<PersonDTO> GetPeople()
{
  return (from p in GetPersonQuery()
              return p).ToList();
}

public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
  return from p in Context.Invoices
             select new InvoiceDTO
             {
               InvoiceID = p.InvoiceID,
               InvoiceNumber = p.InvoiceNumber
             };
}

public List<InvoiceDTO> GetInvoices()
{
  return (from i in GetInvoiceQuery()
              return i).ToList();
}
  

Все они работают, как ожидалось. Теперь, допустим, я добавляю InvoiceItems к запросу Invoice, вот так:

 public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
  return from p in Context.Invoices
             select new InvoiceDTO
             {
               InvoiceID = p.InvoiceID,
               InvoiceNumber = p.InvoiceNumber,
               Items = (from ii in p.Items
                             select new ItemDTO
                             {
                               ItemID = ii.ItemID,
                               Description = ii.Description
                             }).ToList()
             };
}
  

Это также отлично работает и выдает всего пару запросов. Однако следующее:

 public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
  return from p in Context.Invoices
             select new InvoiceDTO
             {
               InvoiceID = p.InvoiceID,
               InvoiceNumber = p.InvoiceNumber,
               Items = (from ii in p.Items
                             select new ItemDTO
                             {
                               ItemID = ii.ItemID,
                               Description = ii.Description
                             }).ToList(),
              Notes = (from n in Context.Notes
                             where i.InvoiceID = n.NoteParentID
                             select new NoteDTO
                             {
                               NoteID = n.NoteID,
                               Note = n.Note
                             }).ToList(),
             };
}
  

отправляет отдельный запрос в таблицу примечаний для каждой строки счета в таблице счетов. Итак, если в таблице Invoice 1000 счетов-фактур, это отправляет в базу данных что-то вроде 1001 запроса.

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

Есть ли способ переписать этот окончательный запрос таким образом, чтобы он не отправлял отдельный запрос примечания для каждого счета в таблице?

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

1. в GetInvoiceQuery , если вы временно закомментируете назначение элементов, имеет ли это значение?

2. кроме того, установлена ли у вас связь с внешним ключом для NotesID в родительских таблицах в БД?

3. @Matt. G По какой-то причине я не смог напрямую ответить ни на один из ваших комментариев. Не имеет значения, удаляю ли я элементы, и вообще нет столбца NotesID в таблице Invoice (или таблице Person и т.д.). Похоже, у Ивана Стоева было решение.

Ответ №1:

Проблема действительно заключается в коррелированном свойстве навигации по подзапросу и коллекции. У EF Core query translator по-прежнему возникают проблемы с обработкой таких подзапросов, которые на самом деле являются свойствами навигации по логической коллекции и должны были обрабатываться аналогичным образом.

Интересно, что имитация свойства навигации по коллекции с промежуточной проекцией ( let оператор в синтаксисе запроса LINQ), похоже, устраняет проблему:

 var query =
    from i in Context.Invoices
    let i_Notes = Context.Notes.Where(n => i.InvoiceID == n.NoteParentID) // <--
    select new InvoiceDTO
    {
        InvoiceID = i.InvoiceID,
        InvoiceNumber = i.InvoiceNumber,
        Items = (from ii in i.Items
                 select new ItemDTO
                 {
                     ItemID = ii.ItemID,
                     Description = ii.Description
                 }).ToList(),
        Notes = (from n in i_Notes // <--
                 select new NoteDTO
                 {
                     NoteID = n.NoteID,
                     Note = n.Note
                 }).ToList(),
    };