Essayer de construire une requête LINQ qui effectue une simple jointure interne, regroupe les données et additionne deux des colonnes. D'après les exemples que j'ai vus, cela semble assez simple, mais j'ai dû manquer quelque chose en cours de route.

public class Employee
{
  public int Id { get; set; }
  public int Name { get; set; }
}

public class Inventory
{
  public int Id { get; set; }
  public int EmployeeId { get; set; }
  public decimal OffSite { get; set; }
  public decimal OnSite { get; set; }
}

public class InventoryTotal
{
  public int EmployeeId { get; set; }
  public string EmployeeName { get; set; }
  public decimal EmployeeOffSite { get; set; }
  public decimal EmployeeOnSite { get; set; }
}

La requête que j'ai créée ressemble à ceci

var result = from a in db.Inventory                            
             join b in db.Employee on a.EmployeeId equals b.Id
             group new { a, b } by a.EmployeeId into c                            
             select new InventoryTotal
             {
               EmployeeId = c.Key,
               EmployeeName = c.Name,
               EmployeeOffSite = c.Sum(d => d.a.OffSite),
               EmployeeOnSite = c.Sum(d => d.a.OnSite)
             };

Un problème semble être lié à la colonne Nom , la seule valeur que je souhaite obtenir de la jointure avec l'employé. Je voudrais comprendre comment accéder correctement à cette colonne et mieux comprendre comment construire cette requête dans son ensemble.

EmployeeName = c.Name n'est pas valide, ni aucune autre combinaison que j'ai essayée.

-1
rudeboy 13 mars 2021 à 02:02

2 réponses

Meilleure réponse

Vous avez donc deux tables: Employees et Inventories. Il existe une relation un-à-plusieurs entre ces deux: chaque employé a zéro inventaire ou plus; chaque inventaire est l'inventaire d'exactement un employé, à savoir l'employé auquel se réfère la clé étrangère EmployeeId.

Condition: de chaque employé obtenez son identifiant et son nom, ainsi que le total de tous ses inventaires OffSite et OnSite.

Puisque vous utilisez le framework d'entité, il existe trois méthodes pour le faire. La première consiste à faire le (Group-) Join vous-même, l'autre est de laisser le framework d'entité faire le (Group-) Join, et enfin, la partie la plus intuitive est d'utiliser le virtual ICollection<Inventory.

Faites le groupe

Chaque fois que vous avez une relation un-à-plusieurs, comme les écoles avec leurs élèves, les clients avec leurs commandes ou les employés avec leurs inventaires, et que vous souhaitez commencer du côté "un", envisagez d'utiliser l'une des surcharges de Queryable.GroupJoin.

Par contre, si vous voulez commencer par le côté "Beaucoup", si vous voulez que l'Etudiant avec l'école qu'il fréquente, la Commande avec le Client qui a passé la commande, pensez à utiliser Queryable.Join

Vous voulez récupérer "les employés avec (des informations sur) leurs inventaires, nous allons donc utiliser un GroupJoin. J'utiliserai la surcharge de GroupJoin avec un paramètre resultSelector, afin que nous puissions spécifier ce que nous voulons comme résultat.

var inventoryTotals = dbContext.Employees.GroupJoin(dbContext.Inventories,

employee => employee.Id,            // from every Employee take the primary key
inventory => inventory.EmployeeId,  // from every Inventory take the foreign key

// parameter resultSelector: from every Employee, and all Inventories that have a foreign
// key that refers to this Employee, make one new
(employee, inventoriesOfThisEmployee) => new InventoryTotal
{
    EmployeeId = employee.Id,
    EmployeeName = employee.Name,

    EmployeeOffSite = inventoriesOfThisEmployee
        .Select(inventory => inventory.OffSite).Sum(),
    EmployeeOnSite = inventoriesOfThisEmployee
        .Select(inventory => inventory.OnSite).Sum(),
});

Laisser Entity Framework faire le GroupJoin

Celui-ci semble un peu plus naturel, pour chaque employé, nous sélectionnons un total d'inventaire, comme demandé.

var inventoryTotals = dbContext.Employees.Select(employee => new InventoryTotal
{
    // Select the Employee properties that you want.
    EmployeeId = employee.Id,
    EmployeeName = employee.Name,

    // Get the inventories of this Employee:
    EmployeeOffSite = dbContext.Inventories
        .Where(inventory => inventory.EmployeeId == employee.Id)
        .Select(inventory => inventory.OffSite).Sum(),

    EmployeeOnSite = dbContext.Inventories
        .Where(inventory => inventory.EmployeeId == employee.Id)
        .Select(inventory => inventory.OnSite).Sum(),
});

Utilisez les ICollections virtuelles

Celui-ci semble le plus naturel. Il est également très facile de tester votre utilisation unitaire sans véritable base de données.

Si vous avez suivi les conventions du cadre d'entité, vous ont des classes similaires à:

public class Employee
{
    public int Id { get; set; }
    public int Name { get; set; }
    ... // other properties

    // Every Employee has zero or more Inventories (one-to-many)
    public ICollection<Inventory> Inventories {get; set;}
}

public class Inventory
{
    public int Id { get; set; }
    public decimal OffSite { get; set; }
    public decimal OnSite { get; set; }
    ... // other properties

    // Every Inventory is the Inventory of exactly one Employee, using foreign key
    public int EmployeeId { get; set; }
    public virtual Employee Employee {get; set;}  
}

Cela suffit au framework d'entité pour détecter les tables, les colonnes des tables et les relations avec les tables (un-à-plusieurs, plusieurs-à-plusieurs, ...). Uniquement si vous souhaitez vous écarter des conventions: différents identifiants pour les tables et les colonnes, les types de colonnes non par défaut, etc. Des attributs ou une API fluide sont nécessaires.

Dans Entity Framework, les colonnes des tables sont représentées par les propriétés non virtuelles. Les propriétés virtuelles représentent les relations entre les tables.

La clé étrangère est une colonne de la table, elle n'est donc pas virtuelle. L'inventaire n'a pas de colonne Employé, donc la propriété Employé est virtuelle.

Une fois que vous avez défini l'ICollection virtuelle, la requête est simple:

Condition: de chaque employé obtenez son identifiant et son nom, ainsi que le total de tous ses inventaires OffSite et OnSite.

var inventoryTotals = dbContext.Employees.Select(employee => new InventoryTotal
{
    // Select the Employee properties that you want.
    EmployeeId = employee.Id,
    EmployeeName = employee.Name,

    EmployeeOffSite = employee.Inventories
        .Select(inventory => inventory.OffSite).Sum(),

    EmployeeOnSite = employee.Inventories
        .Select(inventory => inventory.OnSite).Sum(),    
});

Simple comme bonjour!

1
Harald Coppoolse 14 mars 2021 à 15:28

Vous devez ajouter Nom à la clé de regroupement:

var result = from a in db.Inventory                            
             join b in db.Employee on a.EmployeeId equals b.Id
             group a by new { a.EmployeeId, a.Name } into c                            
             select new InventoryTotal
             {
               EmployeeId = c.Key.EmployeeId,
               EmployeeName = c.Key.Name,
               EmployeeOffSite = c.Sum(d => d.OffSite),
               EmployeeOnSite = c.Sum(d => d.OnSite)
             };
0
Svyatoslav Danyliv 13 mars 2021 à 09:44