涉及 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 名商户。
实体框架中的类
Merchants
和OrderTransactions
之间似乎存在一对多关系:每个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
。从这些商家中,您至少需要他们的Id
、Name
,以及有关他们的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<...>
,它会自动为你创建(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 转换的主要内容,如果未能解决你的问题,请参考以下文章