【问题标题】:Dynamic LINQ expression for Select with child collection (Entity Framework)带有子集合的 Select 的动态 LINQ 表达式(实体框架)
【发布时间】:2021-03-14 19:49:49
【问题描述】:

我想向您询问一种使用嵌套子集合动态创建 LINQ Select 表达式的方法。所选子集合中的字段可以是静态的,但是我想动态传递当前实体中的字段列表,以及导航属性引用的其他实体中的字段。 这是查询的静态版本,类似于我在代码中使用的那个,在很多地方,我想动态创建:

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 的回答真的很酷,但它不支持引用属性,而且我在上面的示例中也有这个静态嵌套子集合选择

Dynamically build select list from linq to entities query

EF Linq- Dynamic Lambda expression trees

我也尝试过使用 Dynamic.Linq.Core 或 Automapper 之类的库来实现这一点,但似乎前者不支持对子集合进行选择,而后者需要一个 DTO 类,我不想为它创建数百个 DTO选择表达式中的每个字段组合。

我还认为整个方法可能过于复杂,也许我应该改用参数化 SQL 查询,这绝对是一个可能的选择,但我认为我可以在不同的变体中重用这个动态 LINQ 表达式,在我的其他地方项目,从长远来看会有所帮助:)

(我使用的是 EF Core 3.1)

【问题讨论】:

  • 我没有看到创建此类查询的模式。动态表达式树也不会为您创建匿名类。

标签: c# linq lambda entity-framework-core expression-trees


【解决方案1】:

链接帖子中的我的解决方案处理了非常简化的场景。它可以扩展为处理嵌套属性和方法调用,但主要问题是它的结果不是真正动态的。它需要预定义的类型并有选择地选择/填充该类型的成员。这使得它类似于 AutoMapper 显式扩展功能,因此使用后者可能会更好,因为它提供了灵活的自动/手动映射选项。

对于真正的动态输出,您需要一个在运行时生成动态类的库。 Dynamic LINQ 就是其中之一,除了它不寻常的expression language 实际上可以 用于您的场景(至少来自https://dynamic-linq.net/ 的那个,因为该库有多种风格,而且它们支持/不支持某些东西)。

以下是使用 System.Linq.Dynamic.Core 包(或它的 EF Core 版本 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();

【讨论】:

  • 所以可以使用 Linq.Dynamic.Core 做我想做的事...嗯,我一定在文档中忽略了这一点... -.- 我会尽快尝试并更新此评论.非常感谢!
  • 这看起来应该可以工作,但我总是收到No applicable method 'Select' exists in type 任何我代替Posts 的错误。我尝试向子类添加Select 方法以包装库中的Select,但由于某种原因,我似乎无法让我的数据类看到库的Select 与任何using 都存在我能够提出的声明或明确的参考。有什么建议吗?
  • @Trevortni Posts 必须是父类的属性(在本例中为 Blog)并且至少是 IEnumerable&lt;Something&gt;,例如class Foo { public IEnumerable&lt;Bar&gt; Bars { get; set; } }。还要确保使用链接中的最新版本的包,因为 DynamicLINQ 的实现有很多,并且每个都支持/不支持某些方法。
  • 所以如果孩子是一对一的,这行不通?它必须是一个孩子的列表,它不能像Blog.Author.Name?喜欢Author.Select(new (Name, EyeColor)) As Author
  • @Trevortni 如果它是引用类型属性,那么您只需生成成员(w/o Select),例如selectList.Add("new (Author.Name as Name, Author.EyeColor as EyeColor) as Author");
【解决方案2】:

如果您同意添加额外的依赖项,我强烈建议您使用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 查询中,我什至不需要提及 PostPostDto。这是因为ProjectTo使用映射配置来确定需要填写哪些字段,而配置中包含了每个DTO字段需要哪些实体字段的信息。

我假设实体和 DTO 之间的所有属性的名称大部分都匹配,因为这样您就不需要手动映射它们。这样做是为了保持示例简单,但还包括一个示例,说明如何手动将 AuthorDto.MostRecentPost 映射到实际子查询。
对于更复杂的映射,我建议查看 Automapper 的文档。这比我在这里的一个答案中可以解释的要多。

实际上,ProjectTo 会为您生成适当的 Select 语句。

这也意味着,如果我决定更改 PostDto 及其映射,则此更改会自动反映在两个 LINQ 查询中,而无需更改 LINQ 查询本身。

【讨论】:

  • 感谢您的回答,但我认为这对我没有帮助。我们的 BlogDto 类只包含 'Name' 属性,但它也可以包含 100 个属性。在某些查询中,我只想获取其中的 5 个,在另一个查询中获取 10 个,依此类推……我不想让 AutoMapper 将该 Projection 转换为具有 100 列的 SQL 选择。
  • @mctl87:一般建议是创建特定的 DTO 以满足您的特定需求,而不是尝试以多种不同方式使用一个 DTO。否则,您将遇到许多问题,例如额外的空检查、空引用异常、默认(未使用)值与恰好等于默认值的真实数据无法区分等等。
  • 我同意你的观点。使用 DTO 有不可否认的优势。我也在查询中使用它们。然而,在极少数情况下,动态对象似乎更具可重用性,并且不包含太多不必要的信息。
猜你喜欢
  • 1970-01-01
  • 2017-06-15
  • 1970-01-01
  • 2016-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多