【问题标题】:Entity Framework Core: Group by using calculated fieldEntity Framework Core:使用计算字段分组
【发布时间】:2020-08-14 00:49:49
【问题描述】:

我正在尝试编写查询。我目前所拥有的如下所示。

var x = from d in DbContext.StorageDetails

        let state = (d.ReleaseDate.HasValue && d.ReleaseDate < date) || (d.TakeOrPayEndDate.HasValue && d.TakeOrPayEndDate < date) ?
                StorageState.Closed :
                (d.RailcarNumber == null || d.RailcarNumber == string.Empty) ?
                    d.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
                    (d.ArrivalDate.HasValue && d.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute

        group d by new
        {
            d.CustomerId,
            d.Customer.Name,
            d.LocationId,
            d.Location.City,
            d.Location.State
        } into g

        select new
        {
            // ...
        };

给我带来麻烦的部分是我想在每个项目中包含计算出的state 值。我不想按此值进行分组,但我希望能够对其求和。

// Note: This is after my group statement
select new
{
    // state is a let variable and not part of x!
    TotalOpen = g.Sum(x => x.state == StorageState.Open),
    TotalClosed = g.Sum(x => x.state == StorageState.Closed),
    // Etc.
};

有没有办法做到这一点?在group by 之前,我似乎无法select 我自己的一组列。如何将此计算字段插入到每个项目中?

注意:StorageState 是一个枚举。我可以在所有这些表达式中轻松地将其转换为int。我可以弄清楚那部分,但弄清楚它与我在这里的问题是分开的。

【问题讨论】:

标签: c# entity-framework asp.net-core .net-core entity-framework-core


【解决方案1】:

我不知道您将如何将其编写为查询表达式,也不知道 EF 是否可以将该表达式转换为 sql。我会做类似以下的事情;

var query = ....
     // Select all the values you need
     .Select(d => new {
        d.CustomerId,
        d.Customer.Name,
        d.LocationId,
        d.Location.City,
        d.Location.State,
        ....
        state = [insert expression]
     })
     // group by this key
     .GroupBy(d => new {
        d.CustomerId,
        d.Name,
        d.LocationId,
        d.City,
        d.State
     }, 
     // and define the final result set
     (d, g) => new {
        d.CustomerId,
        d.Name,
        d.LocationId,
        d.City,
        d.State,
        TotalOpen = g.Sum(x => x.state == StorageState.Open ? 1 : 0),
        TotalClosed = g.Sum(x => x.state == StorageState.Closed ? 1 : 0)
     });

我认为 sql EF 会生成,将第一个 select 放在 from 子句中,由典型的 select / group by 语句包围。

【讨论】:

  • 是的,也许。我使用 LINQ 表达式的问题是我似乎无法在我的 group by 之前进行选择。在这里,这就是你正在做的。如果我能成功,我会告诉你的。
  • 是的,projection first 会把translation从水中吹出来,你需要在它之后ToList 并实现它。 Ef 在分组方面更严格一些,但是如果您只在帖子组中使用 聚合 projection可能 想办法。其他选项只是制作一个 raw sql 查询,或者在 存储过程 中执行此操作以使其全部在 DB 上运行
  • 如果EF不能处理,你也可以写一个“自定义数据库函数”来插入一个(CASE ... END)sn-p的原始sql。
  • 即使在简化了我的state 计算之后,它仍然抱怨g.Count()s。我想知道它是否不喜欢分组前的Select()。我会继续尝试。
  • @JonathanWood 让它工作的唯一方法是在选择后使用ToList。尝试在Count 中进行状态计算,这将是丑陋的 AF,但它有更多的工作机会。但唉,我怀疑它会。我认为您可能需要查看 Raw SQL 或 SP。以下是关于 EFCore 和 GroupBy 的限制的一些话docs.microsoft.com/en-us/ef/core/querying/…
【解决方案2】:

这个呢:

TotalOpen = g.Sum(x => (x.ReleaseDate.HasValue && x.ReleaseDate < date) || (x.TakeOrPayEndDate.HasValue && x.TakeOrPayEndDate < date) ?
            StorageState.Closed :
            (x.RailcarNumber == null || x.RailcarNumber == string.Empty) ?
                x.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
                (x.ArrivalDate.HasValue && x.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute
 == StorageState.Open? 1 : 0)

你会重复整个表达式四次。

【讨论】:

  • 我在我的 cmets 中解释了 state 不是 x 的成员。你没听懂问题。
  • @JonathanWood 最后使用三元运算符修复它。
【解决方案3】:

这里似乎发生了两件事:

  1. StorageDetails 是从 DbContext 查询的,并且不知何故,对于找到的每个项目 (d),正在处理一些执行以确定 state 的值...
  2. 为每个d 确定state,然后将所有d 分组,然后仅选择d 的少数属性后...

这可能是另一种方法:

var x = DbContext.StorageDetails
    .GroupBy(d => new
    {
        d.CustomerId,
        d.Name,
        d.LocationId,
        d.City,
        d.State
    }).Select(d => new
    {
        d.CustomerId,
        d.Name,
        d.LocationId,
        d.City,
        d.State,
        d.state = getState(d)
    }).ToList();

int getState(d) 
{
    return (d.ReleaseDate.HasValue && d.ReleaseDate < date) || (d.TakeOrPayEndDate.HasValue && d.TakeOrPayEndDate < date) ?
                StorageState.Closed :
                (d.RailcarNumber == null || d.RailcarNumber == string.Empty) ?
                    d.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
                    (d.ArrivalDate.HasValue && d.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute;
}

var TotalOpen = x.Count(x => x.state == StorageState.Open),
var TotalClosed = x.Count(x => x.state == StorageState.Closed)

基本上你首先从数据库组中获取你的数据。分组后,然后选择特定属性并引入一个名为state 的属性...但是状态是计算的,使用另一个从数据库中获取一行的函数从enum StorageState 返回一个值,就像您在问题上所做的那样。然后将结果作为列表返回。

只有在获得列表后,才能更轻松地检查返回的哪些行包含openclosed...

现在您可以进行正常循环并遍历您的 x 并计算 TotalClosedTotalClosed

【讨论】:

    猜你喜欢
    • 2021-02-02
    • 2020-04-23
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 2020-09-14
    • 2019-04-02
    • 2018-09-02
    相关资源
    最近更新 更多