AutoMapper + EF Core:查询嵌套属性等于值的位置

Posted

技术标签:

【中文标题】AutoMapper + EF Core:查询嵌套属性等于值的位置【英文标题】:AutoMapper + EF Core: Query where nested property equals value 【发布时间】:2021-12-05 16:26:44 【问题描述】:

看起来很简单,但我无法使用 EF 核心执行以下查询。

public class ParentThing // EF entity

   public int Id  get; set; 
   public string Name  get; set; 
   public int? ChildThingId  get; set; 
   public ChildThing Child  get; set ; 


public class ChildThing // EF entity

   public int Id  get; set; 
   public string Name  get; set; 


public class ChildDTO 

   public int Id  get; set; 


public class ParentDTO

   public string Name  get; set; 
   public ChildDTO Child  get; set; 


public static void Run(int id, DbSet<ParentThing> dbSet, IMapper mapper)

   // mappings are something like this...
   // CreateMap<ChildThing, ChildDTO>();
   // CreateMap<ParentThing, ParentDTO>();

   var dtos = mapper.ProjectTo<ParentDTO>(dbSet)
               .Where(e => e.Child.Id == id) // this throws an exception
               .ToList();

尝试执行查询时出现以下异常。

System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'.

我尝试将查询到的 id 转换为可为空的 int 并得到以下异常。

System.InvalidOperationException: Rewriting child expression from type 'System.Int32' to type 'System.Nullable`1[System.Int32]' is not allowed, because it would change the meaning of the operation. If this is intentional, override 'VisitUnary' and change it to allow this rewrite.

我真的需要展平嵌套属性才能查询它们吗?

【问题讨论】:

尝试更新 EF Core。过滤器也必须在ProjectTo之前。 你试过这个吗.Where(e => e.Child!=null && e.Child.Id == id) 首先检查空对象仍然会引发异常:System.InvalidOperationException:不允许将子表达式从“System.Int32”类型重写为“System.Nullable`1[System.Int32]”类型,因为它会改变操作的含义。如果这是故意的,请覆盖“VisitUnary”并将其更改为允许此重写。 在调用 ProjectTo 之前过滤 dbSet 似乎确实有效,尽管感觉有点笨拙。我将看看底层的查询字符串,看看它有多复杂。 您是否在没有映射的情况下尝试了相同的查询?它可以帮助找到异常源 【参考方案1】:

我真的需要展平嵌套属性才能查询它们吗?

看起来你需要。

整个问题是“子”引用是否可以是null(即可选或必需)。

如果它是可选的,除了扁平化引用对象属性并使它们可以为空之外,没有其他解决方案。或者在投影之前对实体查询应用过滤器。这是因为 EF Core 处理导航属性的方式与投影不同(并且更好)。 AutoMapper 所做的相当于

db.Set<ParentThing>()
    .Select(p => new ParentDto
    
        // ...
        Child = p.ChildThing == null ? null : new ChildDto  ... )
    
    .Where(...);

您得到的错误是由于条件构造。但是如果你不使用它,那么当数据为空时你会得到不同的异常。

因此,在需要参考时才有可能提供干净的解决方案。在这种情况下,您只需要让 AutoMapper 消除空检查并直接生成Child = new ChildDto ... 。可以用DoNotAllowNull指定,例如

CreateMap<ParentThing, ParentDTO>()
    .ForMember(dst => dst.Child, opt => opt.DoNotAllowNull());

但同样,这仅适用于必需的参考。

【讨论】:

@Lucian 我知道你是 AM 的主要贡献者之一。但是......我总是在发布之前测试这些东西。 DoNotAllowNull(或AllowNullDestinationValues = false)消除了null签入投影。 感谢您的反馈 @IvanStoev 是的,对不起,我猜这些名字没有多大帮助。【参考方案2】:

既然你正在使用

public ChildThing Child  get; set ; 

会混淆实体框架

所以我认为创建显式导航属性会有所帮助

public class ParentThing // EF entity

   public int Id  get; set; 
   public string Name  get; set; 
   public int? ChildThingId  get; set; 
   public ChildThing ChildThing  get; set ; 


public class ChildThing // EF entity

   public int Id  get; set; 
   public string Name  get; set; 

    public string Name  get; set; 
    public ParentThing ParentThing  get; set ; 

我想知道您的 DbSet 是否包含 Child,它可以为 null 并且可能导致异常。但是在这种情况下,您可以使用此代码

 var parents=  dbSet
         .Where(e => e.ChildThingId!=null && ( (int) e.ChildThingId ) == id)
         .ToList();

 var dtos = mapper.Map<List<ParentThing>, List<ParentDto>>(parents);

【讨论】:

我已经在原帖中阐明了 ParentThing 类。导航属性存在,但我忘记在示例中包含它。它使我可以在 ProjectTo 之前进行过滤,但想知道是否有办法在 ProjectTo 之后进行过滤 @WaitsAtWork 我之前告诉过你 child 是 null 。你试过 ChildThingId==id 吗? 我做到了,它可以作为一种解决方法。如果这是唯一的方法,那么我会关闭这个问题。

以上是关于AutoMapper + EF Core:查询嵌套属性等于值的位置的主要内容,如果未能解决你的问题,请参考以下文章

使用 entityFramework Automapper .Net Core 的嵌套映射

EF Core 相关的千倍性能之差: AutoMapper ProjectTo VS Mapster ProjectToType

EF Core 嵌套 Linq 选择导致 N + 1 个 SQL 查询

嵌套 tph 继承成员的 ef-core 负载收集属性

EF Core Eager Loading 嵌套集合

过滤包含在 EF Core 中