【问题标题】:Linq query that involves doing group by with two tables that are not directly connected涉及对未直接连接的两个表进行分组的 Linq 查询
【发布时间】:2014-11-21 21:25:46
【问题描述】:

在具有这些类的 EF 模型中:

 class BoxOutput
 {
     public long BoxId { get; set; }
     public virtual Box Box { get; set; }
     public long BoxId { get; set; }
     public long CategoryId { get; set; }
     public virtual Category Category { get; set; }
     public long? ColorId { get; set; }
     public virtual Category Color { get; set; }
     public decimal Weight { get; set; }

     // ...and other irrelevant properties
 }

 class BoxInput
 {
     public long BoxId { get; set; }
     public virtual Box Box { get; set; }
     public long BoxId { get; set; }
     public long CategoryId { get; set; }
     public virtual Category Category { get; set; }
     public long? ColorId { get; set; }
     public virtual Category Color { get; set; }
     public decimal Weight { get; set; }

     // ...and other irrelevant properties
 }


...我怎样才能对特定的框(例如boxId = 12)进行 LINQ 查询,返回这个?:

  category.name    color.name      inputWeightsSum      outputWeightsSum
 ---------------------------------------------------------------------------
     c             null               0                    0
     c             red                0                    0
     c             blue               0                    0
     m             null               0                    0
     m             red                0                    0
     m             blue               0                    0
                           ....

我目前几乎实现了这一点,但是在做“笛卡尔积”时,“可选颜色”给我带来了麻烦:我没有显示空值......

这就是我所拥有的。

var boxesInputs = dbContext.BoxesInputs
.Where(c => c.BoxId == 12)
.GroupBy(c => new { c.CategoryId, c.ColorId })
.Select(g => new
{
    categoryId = g.Key.CategoryId,
    colorId = g.Key.ColorId,
    sumOfWeights = g.Sum(r => (decimal?)r.Weight) ?? 0,
}).ToList();

var boxesOutputs = dbContext.BoxesOutputs
.Where(c => c.BoxId == 12)
.GroupBy(c => new { c.CategoryId, c.ColorId })
.Select(g => new
{
    categoryId = g.Key.CategoryId,
    colorId = g.Key.ColorId,
    sumOfWeights = g.Sum(r => (decimal?)r.Weight) ?? 0,
}).ToList();

var categoriesAndColors = dbContext.Categories.AsEnumerable()
.SelectMany(category => dbContext.Colors.AsEnumerable()
.Select(color => new
{
    category = new
    {
        categoryId = category.CategoryId,
        name = category.Name,
    },
    color = new
    {
        colorId = color.ColorId,
        name = color.Name,
    },
    inputWeightsSum = boxesInputs.Where(r => r.categoryId == category.CategoryId && r.colorId == color.ColorId).Sum(r => (decimal?) r.sumOfWeights) ?? 0,
    outputWeightsSum = boxesOutputs.Where(r => r.categoryId == category.CategoryId && r.colorId == color.ColorId).Sum(r => (decimal?)r.sumOfWeights) ?? 0,
})).ToList();

在前面的代码中,前两个查询返回:

  category.name    color.name      inputWeightsSum
 -------------------------------------------------------
     c             null               0
     c             red                0
     c             blue               0
     m             null               0
     m             red                0
     m             blue               0 
                           ....

  category.name    color.name      outputWeightsSum
 -------------------------------------------------------
     c             null               0
     c             red                0
     c             blue               0
     m             null               0
     m             red                0
     m             blue               0 
                           ....

第三个加入这两个。我想我需要改进那个连接,因为我正在丢失空值。

此外,此代码使用内存中的代码,我希望它是一个单 sql 查询(linq-to-entities 而不是 linq-to-objects 与 linq-to-entities 混合)。是否可以?假设 BoxOutput 和 BoxInput 是两个不同的表,并且它们没有直接连接。或者无论如何我最终都会得到 3 个查询?

【问题讨论】:

  • 我想我需要一个 GroupJoin
  • 你只是想得到一个笛卡尔积吗?
  • 我的代码的前两个查询被授予正确的。我只想将这两个合并为一个结果,按“(类别,颜色)”分组

标签: c# linq entity-framework


【解决方案1】:

您从两个表中选择相同的匿名类型,将它们合并在一起,然后进行分组并选择。像这样的:

dbContext.BoxesInputs
.Select(g => new TempClass
{
    CategoryId = g.CategoryId,
    ColorId = g.ColorId,
    InputWeight = r.Weight,
    OutputWeight = 0,
})
.Union(dbContext.BoxesOutputs
    .Select(g => new TempClass
    {
        CategoryId = g.CategoryId,
        ColorId = g.ColorId,
        InputWeight = 0,
        OutputWeight = r.Weight
    })
)
.GroupBy(c => new { c.CategoryId, c.ColorId })
.Select(g => new
{
    categoryId = g.Key.CategoryId,
    colorId = g.Key.ColorId,
    sumOfInputWeights = g.Sum(r => r.InputWeight),
    sumOfOutputWeights = g.Sum(r => r.OutputWeight),
}).ToList();

public class TempClass
{
    public int CategoryID { get; set; }
    public int ColorID { get; set; }
    public decimal InnerWeight { get; set; }
    public decimal OuterWeight { get; set; }
}

【讨论】:

  • 但是我只会得到一列“sumOfWeights”,我需要“inputsSumOfWeights”和“outputsSumOfWeights”,分别在同一个结果中
  • 检查我的编辑。那应该行得通。不确定是否最佳,但可能比将整个表拉入内存要好。
  • 但是我认为它不能转换为具有其他表参数的联合的 SQL?
  • 这正是联合的意图。 SQL 中也有一个UNION。不过取决于您的 LINQ 设置。没有比尝试更好的了解方法了。 Union 我的语法可能不正确,我不经常使用它。 MSDN:msdn.microsoft.com/en-us/library/vstudio/…
  • 我不确定是否可以在匿名列表上使用联合? Error: 'System.Linq.IQueryable<AnonymousType#2>' does not contain a definition for 'Union'...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-19
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多