Select 和子集合 (Entity Framework) 的动态 LINQ 表达式
Dynamic LINQ expression for Select with child collection (Entity Framework)
我想请教您一种使用嵌套子集合动态创建 LINQ Select 表达式的方法。 selected 子集合中的字段可以是静态的,但是我想动态传递当前实体中的字段列表,以及其他实体中的字段,由导航属性引用。
这是查询的静态版本,类似于我在代码中的许多地方使用的版本,我想动态创建它:
var listItems = _efDbContext.Blogs.Select(x => new {
ID = x.ID,
Name = x.Name, //field from the current entity
AuthorName = x.Author.Name, //field referenced by navigation property
...<other fields from current or referenced entities(like AuthorName above), passed dynamically>
Posts = x.Posts.Select(y => new { //this select is 'static', is the same for other queries
Id = x.Id,
Name = x.Name
})
});
我试图从下面这两个帖子的答案中找出一些东西,但我没有成功。 Ivan Stoev 的回答真的很酷,但它不支持引用的属性,我在上面的示例中也有这个静态嵌套子集合 select
我也曾尝试使用 Dynamic.Linq.Core 或 Automapper 这样的库来完成此操作,但似乎前者不支持 select 子集合,而后者需要 DTO class而且我不想为 select 表达式中的每个字段组合创建数百个 DTO。
我还认为整个方法可能过于复杂,也许我应该改用参数化 SQL 查询,这绝对是一个可能的选择,但我认为我可以在不同的变体中重用这个动态 LINQ 表达式,在我项目的其他地方,它会在很长一段时间内提供帮助 运行 :)
(我使用的是 EF Core 3.1)
我在链接 post 中的解决方案处理的场景非常简单。它可以扩展以处理嵌套属性和方法调用,但主要问题是它的结果不是真正动态的。它需要一个预定义的类型,并有选择地 selects/populates 该类型的成员。这使得它类似于 AutoMapper 显式扩展功能,因此最好使用后者,因为它提供灵活的 automatic/manual 映射选项。
对于真正的动态输出,您需要一个在运行时生成动态 类 的库。 Dynamic LINQ 就是其中之一,除了它不寻常的 expression language actually can be used for your scenario (at least the one from https://dynamic-linq.net/,因为该库有很多风格,而且它们 support/do 不支持某些东西)。
以下是使用 System.Linq.Dynamic.Core package (or EF Core version of it Microsoft.EntityFrameworkCore.DynamicLinq 的示例,以防您需要异步可查询支持):
var selectList = new List<string>();
// Dynamic part
selectList.Add("ID");
selectList.Add("Name");
selectList.Add("Author.Name as AuthorName");
// Static part. But the nested Select could be built dynamically as well
selectList.Add("Posts.Select(new (Id, Name)) as Posts");
var listItems = _efDbContext.Blogs
.Select($"new ({string.Join(", ", selectList)})")
.ToDynamicList();
如果您同意添加额外的依赖项,我强烈建议您使用 Automapper's ProjectTo()
,它可以有效地用允许可重用映射(包括嵌套映射)的方法替换您的 Select
。
一个简单的例子是:
public class PostDto
{
public string Title { get; set; }
}
public class BlogDto
{
public string Name { get; set; }
public PostDto[] Posts { get; set; }
}
public class AuthorDto
{
public string Name { get; set; }
public PostDto MostRecentPost { get; set; }
}
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Post, PostDto>();
cfg.CreateMap<Blog, BlogDto>();
cfg.CreateMap<Author, AuthorDto>()
.ForMember(
dto => dto.MostRecentPost,
conf => conf.MapFrom(
author => author.Posts
.OrderByDescending(x => x.Date)
.FirstOrDefault()));
});
public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
using (var context = new orderEntities())
{
var blogsWithPosts = context
.Blogs
.ProjectTo<BlogDto>(configuration)
.ToList();
var authorsWithPost = context
.Authors
.ProjectTo<AuthorDto>(configuration)
.ToList();
}
}
请注意,我什至不需要在实际的 LINQ 查询中提及 Post
或 PostDto
。这是因为ProjectTo
使用映射配置来确定需要填写哪些字段,并且该配置包含有关每个DTO字段需要哪些实体字段的信息。
我假设大部分情况下实体和 DTO 之间的所有属性的名称都匹配,因为这样您就不需要手动映射它们。这样做是为了保持示例简单,但也包括一个示例,说明如何手动将 AuthorDto.MostRecentPost
映射到实际的子查询。
对于更复杂的映射,我建议查看 Automapper 的文档。这比我在这里的一个答案所能解释的要多。
实际上,ProjectTo
会为您生成适当的 Select
语句。
这也意味着,如果我决定更改 PostDto
及其映射,则此更改会自动反映在两个 LINQ 查询中,而无需更改 LINQ 查询本身。
我想请教您一种使用嵌套子集合动态创建 LINQ Select 表达式的方法。 selected 子集合中的字段可以是静态的,但是我想动态传递当前实体中的字段列表,以及其他实体中的字段,由导航属性引用。 这是查询的静态版本,类似于我在代码中的许多地方使用的版本,我想动态创建它:
var listItems = _efDbContext.Blogs.Select(x => new {
ID = x.ID,
Name = x.Name, //field from the current entity
AuthorName = x.Author.Name, //field referenced by navigation property
...<other fields from current or referenced entities(like AuthorName above), passed dynamically>
Posts = x.Posts.Select(y => new { //this select is 'static', is the same for other queries
Id = x.Id,
Name = x.Name
})
});
我试图从下面这两个帖子的答案中找出一些东西,但我没有成功。 Ivan Stoev 的回答真的很酷,但它不支持引用的属性,我在上面的示例中也有这个静态嵌套子集合 select
我也曾尝试使用 Dynamic.Linq.Core 或 Automapper 这样的库来完成此操作,但似乎前者不支持 select 子集合,而后者需要 DTO class而且我不想为 select 表达式中的每个字段组合创建数百个 DTO。
我还认为整个方法可能过于复杂,也许我应该改用参数化 SQL 查询,这绝对是一个可能的选择,但我认为我可以在不同的变体中重用这个动态 LINQ 表达式,在我项目的其他地方,它会在很长一段时间内提供帮助 运行 :)
(我使用的是 EF Core 3.1)
我在链接 post 中的解决方案处理的场景非常简单。它可以扩展以处理嵌套属性和方法调用,但主要问题是它的结果不是真正动态的。它需要一个预定义的类型,并有选择地 selects/populates 该类型的成员。这使得它类似于 AutoMapper 显式扩展功能,因此最好使用后者,因为它提供灵活的 automatic/manual 映射选项。
对于真正的动态输出,您需要一个在运行时生成动态 类 的库。 Dynamic LINQ 就是其中之一,除了它不寻常的 expression language actually can be used for your scenario (at least the one from https://dynamic-linq.net/,因为该库有很多风格,而且它们 support/do 不支持某些东西)。
以下是使用 System.Linq.Dynamic.Core package (or EF Core version of it Microsoft.EntityFrameworkCore.DynamicLinq 的示例,以防您需要异步可查询支持):
var selectList = new List<string>();
// Dynamic part
selectList.Add("ID");
selectList.Add("Name");
selectList.Add("Author.Name as AuthorName");
// Static part. But the nested Select could be built dynamically as well
selectList.Add("Posts.Select(new (Id, Name)) as Posts");
var listItems = _efDbContext.Blogs
.Select($"new ({string.Join(", ", selectList)})")
.ToDynamicList();
如果您同意添加额外的依赖项,我强烈建议您使用 Automapper's ProjectTo()
,它可以有效地用允许可重用映射(包括嵌套映射)的方法替换您的 Select
。
一个简单的例子是:
public class PostDto
{
public string Title { get; set; }
}
public class BlogDto
{
public string Name { get; set; }
public PostDto[] Posts { get; set; }
}
public class AuthorDto
{
public string Name { get; set; }
public PostDto MostRecentPost { get; set; }
}
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Post, PostDto>();
cfg.CreateMap<Blog, BlogDto>();
cfg.CreateMap<Author, AuthorDto>()
.ForMember(
dto => dto.MostRecentPost,
conf => conf.MapFrom(
author => author.Posts
.OrderByDescending(x => x.Date)
.FirstOrDefault()));
});
public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
using (var context = new orderEntities())
{
var blogsWithPosts = context
.Blogs
.ProjectTo<BlogDto>(configuration)
.ToList();
var authorsWithPost = context
.Authors
.ProjectTo<AuthorDto>(configuration)
.ToList();
}
}
请注意,我什至不需要在实际的 LINQ 查询中提及 Post
或 PostDto
。这是因为ProjectTo
使用映射配置来确定需要填写哪些字段,并且该配置包含有关每个DTO字段需要哪些实体字段的信息。
我假设大部分情况下实体和 DTO 之间的所有属性的名称都匹配,因为这样您就不需要手动映射它们。这样做是为了保持示例简单,但也包括一个示例,说明如何手动将 AuthorDto.MostRecentPost
映射到实际的子查询。
对于更复杂的映射,我建议查看 Automapper 的文档。这比我在这里的一个答案所能解释的要多。
实际上,ProjectTo
会为您生成适当的 Select
语句。
这也意味着,如果我决定更改 PostDto
及其映射,则此更改会自动反映在两个 LINQ 查询中,而无需更改 LINQ 查询本身。