如何正确使用EF Core与AutoMapper的ProjectTo和Unions?

12

我的设置

  • ASP.NET Core 2.0
  • EntityFrameworkCore 2.0.1
  • AutoMapper 6.2.2

问题

我有一个带有DTO(数据传输对象)PersonDetail和实体Person的项目。当我调用

db.People.Where(p => p.FirstName == "Joe").Union(db.People.Where(p => Age > 30)).ProjectTo<PersonDetail>(mapperConfig).ToList(); 

我不理解PersonDetail数据传输对象(DTO),并且Entity Framework(Core)抛出异常信息:

ArgumentException: 输入序列必须具有类型为“Test.Module.Entities.Person”的项目,但它具有类型为“Test.Module.Dtos.PersonDetail”的项目。


没有问题的示例

当我运行以下代码时:

 db.People.Where(p => p.FirstName == "Joe").Union(db.People.Where(p => Age > 30)).ToList(); 

我无条件获取Person实体。


执行计划

这是一个带有联合的工作计划:

{value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Test.Module.Entities.Person]).Where(entity => ((entity != null) And ((63ed0ebd-2c02-4496-ac8d-b836cbf13259 == entity.CreatedBy) Or (393a6bb0-b437-4664-beb0-6800f509451b == entity.CreatedBy)))).Union(value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Test.Module.Entities.Person]))}

现在是同样的计划,但也包括了Automapper projections:

{value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Test.Module.Entities.Person]).Where(entity => ((entity != null) And ((63ed0ebd-2c02-4496-ac8d-b836cbf13259 == entity.CreatedBy) Or (393a6bb0-b437-4664-beb0-6800f509451b == entity.CreatedBy)))).Union(value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Test.Module.Entities.Person])).Select(dto => new PersonDetail() {FirstName = dto.FirstName, LastName = dto.LastName, Deleted = dto.Deleted, Age = dto.Age, CreatedUtc = dto.CreatedUtc, CreatedBy = dto.CreatedBy, Id = dto.Id, RecordVersion = dto.RecordVersion, DisplayLabel = ((dto.FirstName + " ") + dto.LastName)})}


注意:

我只调用ToList以将此问题简化到最小形式。我知道在这个示例中似乎不需要使用ProjectTo。在我的实际代码中,我们正在使用OData,我们需要将最终结果作为DTO的Queryable对象进行投影查询。我也知道这个Union不是一个好的Union示例,但是为了简化Union问题而已。

我还在相应的GitHub项目上提出了问题:

EntityFrameworkCorehttps://github.com/aspnet/EntityFrameworkCore/issues/11033

AutoMapperhttps://github.com/AutoMapper/AutoMapper/issues/2537


请查看执行计划 - Lucian Bargaoanu
感谢@LucianBargaoanu!这非常有帮助,让我发现这实际上是一个关于联合的问题。我没有意识到我的查询被进一步操纵,并且Union实际上是计划的一部分。我已经更新了问题以反映这一点。 - Phobis
2个回答

5

3

如果使用映射配置文件,请确保映射正确,否则很难确定出错的具体原因。

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Person, PersonDetail>();
    }
}

假设您的 EF 上下文具有一个集合:
public virtual DbSet<Person> People { get; set; }

然后,您应该能够按照以下方式查询上下文和项目:
var details = _context.People
    .Where(p => p.LastName == 'Smith')
    .OrderBy(p => p.FirstName)
    .ProjectTo<PersonDetail>
    .ToList();

你不需要使用AsNoTracking,因为EF不会跟踪不是实体的结果类型,请参阅跟踪和投影文档。
--- 更新 ---
下面的内容应该可以工作,尽管EF Core会在内存中评估它:
var firstNameQuery = db.People
    .Where(p => p.FirstName == "Joe")
    .ProjectTo<PersonDetail>(mapperConfig);
var ageQuery = db.People
    .Where(p => p.FirstName == "Joe")
    .ProjectTo<PersonDetail>(mapperConfig);
var results = firstNameQuery.Union(ageQuery).ToList();

嗨@ChrisR,感谢您的回复。我们现在已将问题更改为与联合有关,因为我们发现这实际上是导致我们问题的原因。 - Phobis
你想要得到什么结果,是所有名字为“Joe”的人且年龄超过30岁的吗? - ChrisR
不,那只是一个轻量级的例子(我知道在那个例子中我可以在非联合查询中使用where子句)。这个例子的重点是即使是简单的联合也行不通。我们在某些情况下确实需要使用联合,但我觉得这对这个问题没有任何价值,只会增加混乱。似乎实际的联合可能已经损坏了。 - Phobis
澄清一下,我认为展示一个更复杂的联合体并没有增加任何价值,因为我们可能需要解释其背后的数据,而这并不是重点。 - Phobis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接