簡體   English   中英

將 SQL 查詢轉換為 linq lambda 實體框架核心

[英]Convert SQL query to linq lambda Entity Framework Core

我需要 select 前 10 個 LINQ 查詢,其中包含 group by、sum(quantity) 和 where 子句,使用 Entity Framework Core。

有人可以幫我將此 SQL 代碼轉換為 linq lambda 嗎?

非常感謝。

 SELECT TOP 10 
     OrderItems.ProductName,
     OrderItems.ProductId,
     SUM(OrderItems.Units) AS Quantity 
 FROM
     Orders, OrderItems  
 WHERE
     OrderItems.OrderId = Orders.Id  
     AND Orders.OrderDate >= '2019-12-18 16:38:27' 
     AND Orders.OrderDate <= '2020-12-18 16:38:27' 
     AND Orders.OrderStatusId = 2 
 GROUP BY  
     ProductName, OrderItems.ProductId  
 ORDER BY 
     Quantity DESC

我試過這個 EF 查詢:

var query = (from sta in _context.Set<OrderItem>().AsQueryable()
                         join rec in _context.Set<Order>().AsQueryable() on sta.OrderId equals rec.Id
                         where rec.OrderStatusId == Convert.ToInt32(orderStatusId) && rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
                         group sta by new
                         {
                             sta.ProductName,
                             sta.ProductId
                         } into grp
                         select new OrderDto()
                         {
                             ProductName = grp.Key.ProductName,
                             ProductId = grp.Key.ProductId,
                             Quantity = grp.Max(t => t.Units),
                         }).OrderBy(x => x.Quantity).Take(Convert.ToInt32(top)).ToList();

我相信您的陳述SELECT... FROM Orders, OrderItems... WHERE OrderItems.OrderId = Orders.Id雖然看起來像CROSS JOIN ,但最終被優化為INNER JOIN

因此,假設您已經使用導航屬性設置了 model,那么使用.Include()可能會更好。 除此之外,我認為你幾乎在那里:

var query = _context.Set<OrderItem>().Include(o => o.Order)
                    .Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
                    .Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
                    .GroupBy(g => new { g.ProductName, g.ProductId })
                    .Select(grp => new OrderDto
                    {
                        ProductName = grp.Key.ProductName,
                        ProductId = grp.Key.ProductId,
                        Quantity = grp.Sum(t => t.Units)
                    })
                    .OrderBy(x => x.Quantity)
                    .Take(Convert.ToInt32(top));

這將產生以下 output:

SELECT TOP(@__p_3) [o].[ProductName], [o].[ProductId], SUM([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderId] = [o0].[Id]
WHERE ([o0].[OrderStatusId] = @__ToInt32_0) AND (([o0].[OrderDate] >= @__ToDateTime_1) AND ([o0].[OrderDate] <= @__ToDateTime_2))
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY SUM([o].[Units])

假設您無法將導航屬性添加到您的OrderItem model,那么您的代碼似乎就在那里:

var query2 = (from sta in _context.Set<OrderItem>() 
                from rec in _context.Set<Order>()
                where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId) 
                        && rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
                group sta by new
                {
                    sta.ProductName,
                    sta.ProductId
                } into grp
                select new OrderDto()
                {
                    ProductName = grp.Key.ProductName,
                    ProductId = grp.Key.ProductId,
                    Quantity = grp.Max(t => t.Units),
                }
                )
                .OrderBy(x => x.Quantity)
                .Take(Convert.ToInt32(top));

這將產生以下 SQL:

SELECT TOP(@__p_3) [o].[ProductName], [o].[ProductId], MAX([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
CROSS JOIN [Orders] AS [o0]
WHERE ((([o].[OrderId] = [o0].[Id]) AND ([o0].[OrderStatusId] = @__ToInt32_0)) AND ([o0].[OrderDate] >= @__ToDateTime_1)) AND ([o0].[OrderDate] <= @__ToDateTime_2)
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY MAX([o].[Units])

這是我的完整測試台供參考


using Microsoft.EntityFrameworkCore
using Microsoft.EntityFrameworkCore.Query.SqlExpressions
using Microsoft.EntityFrameworkCore.Query

#region EF Core 3.1 .ToSql() helper method courtesy of https://stackoverflow.com/a/51583047/12339804
public static class IQueryableExtensions
{
    public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
    {
        var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
        var relationalCommandCache = enumerator.Private("_relationalCommandCache");
        var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
        var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");

        var sqlGenerator = factory.Create();
        var command = sqlGenerator.GetCommand(selectExpression);

        string sql = command.CommandText;
        return sql;
    }

    private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
    private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}
#endregion 

public class OrderItem
{
    public int Id {get;set;}
    public int OrderId {get;set;}
    public int ProductName {get;set;}
    public int ProductId {get;set;}
    public int Units {get;set;}
    
    public Order Order {get;set;} // added navigation property for .Include() to pick up on
}

public class Order { 
    public int Id {get;set;}
    public int OrderStatusId {get;set;}
    public DateTime OrderDate {get;set;}    
}

public class OrderDto
{
    public int ProductName { get; set; }
    public int ProductId { get; set; }
    public int Quantity { get; set; }
}

class Dbc : DbContext
{
    public DbSet<Order> Orders {get;set;}
    public DbSet<OrderItem> OrderItems {get;set;}

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
            optionsBuilder.UseSqlServer(@"Server=.\SQLEXPRESS;Database=Test;Trusted_Connection=True");
        base.OnConfiguring(optionsBuilder);
    }
}

void Main()
{
    var _context = new Dbc();
    var orderStatusId = "2";
    var top = "10";
    var startDate = DateTime.Parse("2019-12-16 16:38:27");
    var endDate = DateTime.Parse("2019-12-18 16:38:27");

var query = _context.Set<OrderItem>().Include(o => o.Order)
                    .Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
                    .Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
                    .GroupBy(g => new { g.ProductName, g.ProductId })
                    .Select(grp => new OrderDto
                    {
                        ProductName = grp.Key.ProductName,
                        ProductId = grp.Key.ProductId,
                        Quantity = grp.Sum(t => t.Units)
                    })
                    .OrderBy(x => x.Quantity)
                    .Take(Convert.ToInt32(top));
    query.ToSql().Dump();

    //alternatively, trying to force a cross join syntax with extra WHERE condition. This way you don't need `public Order Order {get;set;}` navigation property on `OrderItem`
    var query2 = (from sta in _context.Set<OrderItem>() 
                from rec in _context.Set<Order>()
                where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId) 
                        && rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
                group sta by new
                {
                    sta.ProductName,
                    sta.ProductId
                } into grp
                select new OrderDto()
                {
                    ProductName = grp.Key.ProductName,
                    ProductId = grp.Key.ProductId,
                    Quantity = grp.Max(t => t.Units),
                }
                )
                .OrderBy(x => x.Quantity)
                .Take(Convert.ToInt32(top));
    query2.ToSql().Dump();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM