EFCore 中具有多对多关系的 GroupBy
GroupBy with many-to-many relationship in EFCore
产品:
+------+---------+--------------+
| id | name | status |
+------+---------+--------------+
| 1 | foo | in stock |
| 2 | bar | in stock |
| 3 | baz | out of stock |
+------+---------+--------------+
项目:
+------+---------+--------+
| id | name | Cost |
+------+---------+--------+
| 1 | item1 | 10 |
| 2 | item2 | 20 |
| 3 | item3 | 5 |
+------+---------+--------+
中级 table:
+--------------+------------+
| product_id | items_id |
+--------------+------------+
| 1 | 1 |
| 1 | 3 |
| 2 | 2 |
+--------------+------------+
多对多配置:
modelBuilder.Entity<Product>()
.HasMany<Item>(x => x.Items)
.WithMany(y => y.Products)
.UsingEntity(p => p.ToTable("ProductItems"));
结果应该是:
{
'TotalItems': 3,
'Items': [
{ 'Status': 'in stock', 'Cost': 30 },
{ 'Status': 'out of stock', 'Cost': 5 }
]
}
特定产品的项目总数:
context.Products
.Where(x => x.Name.Equals("foo"))
.SelectMany(x => x.Items)
.Count();
现在我想根据产品状态对项目进行分组。类似于:
context.Products
.Where(x => x.Name.Equals("foo"))
.SelectMany(x => x.Items)
.GroupBy(Status, (g,x) => new {
Status = ...
Cost = ...
})
.ToList()
有什么想法可以实现所需的输出吗?
具有中间实体的解决方案
实体:
public class MyContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<ProductItem> ProductsItems => Set<ProductItem>();
public DbSet<Item> Items => Set<Item>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductItem>()
.HasKey(pi => new { pi.ProductId, pi.ItemId });
modelBuilder.Entity<Product>()
.HasMany(p => p.Items)
.WithOne(pi => pi.Product)
.HasForeignKey(pi => pi.ProductId);
modelBuilder.Entity<Item>()
.HasMany(i => i.Products)
.WithOne(pi => pi.Item)
.HasForeignKey(pi => pi.ItemId);
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public List<ProductItem> Items { get; set; }
}
public class ProductItem
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public List<ProductItem> Products { get; set; }
}
查询:
var totalProducts = context.Products.Count();
var totalItems = context.Items.Count();
var items = context.Products
.Join(context.ProductsItems, p => p.Id, pi => pi.ProductId, (p, pi) => new { p.Status, pi.ItemId })
.Join(context.Items, si => si.ItemId, i => i.Id, (si, i) => new { si.Status, i.Cost })
.GroupBy(p => p.Status, (s, pis) => new {
Status = s,
Cost = pis.Sum(pi => pi.Cost)
}).ToList();
SQL 生成:
SELECT COUNT(*)
FROM [Products] AS [p]
SELECT COUNT(*)
FROM [Items] AS [i]
SELECT [p].[Status], COALESCE(SUM([i].[Cost]), 0.0) AS [Cost]
FROM [Products] AS [p]
INNER JOIN [ProductsItems] AS [p0] ON [p].[Id] = [p0].[ProductId]
INNER JOIN [Items] AS [i] ON [p0].[ItemId] = [i].[Id]
GROUP BY [p].[Status]
没有中间实体的解决方案
实体:
public class MyContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Item> Items => Set<Item>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Product>()
.HasMany<Item>(x => x.Items)
.WithMany(y => y.Products)
.UsingEntity(p => p.ToTable("ProductItems"));
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public List<Product> Products { get; set; }
}
查询:
var totalProducts = context.Products.Count();
var totalItems = context.Items.Count();
var items = context.Products
.SelectMany(p => p.Items.Select(i => new { p.Status, i.Cost }))
.GroupBy(p => p.Status, (s, ps) => new {
Status = s,
Cost = ps.Sum(i => i.Cost)
})
.ToList();
SQL 生成:
SELECT COUNT(*)
FROM [Products] AS [p]
SELECT COUNT(*)
FROM [Items] AS [i]
SELECT [t].[Status], COALESCE(SUM([t].[Cost]), 0.0) AS [Cost]
FROM [Products] AS [p]
CROSS APPLY (
SELECT [p].[Status], [i].[Cost]
FROM [ProductItems] AS [p0]
INNER JOIN [Items] AS [i] ON [p0].[ItemsId] = [i].[Id]
WHERE [p].[Id] = [p0].[ProductsId]
) AS [t]
GROUP BY [t].[Status]
你可以像下面这样创建两个 dto 并使用 groupby 来做到这一点
public class ItemDTO
{
public int Status { get; set; }
public decimal Cost { get; set; }
}
public class productDTO
{
public int TotalProducts { get; set; }
public int TotalItems { get; set; }
public List<ItemDTO> Items { get; set; }
}
然后使用这个
var grouped = products.GroupBy(x => x.Status).Select(x =>new
{
key = x.Key,
value = x.ToList()
});
var res = new productDTO
{
TotalProducts = products.Count,
TotalItems = products.Sum(x => x.Items?.Count)??0,
Items = grouped.Select(x => new ItemDTO
{
Cost = x.value.Sum(q => q.Items?.Sum(i => i.Cost)??0),
Status = x.key
}).ToList()
};
产品:
+------+---------+--------------+
| id | name | status |
+------+---------+--------------+
| 1 | foo | in stock |
| 2 | bar | in stock |
| 3 | baz | out of stock |
+------+---------+--------------+
项目:
+------+---------+--------+
| id | name | Cost |
+------+---------+--------+
| 1 | item1 | 10 |
| 2 | item2 | 20 |
| 3 | item3 | 5 |
+------+---------+--------+
中级 table:
+--------------+------------+
| product_id | items_id |
+--------------+------------+
| 1 | 1 |
| 1 | 3 |
| 2 | 2 |
+--------------+------------+
多对多配置:
modelBuilder.Entity<Product>()
.HasMany<Item>(x => x.Items)
.WithMany(y => y.Products)
.UsingEntity(p => p.ToTable("ProductItems"));
结果应该是:
{
'TotalItems': 3,
'Items': [
{ 'Status': 'in stock', 'Cost': 30 },
{ 'Status': 'out of stock', 'Cost': 5 }
]
}
特定产品的项目总数:
context.Products
.Where(x => x.Name.Equals("foo"))
.SelectMany(x => x.Items)
.Count();
现在我想根据产品状态对项目进行分组。类似于:
context.Products
.Where(x => x.Name.Equals("foo"))
.SelectMany(x => x.Items)
.GroupBy(Status, (g,x) => new {
Status = ...
Cost = ...
})
.ToList()
有什么想法可以实现所需的输出吗?
具有中间实体的解决方案
实体:
public class MyContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<ProductItem> ProductsItems => Set<ProductItem>();
public DbSet<Item> Items => Set<Item>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductItem>()
.HasKey(pi => new { pi.ProductId, pi.ItemId });
modelBuilder.Entity<Product>()
.HasMany(p => p.Items)
.WithOne(pi => pi.Product)
.HasForeignKey(pi => pi.ProductId);
modelBuilder.Entity<Item>()
.HasMany(i => i.Products)
.WithOne(pi => pi.Item)
.HasForeignKey(pi => pi.ItemId);
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public List<ProductItem> Items { get; set; }
}
public class ProductItem
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public List<ProductItem> Products { get; set; }
}
查询:
var totalProducts = context.Products.Count();
var totalItems = context.Items.Count();
var items = context.Products
.Join(context.ProductsItems, p => p.Id, pi => pi.ProductId, (p, pi) => new { p.Status, pi.ItemId })
.Join(context.Items, si => si.ItemId, i => i.Id, (si, i) => new { si.Status, i.Cost })
.GroupBy(p => p.Status, (s, pis) => new {
Status = s,
Cost = pis.Sum(pi => pi.Cost)
}).ToList();
SQL 生成:
SELECT COUNT(*)
FROM [Products] AS [p]
SELECT COUNT(*)
FROM [Items] AS [i]
SELECT [p].[Status], COALESCE(SUM([i].[Cost]), 0.0) AS [Cost]
FROM [Products] AS [p]
INNER JOIN [ProductsItems] AS [p0] ON [p].[Id] = [p0].[ProductId]
INNER JOIN [Items] AS [i] ON [p0].[ItemId] = [i].[Id]
GROUP BY [p].[Status]
没有中间实体的解决方案
实体:
public class MyContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Item> Items => Set<Item>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Product>()
.HasMany<Item>(x => x.Items)
.WithMany(y => y.Products)
.UsingEntity(p => p.ToTable("ProductItems"));
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public List<Product> Products { get; set; }
}
查询:
var totalProducts = context.Products.Count();
var totalItems = context.Items.Count();
var items = context.Products
.SelectMany(p => p.Items.Select(i => new { p.Status, i.Cost }))
.GroupBy(p => p.Status, (s, ps) => new {
Status = s,
Cost = ps.Sum(i => i.Cost)
})
.ToList();
SQL 生成:
SELECT COUNT(*)
FROM [Products] AS [p]
SELECT COUNT(*)
FROM [Items] AS [i]
SELECT [t].[Status], COALESCE(SUM([t].[Cost]), 0.0) AS [Cost]
FROM [Products] AS [p]
CROSS APPLY (
SELECT [p].[Status], [i].[Cost]
FROM [ProductItems] AS [p0]
INNER JOIN [Items] AS [i] ON [p0].[ItemsId] = [i].[Id]
WHERE [p].[Id] = [p0].[ProductsId]
) AS [t]
GROUP BY [t].[Status]
你可以像下面这样创建两个 dto 并使用 groupby 来做到这一点
public class ItemDTO
{
public int Status { get; set; }
public decimal Cost { get; set; }
}
public class productDTO
{
public int TotalProducts { get; set; }
public int TotalItems { get; set; }
public List<ItemDTO> Items { get; set; }
}
然后使用这个
var grouped = products.GroupBy(x => x.Status).Select(x =>new
{
key = x.Key,
value = x.ToList()
});
var res = new productDTO
{
TotalProducts = products.Count,
TotalItems = products.Sum(x => x.Items?.Count)??0,
Items = grouped.Select(x => new ItemDTO
{
Cost = x.value.Sum(q => q.Items?.Sum(i => i.Cost)??0),
Status = x.key
}).ToList()
};