LINQ:多table/加入+分组依据
LINQ: Multi table/ join + Group by
我有一组相当不寻常的 tables,我需要为其编写 Linq 查询和 return 一个特定的自定义模型,以下是实体及其关系
public class CustomerAccount
{
public Guid Id { get; set; }
public virtual Shop Shop { get; set; }
public Guid ShopId { get; set; }
public virtual Account Account { get; set; }
public Guid AccountId { get; set; }
public string MyAreaName { get; set; }
public virtual ICollection<CustomerAccountTag> CustomerAccountTags { get; set; }
}
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Account
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CustomerAccountTag
{
public Guid CustomerAccountId { get; set; }
public string TagId { get; set; }
public virtual CustomerAccount CustomerAccount { get; set; }
public virtual Tag Tag { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<CustomerAccountTag> CustomerAccountTags { get; set; }
}
CustomerAccount 包含客户列表以及他们的商店和帐户详细信息,以及一些元数据。
它将包含如下记录
{
"CustomerAccount": [
{
"Id": "1",
"ShopId": "Shop1",
"AccountId": "AccountA"
},
{
"Id": "2",
"ShopId": "Shop1",
"AccountId": "AccountB"
},
{
"Id": "3",
"ShopId": "Shop2",
"AccountId": "AccountC"
}
]
}
table CustomerAccountTag 只是 CustomerAccount 和标签 table 之间的链接 table,标签是分配给每个 CustomerAccount 行的字符串。
我需要以下输出,
按商店 ID 分组(因此上面的示例将有两行数据,一行用于 shop1,另一行用于 shop2)
每行将有以下数据
{
ShopName
ShopId
Accounts = "custom list of accounts per shop, with Account Id, Account Name, AreaName and a list of related Tags"
}
以下是我的做法,它可以工作,但只适用于 LINQ lambda,问题是我不能“分组依据”,除非我 return 加入的 tables 作为列表然后我可以处理它。否则它会抱怨它不能 运行 服务器上的查询,需要在客户端上进行评估!
var queryResults = (from e in _applicationDbContext.CustomerAccount
.Include(g => g.Shop)
.Include(y => y.Account)
.Include(a => a.CustomerAccountTag)
.ThenInclude(t => t.Tag)
select e).ToList();
var result = queryResults.GroupBy(x => x.Shop)
.Select(y => new CustomAccountResponse
{
ShopId = y.Key.Id,
ShopName = y.Key.Name,
Accounts = y.Select(x => new { y.Account.Name, y.Account.Id, y.AreaName, string.Join(",", y.CustomerAccountTags.Select(x => x.Tag.Name)) })
});
想知道我是否可以用 Linq 做同样的事情,但是 运行 一切都在数据库上,而不是一半在客户端,一半在客户端!
更新:
我想下面的示例代码可以解释这个问题,也许 EFCore 不支持我的要求
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
public static class Program
{
public static void Main()
{
using (var context = new ApplicationDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var shop1 = new Shop { Name = "Shop1" };
var shop2 = new Shop { Name = "Shop2" };
var shop3 = new Shop { Name = "Shop3" };
var shop4 = new Shop { Name = "Shop4" };
var shop5 = new Shop { Name = "Shop3" };
List<Shop> shop = new List<Shop>() { shop1, shop2, shop3, shop4, shop5 };
context.Shops.AddRange(shop);
context.SaveChanges();
}
using (var context = new ApplicationDbContext())
{
var query = from shop in context.Shops
group shop by shop.Name into grouped
select new
{
Id = grouped.Key,
Count = grouped.Count(),
Items = grouped
};
}
/*
Processing of the LINQ expression 'GroupByShaperExpression:
KeySelector: s.Name,
ElementSelector:EntityShaperExpression:
EntityType: Shop
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
*/
Console.ReadLine();
}
}
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer("Server=(localdb)\mssqllocaldb;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Shop> Shops { get; set; }
}
}
我真的可以在我的查询中获得分组项目的列表吗?
这正是发生以下异常的地方
/*
Processing of the LINQ expression 'GroupByShaperExpression:
KeySelector: s.Name,
ElementSelector:EntityShaperExpression:
EntityType: Shop
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
*/
您创建了正确的查询。没有简单的方法可以使用 EF 在服务器上完全 运行 此查询。您可以在此处执行的操作 - 限制从服务器检索的数据并强制 EF 生成最佳查询。
var query =
from a in _applicationDbContext.CustomerAccount
from t in a.CustomerAccountTags
select new
{
Shop = new { a.Shop.Id, a.Shop.Name },
Account = new { a.Account.Name, a.Account.Id, a.AreaName },
TagName = t.Tag.Name
};
var result =
from q in query.AsEnumerable()
group q by q.Shop into g
select new CustomAccountResponse
{
ShopId = g.Key.Id,
ShopName = g.Key.Name,
Accounts = g.GroupBy(x => x.Account).Select(x => new
{
x.Key.Account.Name,
x.Key.Account.Id,
x.Key.Account.AreaName,
Tags = string.Join(",", x.Select(y => y.TagName))
}).ToList()
};
我有一组相当不寻常的 tables,我需要为其编写 Linq 查询和 return 一个特定的自定义模型,以下是实体及其关系
public class CustomerAccount
{
public Guid Id { get; set; }
public virtual Shop Shop { get; set; }
public Guid ShopId { get; set; }
public virtual Account Account { get; set; }
public Guid AccountId { get; set; }
public string MyAreaName { get; set; }
public virtual ICollection<CustomerAccountTag> CustomerAccountTags { get; set; }
}
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Account
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CustomerAccountTag
{
public Guid CustomerAccountId { get; set; }
public string TagId { get; set; }
public virtual CustomerAccount CustomerAccount { get; set; }
public virtual Tag Tag { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<CustomerAccountTag> CustomerAccountTags { get; set; }
}
CustomerAccount 包含客户列表以及他们的商店和帐户详细信息,以及一些元数据。
它将包含如下记录
{
"CustomerAccount": [
{
"Id": "1",
"ShopId": "Shop1",
"AccountId": "AccountA"
},
{
"Id": "2",
"ShopId": "Shop1",
"AccountId": "AccountB"
},
{
"Id": "3",
"ShopId": "Shop2",
"AccountId": "AccountC"
}
]
}
table CustomerAccountTag 只是 CustomerAccount 和标签 table 之间的链接 table,标签是分配给每个 CustomerAccount 行的字符串。
我需要以下输出, 按商店 ID 分组(因此上面的示例将有两行数据,一行用于 shop1,另一行用于 shop2) 每行将有以下数据
{
ShopName
ShopId
Accounts = "custom list of accounts per shop, with Account Id, Account Name, AreaName and a list of related Tags"
}
以下是我的做法,它可以工作,但只适用于 LINQ lambda,问题是我不能“分组依据”,除非我 return 加入的 tables 作为列表然后我可以处理它。否则它会抱怨它不能 运行 服务器上的查询,需要在客户端上进行评估!
var queryResults = (from e in _applicationDbContext.CustomerAccount
.Include(g => g.Shop)
.Include(y => y.Account)
.Include(a => a.CustomerAccountTag)
.ThenInclude(t => t.Tag)
select e).ToList();
var result = queryResults.GroupBy(x => x.Shop)
.Select(y => new CustomAccountResponse
{
ShopId = y.Key.Id,
ShopName = y.Key.Name,
Accounts = y.Select(x => new { y.Account.Name, y.Account.Id, y.AreaName, string.Join(",", y.CustomerAccountTags.Select(x => x.Tag.Name)) })
});
想知道我是否可以用 Linq 做同样的事情,但是 运行 一切都在数据库上,而不是一半在客户端,一半在客户端!
更新:
我想下面的示例代码可以解释这个问题,也许 EFCore 不支持我的要求
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
public static class Program
{
public static void Main()
{
using (var context = new ApplicationDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var shop1 = new Shop { Name = "Shop1" };
var shop2 = new Shop { Name = "Shop2" };
var shop3 = new Shop { Name = "Shop3" };
var shop4 = new Shop { Name = "Shop4" };
var shop5 = new Shop { Name = "Shop3" };
List<Shop> shop = new List<Shop>() { shop1, shop2, shop3, shop4, shop5 };
context.Shops.AddRange(shop);
context.SaveChanges();
}
using (var context = new ApplicationDbContext())
{
var query = from shop in context.Shops
group shop by shop.Name into grouped
select new
{
Id = grouped.Key,
Count = grouped.Count(),
Items = grouped
};
}
/*
Processing of the LINQ expression 'GroupByShaperExpression:
KeySelector: s.Name,
ElementSelector:EntityShaperExpression:
EntityType: Shop
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
*/
Console.ReadLine();
}
}
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer("Server=(localdb)\mssqllocaldb;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Shop> Shops { get; set; }
}
}
我真的可以在我的查询中获得分组项目的列表吗? 这正是发生以下异常的地方
/*
Processing of the LINQ expression 'GroupByShaperExpression:
KeySelector: s.Name,
ElementSelector:EntityShaperExpression:
EntityType: Shop
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
*/
您创建了正确的查询。没有简单的方法可以使用 EF 在服务器上完全 运行 此查询。您可以在此处执行的操作 - 限制从服务器检索的数据并强制 EF 生成最佳查询。
var query =
from a in _applicationDbContext.CustomerAccount
from t in a.CustomerAccountTags
select new
{
Shop = new { a.Shop.Id, a.Shop.Name },
Account = new { a.Account.Name, a.Account.Id, a.AreaName },
TagName = t.Tag.Name
};
var result =
from q in query.AsEnumerable()
group q by q.Shop into g
select new CustomAccountResponse
{
ShopId = g.Key.Id,
ShopName = g.Key.Name,
Accounts = g.GroupBy(x => x.Account).Select(x => new
{
x.Key.Account.Name,
x.Key.Account.Id,
x.Key.Account.AreaName,
Tags = string.Join(",", x.Select(y => y.TagName))
}).ToList()
};