Utilizzando EF Core 2.2.2, ho una tabella nel mio database che viene utilizzata per memorizzare le note per molte altre tabelle. In altre parole, è una specie di tabella dei dettagli in una relazione principale-dettaglio, ma con più tabelle principali. Considera questo modello EF semplificato:
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; }
}
In questo caso, Notes può archiviare note personali o note fattura (o note fattura), sebbene diciamo solo che l'interfaccia utente non lo supporta).
Ho metodi di query impostati in questo modo:
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();
}
Tutti funzionano come previsto. Ora, supponiamo di aggiungere InvoiceItems alla query Invoice, in questo modo:
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()
};
}
Anche questo funziona alla grande, ed emette solo un paio di domande. Tuttavia, quanto segue:
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(),
};
}
invia una query separata alla tabella Note per ciascuna riga Fattura nella tabella Fattura. Quindi, se ci sono 1.000 fatture nella tabella Fattura, questo sta inviando qualcosa come 1.001 query al database.
Sembra che la sottoquery Articoli non abbia lo stesso problema perché esiste una relazione esplicita tra Fatture e Articoli, mentre non esiste una relazione specifica tra Fatture e Note (poiché non tutte le note sono correlate alle fatture).
C'è un modo per riscrivere quella query finale, in modo che non invii una query nota separata per ogni fattura nella tabella?
Il problema è in effetti la subquery correlata rispetto alla proprietà di navigazione della raccolta. Il traduttore di query EF Core ha ancora problemi nell'elaborazione di tali sottoquery, che in realtà sono proprietà logiche di navigazione della raccolta e avrebbero dovuto essere elaborate in modo simile.
È interessante notare che la simulazione della proprietà di navigazione della raccolta con proiezione intermedia ( let
all'operatore nella sintassi della query LINQ) sembra risolvere il problema:
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(),
};