【问题标题】:C# how to achieve 'group by' 'left join' and list inside a list selection in linqC#如何在linq中的列表选择中实现'group by''left join'和列表
【发布时间】:2020-11-11 19:51:02
【问题描述】:
var products = from P in product.GetAllAsync().Result
                           join PV in 
_repositoryVariation.GetAllAsync().Result on P.Id equals PV.ProductId
                           into PVLJ
                           from PV in PVLJ.DefaultIfEmpty()

                           select new ProductwithVarientsGroupDto
                           {
                               Label = P.ProductName,
                               ProductId = P.Id,
                               Items = new List<ProdcutVariantsdto>() { new ProdcutVariantsdto { Label = P.ProductName.ToString() + (PV == null ? "" : " (" + PV?.VariantName.ToString() + ")"), ProductId = P.Id, ProdVariantId = PV?.Id > 0 ? PV?.Id : 0 } }
                                 //here i need to add multiple variant records to Items, now it 
                                  // showing each item as a separate row data 
                           };
            var result = products.ToList();
            return result;

【问题讨论】:

  • 你为什么不用await
  • 看起来您使用了“通用”存储库 anti 模式,其实现返回 all 结果为GetAll。正确使用 EF Core 会更轻松、更快捷。存储库反模式是一个低级结构。 ORM 是一个更高层次的抽象。通过将它放在 EF Core 之上,您已经破坏了它而没有获得任何好处
  • 如何解决:完全删除存储库。使用适当的实体和关系并让 EF Core 创建连接。如果Product 有一个名为Variations 的属性,EF Core 将自动加载所有相关的Variations。一个简单的var products=(from product in _context.Products select new MyDTO { Label=product.Name, Items=product.Variations}).ToList() 就可以了。您可以向product.Variations 添加额外操作以仅提取特定字段
  • 根本不工作。您正在从内存中的数据库中加载 all 产品和 all 变体,然后尝试在没有索引的情况下加入。这非常慢,并且由于从数据库读取时的锁定,将阻止任何其他尝试在该表中插入或更新记录的代码。这将严重损害除最小应用程序之外的任何应用程序的可扩展性
  • 也不需要 - 如果您正确使用 EF Core,所有复杂性都会消失。我只需要编写两行代码来检索嵌套的产品和变体,无需加载任何不需要的数据

标签: c# list linq .net-core entity-framework-core


【解决方案1】:

以下查询将起作用。离开加入数据后,只需切换到客户端评估(使用 AsEnumerable()ToList())并使用 Linq 进行实际分组。 EF Core 将无法自行进行 Linq 样式分组,并且无论如何都会检索数据,因此在客户端执行此操作没有缺点:

var products = (from p in context.Products
    join v in
        context.ProductVariations on p.Id equals v.ProductId
        into g
    from v in g.DefaultIfEmpty()
    select new {product = p, variation = v})
    .AsEnumerable() // <-- switch to client-evaluation
    .GroupBy(g => g.product, g => g.variation)
    .Select(g => new ProductwithVarientsGroupDto
    {
        Label = g.Key.ProductName,
        ProductId = g.Key.Id,
        Items = g.Select(v => new ProdcutVariantsdto()
        {
            Label = g.Key.ProductName + (v == null
                ? ""
                : " (" + v.VariantName.ToString() + ")"),
            ProductId = g.Key.Id,
            ProdVariantId = v.Id > 0
                ? v.Id
                : 0
        }).ToList()
    });

正如@PanagiotisKanavos 在 cmets 中已经提到的,您的原始代码首先从数据库中检索所有实体,然后在内存中对它们执行查询。如果您想继续这样做,只需将context.Products 替换为product.GetAllAsync().Result 并将context.ProductVariations 替换为_repositoryVariation.GetAllAsync().Result。然后您也可以删除AsEnumerable(),因为您已经在客户端处理查询。


这是我用于测试的完整的示例控制台项目:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class Product
    {
        public int Id { get; set; }
        public string ProductName { get; set; }
    }

    public class ProductVariation
    {
        public int Id { get; set; }
        public int ProductId { get; set; }
        public string VariantName { get; set; }
    }
    
    public class ProductwithVarientsGroupDto
    {
        public string Label { get; set; }
        public int ProductId { get; set; }
        public List<ProdcutVariantsdto> Items = new List<ProdcutVariantsdto>();
    }
    
    public class ProdcutVariantsdto
    {
        public string Label { get; set; }
        public int ProductId { get; set; }
        public int? ProdVariantId { get; set; }
    }
    
    public class Context : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<ProductVariation> ProductVariations { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(
                    @"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63031344")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Product>().HasData(
                new Product {Id = 1, ProductName = "Car"},
                new Product {Id = 2, ProductName = "Bus"});

            modelBuilder.Entity<ProductVariation>().HasData(
                new ProductVariation {Id = 1, ProductId = 1, VariantName = "Minivan"},
                new ProductVariation {Id = 2, ProductId = 1, VariantName = "Convertible"},
                new ProductVariation {Id = 3, ProductId = 2, VariantName = "Public Transportation"},
                new ProductVariation {Id = 4, ProductId = 2, VariantName = "Shuttle"});
        }
    }

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

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

            var products = (from p in context.Products
                join v in
                    context.ProductVariations on p.Id equals v.ProductId
                    into g
                from v in g.DefaultIfEmpty()
                select new {product = p, variation = v})
                .AsEnumerable()
                .GroupBy(g => g.product, g => g.variation)
                .Select(g => new ProductwithVarientsGroupDto
                {
                    Label = g.Key.ProductName,
                    ProductId = g.Key.Id,
                    Items = g.Select(v => new ProdcutVariantsdto()
                    {
                        Label = g.Key.ProductName + (v == null
                            ? ""
                            : " (" + v.VariantName.ToString() + ")"),
                        ProductId = g.Key.Id,
                        ProdVariantId = v.Id > 0
                            ? v.Id
                            : 0
                    }).ToList()
                });

            var result = products.ToList();
            
            Debug.Assert(result.Count == 2);
            Debug.Assert(result[0].ProductId == 1);
            Debug.Assert(result[0].Items.Count == 2);
            Debug.Assert(result[0].Items[0].ProdVariantId == 1);
        }
    }
}

【讨论】:

  • 没问题。如果您想了解有关 joins 的 @PanagiotisKanavos cmets,请参阅 Relationships。特别是他的观点,不使用通用存储库模式是非常有效的。 DbSet&lt;T&gt; 已经是一个存储库,DbContext 已经是一个工作单元。有关这方面的更多信息,请参阅Is the repository pattern useful with Entity Framework Core?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-11
  • 2021-01-30
  • 1970-01-01
相关资源
最近更新 更多