首页 > 解决方案 > Entity Framework Core 3 - 缺少类型映射配置或不支持的映射

问题描述

我正在尝试在 Entity Framework Core 中映射实体并收到以下错误。我有两个主要实体PersonPersonNote它们具有一对多的关系。

一个人可以有很多个人笔记。我尝试检索人物的方法确实返回了结果,但是在尝试映射时失败了。正如您在我的帖子中看到的那样,我已经定义了两个映射。一种是将 PersonNote 与 Person 进行映射,以便可以评估 AuthorName。PersonNote 中的 AuthorId 与 Id in Person 相关。

我的 PersonNote 模型包含 AuthorName 但 DataModel 不包含。我正在尝试在 createmap 函数中映射该值。我错过了什么吗

缺少类型映射配置或不支持的映射。\r\n\r\n映射类型:\r\nEntityQueryable`1 ->

我已经定义了以下模型

    namespace Genistar.Organisation.Models.User
    {
        public class PersonNote
        {
            public PersonNote()
            {
                 Person = new HashSet<Person>();
            }

            public int Id { get; set; }
            public int PersonId { get; set; }
            public string Note { get; set; }
            public int AuthorId { get; set; }
            public string AuthorName { get; set; }
            public string CreatedBy { get; set; }
            public DateTime Created { get; set; }

            public ICollection<Person> Person { get; set; }
        }
    }

namespace Genistar.Organisation.Models.DataModels
{
        [Table(nameof(PersonNote), Schema = "common")]
        public class PersonNote
        {
            public int Id { get; set; }
            public int PersonId { get; set; }
            public string Note { get; set; }
            public int AuthorId { get; set; }
            public string CreatedBy { get; set; }
            public DateTime Created { get; set; }
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime RecordStartDateTime { get; set; }
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime RecordEndDateTime { get; set; }
        }
    }

    namespace Genistar.Organisation.Models.DataModels
    {
        public class Person
        {
            public int Id { get; set; }
            public int? TitleId { get; set; }
            public string FirstName { get; set; }
            public string FirstNamePref { get; set; }
            public string MiddleName { get; set; }
            public string LastName { get; set; }
            public DateTime? DateOfBirth { get; set; }
            public string Gender { get; set; }
            public int AddressId { get; set; }
            public string TelephoneNumber { get; set; }
            public string MobileNumber { get; set; }
            public string Email { get; set; }
            public int? PartnerId { get; set; }
            public bool Enabled { get; set; }
            public string CreatedBy { get; set; }
            public DateTime Created { get; set; }
            public string ModifiedBy { get; set; }
            public DateTime Modified { get; set; }
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime RecordStartDateTime { get; set; }
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime RecordEndDateTime { get; set; }
            public Address Address { get; set; }
            public Title Title { get; set; }
            public Client Client { get; set; }

            public List<PersonNote> PersonNotes { get; set; }
        }
    }

创建地图功能

 CreateMap<Genistar.Organisation.Models.DataModels.Person, Genistar.Organisation.Models.User.PersonNote>()
                .ForMember(t => t.AuthorId, opt => opt.MapFrom(s => s.Id))
                .ForMember(t => t.AuthorName, opt => opt.MapFrom(s => s.FirstName + " " + s.LastName));

CreateMap<Genistar.Organisation.Models.DataModels.PersonNote, Genistar.Organisation.Models.User.PersonNote>()
                  //.ForMember(t => t.Id, opt => opt.MapFrom(s => s.Id))
                  //.ForMember(t => t.PersonId, opt => opt.MapFrom(s => s.PersonId))
                  //.ForMember(t => t.Note, opt => opt.MapFrom(s => s.Note))
                  //.ForMember(t => t.AuthorId, opt => opt.MapFrom(s => s.AuthorId))
                  //.ForMember(t => t.CreatedBy, opt => opt.MapFrom(s => s.CreatedBy))
                  //.ForMember(t => t.Created, opt => opt.MapFrom(s => s.Created));

                .ForMember(t => t.Id, opt => opt.Ignore())
               .ForMember(t => t.PersonId, opt => opt.Ignore())
               .ForMember(t => t.Note, opt => opt.Ignore())
               .ForMember(t => t.AuthorId, opt => opt.Ignore())
               .ForMember(t => t.CreatedBy, opt => opt.Ignore())
               .ForMember(t => t.Created, opt => opt.Ignore());

查询 - 请注意此处返回的 PersonNote 是 Genistar.Organisation.Models.User.PersonNote 类型

[FunctionName(nameof(GetPersonNote))]
[UsedImplicitly]
public Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
            int id) => _helper.HandleAsync(async () =>
                               {
                                   var personNotes = _organisationRepository.GetPersonNotes(id);
                                   return new OkObjectResult(_mapper.Map<PersonNote>(personNotes));
                               });

 public IEnumerable<PersonNote> GetPersonNotes(int personId)
 {
     var PersonNotes1 = _context.PersonNotes.Where(p => p.PersonId == personId);
     return PersonNotes1;
 }

标签: entity-frameworkentity-framework-coreautomapper

解决方案


您在此处的示例没有意义,您不能在同一个命名空间中有 2 个“PersonNote”类。看起来您有实体,然后将 ViewModel/DTO 分开,尽管将它们命名相同会令人困惑。如果这是需要或需要的,那么我建议给命名空间起别名。IE:

using Models = Genistar.Organisation.Models;
using DTOs = Genistar.Organisation.Models.DataModels;

这样代码就可以明确读取哪个是什么。

例如,这里的代码没有多大意义:

public Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
        int id) => _helper.HandleAsync(async () =>
{
    var personNotes = _organisationRepository.GetPersonNotes(id);
    return new OkObjectResult(_mapper.Map<PersonNote>(personNotes));
});

public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
    var PersonNotes1 = _context.PersonNotes.Where(p => p.PersonId == personId);
    return PersonNotes1;
}

我假设这些是从 2 个不同的类中提取的,因为第一种方法看起来应该处理 DTO,而第二种方法应该来自存储库并处理实体?

有几件事确实很突出。首先,存储库方法应该返回IQueryable<T>而不是IEnumerable<T>. 这样做的好处是它将允许您进一步链接 Linq 表达式,包括允许 Automapper 将您的集合减少到您的 DTO 并将该优化传递给组合的 SQL。

第二件事是利用 Automapper 的ProjectTo方法而不是Map. 结合IQueryable这一点,可以在 SQL 级别完成映射:

因此存储库方法更改为:

public IQueryable<PersonNote> GetPersonNotes(int personId)
{
    var PersonNotes1 = _context.PersonNotes.Where(p => p.PersonId == personId);
    return PersonNotes1;
}

然后你的控制器代码:

public Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
        int id) => _helper.HandleAsync(async () =>
    {
       var personNotes = _organisationRepository.GetPersonNotes(id).ProjectTo<DTO.PersonNote>(_mapper.ConfigurationProvider);
       return new OkObjectResult(personNotes);
    });

当您的存储库返回IEnumerable时,映射器调用将尝试根据预先加载的内容以面值浏览属性,并处理 EF 插入的任何代理类型。我不是 100% 了解不同 EF Core 版本在 EF6 上的差异,但您现有的代码似乎被代理类型绊倒了。Map即便如此,如果没有这个,Automapper调用会在遍历属性时延迟加载任何相关实体,从而导致额外的 DB 命中。


推荐阅读