EF核心分组多个联接表

时间:2020-07-22 13:56:14

标签: c# linq entity-framework-core

尝试对已联接在一起的3个表进行分组时,在运行时出现错误。我知道我可以在sql中执行此操作,但如果可能的话,我会尝试保持EF Core。 EF Core抱怨说linq表达式无法翻译,或者我需要切换到客户端评估。目标是在sql服务器中运行这种简单的联接和分组。

from line in _context.OrderLines
    join item in _context.Items on line.ItemId equals item.ItemId
    join supplier in _context.Suppliers on item.SupplierId equals supplier.SupplierId
group line by supplier.Name

上面的分组在编译时是有效的查询表达式,如果没有随后的选择将失败,但将详细说明我的最终用途:我希望能够由供应商对我的行数量进行求和。分组后,这应该是简单的汇总。

from line in _context.OrderLines
    join item in _context.Items on line.ItemId equals item.ItemId
    join supplier in _context.Suppliers on item.SupplierId equals supplier.SupplierId
group line by supplier.Name into lineGrouping
select new { Name = lineGrouping.Key, Qty = lineGrouping.Sum(x => x.Qty) }

执行将导致以下错误:

System.InvalidOperationException:不支持客户端GroupBy。 在 Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)

1 个答案:

答案 0 :(得分:2)

有关Complex Query Operators: GroupBy的常规信息,请查看EF Core官方文档。

由于没有数据库结构可以表示IGrouping,因此GroupBy运算符在大多数情况下没有转换。将聚合运算符应用于每个返回标量的组时,可以将其转换为关系数据库中的SQL GROUP BY。 SQL GROUP BY也有限制。它要求您仅按标量值分组。投影只能包含分组键列或应用于列的任何聚合。 EF Core识别此模式并将其转换为服务器[...]

现在,对于您的具体示例,它可以正常工作。这是一个示例控制台项目,以证明这一点:

using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class OrderLine
    {
        [Key]
        public int OrderLineId { get; set; }
        public int ItemId { get; set; }
        public int Qty { get; set; }
    }

    public class Supplier
    {
        [Key]
        public int SupplierId { get; set; }
        public string Name { get; set; }
    }

    public class Item
    {
        [Key]
        public int ItemId { get; set; }
        public int SupplierId { get; set; }
    }
    
    public class Context : DbContext
    {
        public DbSet<OrderLine> OrderLines { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
        public DbSet<Item> Items { get; set; }
        
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63040380")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<OrderLine>()
                .HasData(
                    new OrderLine {OrderLineId = 1, ItemId = 1, Qty = 100},
                    new OrderLine {OrderLineId = 2, ItemId = 2, Qty = 42},
                    new OrderLine {OrderLineId = 3, ItemId = 3, Qty = 21});

            builder.Entity<Supplier>()
                .HasData(
                    new Supplier {SupplierId = 1, Name = "Supplier A"},
                    new Supplier {SupplierId = 2, Name = "Supplier B"});
            
            builder.Entity<Item>()
                .HasData(
                    new Item {ItemId = 1, SupplierId = 1},
                    new Item {ItemId = 2, SupplierId = 1},
                    new Item {ItemId = 3, SupplierId = 2});
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var supplierNameAndQuantity = (from line in context.OrderLines
                join item in context.Items on line.ItemId equals item.ItemId
                join supplier in context.Suppliers on item.SupplierId equals supplier.SupplierId
                group line by supplier.Name into lineGrouping
                select new {Name = lineGrouping.Key, Qty = lineGrouping.Sum(x => x.Qty)})
                .ToList();

            Debug.Assert(supplierNameAndQuantity.Count == 2);
            Debug.Assert(supplierNameAndQuantity[0].Name == "Supplier A");
            Debug.Assert(supplierNameAndQuantity[0].Qty == 142);
            Debug.Assert(supplierNameAndQuantity[1].Name == "Supplier B");
            Debug.Assert(supplierNameAndQuantity[1].Qty == 21);
        }
    }
}

查询被转换为以下正确的SQL:

SELECT [s].[Name], SUM([o].[Qty]) AS [Qty]
FROM [OrderLines] AS [o]
INNER JOIN [Items] AS [i] ON [o].[ItemId] = [i].[ItemId]
INNER JOIN [Suppliers] AS [s] ON [i].[SupplierId] = [s].[SupplierId]
GROUP BY [s].[Name]
相关问题