涉及 Max() 的 SQL 到 LINQ 转换

Posted

技术标签:

【中文标题】涉及 Max() 的 SQL 到 LINQ 转换【英文标题】:SQL to LINQ Conversion Involving Max() 【发布时间】:2021-06-01 15:40:39 【问题描述】:

我想检索最新订单和总金额的前 5 位商户。但是,我似乎无法获得总金额。我认为问题在于LastOrderGrandTotal= x.FirstOrDefault().GrandTotal。语法下面的代码是 LINQ 方法语法

var list = _ApplicationDbContext.OrderTransaction
          .Where(x => x.Status != 0)
          .GroupBy(x => x.MerchantUserId)
          .Select(x => new DashboardActiveMerchantStore  MerchantUserId = x.Key, CreatedDate = x.Max(c => c.CreatedDate), LastOrderGrandTotal= x.FirstOrDefault().GrandTotal )
          .OrderByDescending(x => x.CreatedDate)
          .Take(5)
          .ToList();

为了进一步说明上述查询的 SQL 语法是

select TOP(5) MerchantUserId,MAX(CreatedDate),GrandTotal from OrderTransaction 
group by MerchantUserId
order by CreatedDate desc 

我有一个类来存储从 LINQ 语法中检索的数据

public class DashboardActiveMerchantStore
    
        public string MerchantName  get; set; 
        public double LastOrderGrandTotal  get; set; 
        public Guid MerchantUserId  get; set; 
        public DateTime CreatedDate  get; set; 

    

【问题讨论】:

我不认为x.FirstOrDefault() 是同一个,因为您在调用它之后对列表进行排序。您最安全的选择是保留整个列表,排序,然后在排序后挑选它。 但我不认为我们可以在保留整个列表正确后对其进行排序。由于列表包含重复条目,我希望它按商家分组。我最初的计划是通过创建的数据按商家订单分组并获得总计,但我似乎无法访问总计 根据您的 SQL 查询,它应该是 LastOrderGrandTotal= x.GrandTotal。但我不确定您的 SQL 查询如何正常工作。因为您在MerchantUserId 上有groupby。所以你只能在选择中拥有MerchantUserId 和聚合函数,而没有其他列。请检查您的 SQL 查询是否运行成功。 @谭一静 那个 SQL 查询不可能工作;它会抛出...is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause 【参考方案1】:

当您使用实体框架时,一种完全不同的、更自然的方法是可能的。您可以使用virtual ICollection<...>

我想检索最近订单和总金额的前 5 名商户。

实体框架中的类

MerchantsOrderTransactions之间似乎存在一对多关系:每个Merchant都有零个或多个OrderTransactions,并且每个OrderTransaction都是恰好Merchant时的交易,即外键MechantId的Merchant指。

如果您已关注entity framework conventions,您将拥有类似于以下内容的课程:

public class Merchant

    public Guid Id get; set;      // primary key
    public string Name get; set;
    ...

    // Every Merchant has zero or more TransactionOrders (one-to-many)
    public virtual ICollection<TransactionOrder> TransactionOrders get; set;


public class TransactionOrder

    public Guid Id get; set;      // primary key
    public DateTime CreatedDate get; set;
    public int Status get; set;
    public double GrandTotal get; set;
    ...

    // every TransactionOrder is an order of a Merchant, using foreign key
    public Guid MerchantId get; set;
    public virtual Merchant Merchant get; set;

当然还有 DbContext:

public class OrderDbContext : DbContext

    public DbSet<Merchant> Merchants get; set;
    public DbSet<TransactionOrder> TransactionOrders get; set;

这就是实体框架检测表、表的列以及表之间的一对多关系所需的全部内容。只有当你偏离约定时,比如你想使用不同的属性名,或者不同的表名,你才需要属性或流畅的 API。

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

获得前 5 名商家

如果您想了解“商家……以及有关他们的某些 OrderTransactions 的一些信息”的一些信息,请从 dbContext.Merchants 开始;

如果您想了解“OrderTransactions that ...,每个都包含有关其商家的一些信息”的一些信息,请从 dbContext.OrderTransactions 开始。

我要查询最近订单和总金额前5位的商户

所以你想要Merchants。从这些商家中,您至少需要他们的IdName,以及有关他们的OrderTransactions 的一些信息。

不是所有的 OrderTransaction,只有关于其最后一个 OrderTransaction 的信息,其状态为非零。 从最后一个 OrderTransaction 中,您需要 CreatedDate 和 GrandTotal。

现在您已经获得了最后一个非零状态订单的商家,您不需要所有这些商家,您只需要具有最新 CreatedDate 的五个商家。

我希望以上是您的要求。这不是您的 SQL 所说的,而是您的 SQL 没有获取 Merchants,而是获取了 TransactionOrders 组。

var result = dbContext.Merchants
    .Select(merchant => new
    
        Id = merchant.Id,
        Name = merchant.Name,

        // from Every Merchant, get the Date and Total of their last
        // non-zero status Order
        // or null if there is no such order at all
        LastOrder = merchant.OrderTransactions
            .Where(orderTransaction => orderTransaction.Status != 0)
            .OrderByDescending(orderTransaction => oderTransaction.CreatedDate)
            .Select(orderTransaction => new
            
                CreatedDate = orderTransaction.CreatedDate,
                GrandTotal = orderTransaction.GrandTotal,
            )
            .FirstOrDefault(),
    )

Entity Framework 知道您的一对多关系。因为你使用virtual ICollection&lt;...&gt;,它会自动为你创建(Group-)Join。

现在您不想要所有商家,也不想要没有 LastOrder 的商家。他们没有任何非零状态的订单。从剩余的商户中,您只需要具有最新 LastOrder.CreatedDate 的五个商户。

所以删除所有没有 LastOrders 的商家,按 LastOrder.CreatedDate 降序排序,然后取前 5 名。

继续 LINQ:

.Where(merchant => merchant.LastOrder != null) // at leas one Order with non-zero Status
    .OrderbyDescending(merchant => merchant.LastOrder.CreatedDate)
    .Take(5).

您将拥有“Merchants (Id and Name) with their LastOrder (CreatedData and GrandTotal)”,如果需要,添加额外的 Select 以将其转换为五个 DashboardActiveMerchantStores

【讨论】:

【参考方案2】:
var lastFiveProducts = products.OrderByDescending(p => p.LastOrderGrandTotal).Take(5)

【讨论】:

【参考方案3】:

我会像这样重写查询。你甚至可能不需要FirstOrDefault,因为GroupBy 我相信总有至少一个记录。

var result = data
    .Where(x => x.Status != 0)
    .GroupBy(x => x.MerchantUserId)
    .Select(q => new
    
        q.Key,
        Orders = q.OrderByDescending(q => q.CreatedDate),
    )
    .Select(x => new DashboardActiveMerchantStore  
        MerchantUserId = x.Key, 
        CreatedDate = x.Orders.FirstOrDefault().CreatedDate,
        LastOrderGrandTotal = x.Orders.FirstOrDefault().GrandTotal,
    )
    .Take(5)
    .ToList();

【讨论】:

“使用 groupby 总有一条记录”——即使我们对一个空列表进行分组? @CaiusJard 那么没有x 可供选择,因此您不必担心子集合中没有项目。所以,是的,空列表 = 没有记录。

以上是关于涉及 Max() 的 SQL 到 LINQ 转换的主要内容,如果未能解决你的问题,请参考以下文章

使用 NOT IN 的 SQL 到 LINQ 转换

无法在转换后的查询中访问表(普通 SQL 到 LINQ)

具有XPath查询的SQL到LINQ转换

linq 怎么转换日期为字符串

Linq 到 SQL 转换失败以及本地评估

linq 怎么转换日期为字符串