J'ai une requête dans EF Core 1.1.2 qui est évaluée côté client et je voudrais savoir s'il existe un meilleur moyen de la traduire en sql?
La requête:
from l in _ctx.Locations
join i in _ctx.Inventories on l.Id equals i.LocationId
join it in _ctx.Items on i.ItemId equals it.Id
where l.ProjectId == projectid
group i by new {l.Id, l.LHA} into il
select new InventoryLocations() {
Id= il.Key.Id,
LHA = il.Key.LHA,
FlaggedItems = il.Any(x=>x.Item != null && x.Item.Flagged)
}
Si non, quelles autres options ai-je?
Des modèles:
public class Location
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string Name { get; set; }
public string LHA { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
[ForeignKey("GroupLeader")]
public Guid? GroupLeaderId { get; set; }
public User GroupLeader { get; set; }
public int State { get; set; }
}
public class Inventory
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string EANCode { get; set; }
[ForeignKey("Location")]
public Guid LocationId { get; set; }
public Location Location { get; set; }
public Double ScanQty { get; set; }
[ForeignKey("ScanUser")]
public Guid? ScanUserId { get; set; }
public User ScanUser { get; set; }
public DateTime? ScanDate { get; set; }
[ForeignKey("Item")]
public Guid? ItemId { get; set; }
public Item Item { get; set; }
[ForeignKey("InventoryTask")]
public Guid? InventoryTaskId { get; set; }
public InventoryTask InventoryTask { get; set; }
[ForeignKey("CheckUser")]
public Guid? CheckUserId { get; set; }
public User CheckUser { get; set; }
public DateTime? CheckDate { get; set; }
public Double PrevQty { get; set; }
}
public class Item
{
public Guid Id { get; set; }
[ForeignKey("Project")]
public Guid ProjectId { get; set; }
public Project Project {get; set; }
public string ItemNo { get; set; }
public string EANCode { get; set; }
public string Name { get; set; }
public Double Price { get; set; }
public bool Deleted { get; set; }
public DateTime ChangeTime { get; set; }
public Double BaseQty { get; set; }
public bool Flagged { get; set; }
}
Actuellement (et cela ressemble également à l'EF Core entrant v.2.0), les requêtes GroupBy
sont traitées localement, il est donc essentiel de les éviter dans la mesure du possible.
Et votre requête semble être éligible à cela. Il n'est pas nécessaire de commencer par multiplier le jeu de données avec des jointures, puis de le regrouper.
J'ai remarqué que vous n'utilisez que reference les propriétés de navigation et les FK dans vos entités, comme un enregistrement de table de base de données et SQL. Mais EF vous permet de définir également les propriétés de navigation collection correspondantes qui vous permettent de lancer des requêtes à partir de la racine logique, éliminant ainsi le besoin de jointures et de regrouper par.
Si vous définissez une propriété de navigation de Location
à Inventory
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
alors la requête équivalente pourrait être simplement:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.Item != null && inv.Item.Flagged)
}
qui sera entièrement traduit en SQL.
Si, pour une raison quelconque, vous ne pouvez pas créer la propriété de navigation de collection ci-dessus, vous pouvez toujours commencer par les emplacements et les corréler manuellement aux inventaires:
from loc in _ctx.Locations
where loc.ProjectId == projectid
select new InventoryLocations()
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = _ctx.Inventories.Any(inv => loc.Id == inv.LocationId && inv.Item != null && inv.Item.Flagged)
}
Si vous ajoutez la propriété de navigation comme Ivan le suggère correctement:
public class Location
{
// ...
public ICollection<Inventory> Inventories { get; set; }
}
Ensuite, vous pouvez simplement créer une requête comme celle-ci:
var locations = _ctx.Locations
.Include(x => x.Inventories)
.ThenInclude(x => x.Item)
.Where(x => x.ProjectId == projectId)
.Select(loc => new InventoryLocations
{
Id = loc.Id,
LHA = loc.LHA,
FlaggedItems = loc.Inventories.Any(inv => inv.LocationId == loc.Id && inv.Item?.Flagged)
});