【问题标题】:Entity Framework group by left join query通过左连接查询的实体框架组
【发布时间】:2020-10-06 17:49:41
【问题描述】:

结果应该是这样的

我已经开始使用 Entity Framework 一个月了,所以我对 linq 查询不熟悉。我用SQL写的查询是:

SELECT 
    om0001.CUSTOMER, om0001.ITEM_CODE, 
    SUM(om0001.AMOUNT) AS AMOUNT, 
    SUM(ep0001.EXPORT_AMOUNT) AS EXPORT_AMOUNT
FROM
    om0001
LEFT OUTER JOIN 
    ep0001 ON om0001.ID = ep0001.om0001_ID
GROUP BY 
    om0001.CUSTOMER, om0001.ITEM_CODE; 

当我在 SQL 中运行此查询时,它运行良好,因此我尝试将其转换为 linq 查询。

到目前为止我所做的是

var testjoin = from om0001 in EF.om0001
               join ep0001 in EF.ep0001 on om0001.ID equals ep0001.om0001_ID
               into jointable
               from z in jointable.DefaultIfEmpty()
               group z by new {om0001.CUSTOMER, om0001.ITEM_CODE } into g
               select new
                      {
                          CUSTOMER = g.Key.CUSTOMER,
                          ITEM_CODE = g.Key.ITEM_CODE,
                          om0001SUMamount = g.Sum(x => x.AMOUNT),
                          ep0001EXPORTsumAmount = g.Sum(y => y.EXPORT_AMOUNT)
                      };

这个 linq 查询的问题是我无法获得 om0001SUMamount。我只得到 ep0001 列数据。请帮忙

【问题讨论】:

  • 请添加您的示例数据并添加您期望的结果
  • 如果您给我们上课,我们会更容易定制答案

标签: entity-framework linq group-by sum left-join


【解决方案1】:

显然,我无法查看您的 EF 数据库,因此我创建了一些示例数据(隐含了“item”类结构):

var EF = new efClass {
    om0001 = new List<om0001item> {
        new om0001item { ID = 0, CUSTOMER = 0, ITEM_CODE = 0, AMOUNT = 10 },
        new om0001item { ID = 1, CUSTOMER = 0, ITEM_CODE = 0, AMOUNT = 20 },
        new om0001item { ID = 2, CUSTOMER = 1, ITEM_CODE = 1, AMOUNT = 30 },
        new om0001item { ID = 3, CUSTOMER = 1, ITEM_CODE = 1, AMOUNT = 40 }
    },
    ep0001 = new List<ep0001item> {
        new ep0001item { om0001_ID = 0, EXPORT_AMOUNT = -20 },
        new ep0001item { om0001_ID = 1, EXPORT_AMOUNT = -20 }        
    }
};

使用这些数据,我创建了一个查询,坦率地说,它让我感到不雅,让我失望,但这就是 LINQ 中左连接的本质:

var testjoin = from om0001 in EF.om0001
    join ep0001 in EF.ep0001 on om0001.ID equals ep0001.om0001_ID into jointable
    select new { om0001, ep0001 = jointable.DefaultIfEmpty() } into combined
    group combined by new { 
        combined.om0001.CUSTOMER,
        combined.om0001.ITEM_CODE
    } into g
    select new {
       CUSTOMER = g.Key.CUSTOMER,
       ITEM_CODE = g.Key.ITEM_CODE,
       om0001SUMamount = g.Sum(x => x.om0001.AMOUNT),
       ep0001EXPORTsumAmount = g.Sum(x => x?.ep0001.Sum(y => y?.EXPORT_AMOUNT ?? 0)) 
    };

底线是,当您按“可连接”分组时,您丢失了 ep0001 引用。因此,将 ep0001 和 om0001 都选择为一个新的“组合”对象,然后根据它进行分组。

当我创建一个具有一些类似 LINQ 功能的 javascript 库 (fluent-data) 时,我对 LINQ 开发人员产生了极大的尊重和同情。尽管如此,我不知道他们为什么不只是创建一个左连接运算符来为所有使用 LINQ 的 C# 开发人员增加更多价值。

【讨论】:

  • 你给我的代码很好。但是y =&gt; y?.EXPORT_AMOUNT ?? 0)这部分说出现了lamdba异常空值错误所以我将该部分更改为g.Sum(y =&gt; y.ep0001.Sum(x=&gt; x.EXPORT_AMOUNT))它没有显示错误但是当我运行程序时它说空值异常。这里有什么问题?
  • 我巧妙地编辑了代码。对于有问题的行,请尝试g.Sum(x =&gt; x?.ep0001?.Sum(y =&gt; y?.EXPORT_AMOUNT ?? 0))
  • 还是同样的错误。错误提示“表达式树 lambda 可能不包含空传播运算符”
  • 这是因为当实体框架与 SQL 一起工作时,它必须进行翻译,而在使用 C# 对象时则不必进行翻译。试试g.Sum(x =&gt; x == null ? null : x.ep0001.Sum(y =&gt; y == null ? null : y.EXPORT_AMOUNT ?? 0)) 如果这不起作用,希望其他人可以帮助你,或者如果没有,我明天再看看,但我的能力可能会受到这种特殊情况的限制。
  • 感谢您的支持。它对我有很大帮助。我尝试了代码,但现在它给出了另一个错误,即“无法确定条件表达式的类型,因为 'int' 和 null 之间没有隐式转换。
【解决方案2】:

所以你有一个带有Oms 的表(实际上是 Om00001,但我不会一遍又一遍地写所有那些 0001),还有一个带有 Eps 的表,你有一个一对多OmsEps的关系:每一个Om有零个或多个Eps,每一个Ep只属于一个Om,即外键EpId所指的Om

如果您关注了Entity Framework code first conventions,您将获得类似于以下的课程:

class Om
{
    public int Id {get; set;}
    public string Customer {get; set;}
    public string ItemCode {get; set;}
    ...

    // Every Om has zero or more Eps (one-to-many)
    public virtual ICollection<Ep> Eps {get; set;}
}

class Ep
{
    public int Id {get; set;}
    public int Amount {get; set;}
    public int ExportAmount {get; set;}
    ...

    // every Ep belongs to exactly one Om, using foreign key
    public int OmId {get; set;}
    public virtual Om Om {get; set;}
}

这足以让实体框架检测您的一对多关系。因为我遵循约定,所以不需要 Attributes,也不需要 fluent API。如果你想要不同的表名或列,你需要流畅的 API/属性。

在实体框架中,非虚拟属性表示表的列,虚拟属性表示表之间的关系(一对多、多对多……)

使用 GroupJoin 的解决方案

var result = dbContext.Oms.GroupJoin(dbContext.Eps,
    om => om.Id,                   // from every Om take the primary key
    ep => ep.OmId,                 // from every ep take the foreign key to the Om

    (om, epsOfThisOm) => new       // from every om and all eps having the correct foreign key
    {                              // make one new object
        // Select only the properties that you plan to use:
        Customer = om.Customer,
        ItemCode = om.ItemCode,

        Amount = epsOfThisOm.Sum(ep => ep.Amount);
        ExportAmount = epsOfThisOm.Sum(ep => ep.ExportAmount);
    });

使用虚拟 ICollection 的解决方案

您也可以使用虚拟 ICollection,而不是执行 GroupJoin。

要求:从每个 Om 中,给我 Customer 和 ItemCode,以及其 Eps 的所有 Amounts 的总和,以及其 Eps 的所有 ExportAmounts 的总和。

var result = dbContext.Oms.Select(om => new
{
    Customer = om.Customer,
    ItemCode = om.ItemCode,

    Amount = om.Eps.Sum(ep => ep.Amount);
    ExportAmount = om.Eps.Sum(ep => ep.ExportAmount);
});        

这看起来更整洁,更直接符合您的要求。实体框架知道关系,并会为你做正确的 GroupJoin。

【讨论】:

  • 非常感谢。我正在尝试您的代码,但遇到了问题。 var result = EF.om0001.GroupJoin(EF.ep0001, om0001 =&gt; om0001.ID, ep0001 =&gt; ep0001.om0001_ID, (om0001, epsOfThisOm) =&gt; new { Customer = om0001.CUSTOMER, ItemCode = om0001.ITEM_CODE, Amount = epsOfThisOm.Sum(ep =&gt; om0001.AMOUNT), ExportAmount = epsOfThisOm.Sum(ep =&gt; ep.EXPORT_AMOUNT) }); 这是我尝试的,因为我找不到 ep.Amount。 ep 没有 Amount 不知道原因。 Amount = om.Eps.Sum(ep =&gt; ep.Amount);
  • 你没有写你的 ep0001 类。显然它没有属性Amount。最好编辑您的问题,在最后添加 om00001 和 ep00001 的相关部分,然后编写您尝试过的代码。没完没了的清单或评论对未来的读者没有帮助。
猜你喜欢
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多