带有连接、组和求和的 EF Linq 查询

EF Linq query with join, group and sum

尝试构造一个执行简单内部联接的 LINQ 查询,对数据进行分组并对其中两列求和。从我看到的例子来看,它看起来相当简单,但我一定是错过了一些东西。

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

我创建的查询如下所示

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

一个问题似乎与 Name 列有关,这是我想从与 Employee 的连接中获得的唯一值。我想了解如何正确访问该列并更好地了解如何构建整个查询。

EmployeeName = c.Name 无效,我尝试过的其他一些组合也无效。

您必须将 名称 添加到分组键:

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

所以你有两个 table:EmployeesInventories。这两者之间存在一对多关系:每个 Employee 都有零个或多个 Inventories;每个Inventory恰好是一个Employee的Inventory,即外键EmployeeId所指的Employee。

要求: 从每个员工那里获取他的 ID 和姓名,以及他所有的 OffSite 和 OnSite 库存总数。

由于您使用的是 entity framework,因此可以通过三种方法完成此操作。一种是自己做 (Group-)Join,另一种是让 entity framework 做 (Group-)Join,最后,最直观的部分是使用 virtual ICollection<Inventory.

自己做 GroupJoin

每当您有一对多关系时,例如学校与他们的学生、客户与他们的订单或员工与他们的库存,并且您想从“一”端开始,请考虑使用其中之一Queryable.GroupJoin.

的重载

另一方面,如果你想从“多”这边开始,如果你想要Student和他就读的学校,Order和下单的Customer,考虑使用Queryable.Join

你想获取“员工及其库存的(一些信息),所以我们将使用 GroupJoin。我将使用 GroupJoin 的重载和参数结果Selector,这样我们就可以指定我们想要的结果。

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

让 Entity Framework 执行 GroupJoin

这个感觉更自然一些,我们根据要求为每位员工 Select 一个 InventoryTotal。

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

使用虚拟 ICollections

这张感觉最自然。在没有真实数据库的情况下也很容易对您的使用情况进行单元测试。

如果您遵循了 entity framework conventions,您将 类 类似于:

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

这足以让entity framework检测tables、tables的列以及与tables的关系(一对一许多,多对多,...)。仅当您想偏离约定时:tables 和列的不同标识符、非默认列类型等需要属性或流畅的 API。

In Entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables.

外键是table中的一列,因此是非虚拟的。 Inventory 没有 Employee 列,因此 属性 Employee 是虚拟的。

一旦定义了虚拟 ICollection,查询就很简单了:

要求: 从每个员工那里获取他的 ID 和姓名,以及他所有的 OffSite 和 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(),    
});

简单的comme bonjour!