Al intentar construir una consulta LINQ que realiza una combinación interna simple, agrupa los datos y suma dos de las columnas. Por los ejemplos que he visto, parece bastante sencillo, pero debo haber perdido algo en el camino.

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 consulta que he creado se ve así

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 problema parece ser con la columna Nombre , el único valor que quiero obtener de la unión con Empleado. Me gustaría comprender cómo acceder correctamente a esa columna y comprender mejor cómo construir esta consulta en su conjunto.

EmployeeName = c.Name no es válido, ni algunas otras combinaciones que he probado.

-1
rudeboy 13 mar. 2021 a las 02:02

2 respuestas

La mejor respuesta

Entonces tienes dos tablas: Employees y Inventories. Existe una relación de uno a muchos entre estos dos: cada empleado tiene cero o más inventarios; cada Inventario es el Inventario de exactamente un Empleado, es decir, el Empleado al que se refiere la clave externa EmployeeId.

Requisito: de cada Empleado obtenga su Id. y Nombre, y el total de todos sus inventarios OffSite y OnSite.

Dado que está utilizando el marco de la entidad, hay tres métodos para hacer esto. Una es hacer la (Group-) Join usted mismo, la otra es dejar que el marco de la entidad haga la (Group-) Join, y finalmente, la parte más intuitiva es usar virtual ICollection<Inventory.

Haz el grupo Únete

Siempre que tenga una relación de uno a varios, como las escuelas con sus estudiantes, los clientes con sus pedidos o los empleados con sus inventarios, y desee comenzar por el lado "uno", considere utilizar una de las sobrecargas de Queryable.GroupJoin.

Por otro lado, si desea comenzar por el lado "Muchos", si desea que el Alumno con la Escuela a la que asiste, el Pedido con el Cliente que realizó el pedido, considere usar Queryable.Join

Desea obtener "Empleados con (alguna información sobre) sus inventarios, así que usaremos un GroupJoin. Usaré la sobrecarga de GroupJoin con un parámetro resultSelector, para que podamos especificar lo que queremos como resultado".

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(),
});

Deje que Entity Framework haga el GroupJoin

Este se siente un poco más natural, para cada Empleado seleccionamos un Total de Inventario, según lo solicitado.

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(),
});

Utilice las ICollections virtuales

Este se siente más natural. También es muy fácil realizar una prueba unitaria de su uso sin una base de datos real.

Si ha seguido las convenciones del marco de la entidad, tener clases similares a:

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;}  
}

Esto es suficiente para que el marco de la entidad detecte las tablas, las columnas de las tablas y las relaciones con las tablas (uno a muchos, muchos a muchos, ...). Solo si desea desviarse de las convenciones: diferentes identificadores para tablas y columnas, tipos de columnas no predeterminados, etc. Se necesitan atributos o API fluida.

En Entity framework, las columnas de las tablas están representadas por las propiedades no virtuales. Las propiedades virtuales representan las relaciones entre las tablas.

La clave externa es una columna en la tabla, por lo tanto, no es virtual. El Inventario no tiene una columna de Empleado, por lo que la propiedad Empleado es virtual.

Una vez que haya definido la ICollection virtual, la consulta es simple:

Requisito: de cada Empleado obtenga su Id. y Nombre, y el total de todos sus inventarios OffSite y 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 mar. 2021 a las 15:28

Tienes que agregar Nombre a la clave de agrupación:

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 mar. 2021 a las 09:44