automapper unflatten excludes value from parent ob

2019-08-09 03:16发布

问题:

I've a situation where entity framework core (2.0) is performing additional work to a parent table when I update a row in a child table. I've pin-pointed the cause to a value not being set in the unflattened object tree produced by AutoMapper (I'm not saying it is an error in AutoMapper; it's probably more to do with my code).

I'm using ASP.NET Core 2.0, C#, EF Core 2.0 and AutoMapper for the API development side. The database already exists and the EF classes scaffolded from it.

To keep it short, the child table is Note and the parent table is NoteType. The EF classes (extraneous columns removed) are as follows :

//Entity classes
public partial class Note
    {
        public int NoteBookId { get; set; }
        public short NoteSeqNo { get; set; }
        public short NoteTypeId { get; set; }
        public string NoteText { get; set; }

        public NoteBook NoteBook { get; set; }
        public NoteType NoteType { get; set; }
    }

public partial class NoteType
    {
        public NoteType() { Note = new HashSet<Note>(); }
        public short NoteTypeId { get; set; }
        public string NoteTypeDesc { get; set; }
        public ICollection<Note> Note { get; set; }
    }

//DTO class
    public class NoteDto
    {
        public int NoteBookId { get; set; }
        public short NoteSeqNo { get; set; }
        public short NoteTypeId { get; set; }
        public string NoteTypeNoteTypeDesc { get; set; }
        public string NoteText { get; set; }
    }

 public class NoteTypeDto
    {
        public short NoteTypeId { get; set; }
        public string NoteTypeDesc { get; set; }
    }
  • (NoteBookId + NoteSeqNo) is Note's primary key.
  • NoteTypeId is the NoteType's primary key.

Configuration

This is the AutoMapper configuration:

// config in startup.cs
config.CreateMap<Note,NoteDto>().ReverseMap();
config.CreateMap<NoteType,NoteTypeDto>().ReverseMap();

Read the data

As a result of data retrieval I get the expected result and the parent note type description is populated.

// EF get note in repository
            return await _dbcontext.Note
                .Where(n => n.NoteId == noteId && n.NoteSeqNo == noteSeqNo)
                .Include(n => n.NoteType)
                .FirstOrDefaultAsync();

// Get note function in API controller
                Note note = await _repository.GetNoteAsync(id, seq);
                NoteDto noteDto = Mapper.Map<NoteDto>(note);

Example JSON result:

{
    "noteBookId": 29,
    "noteSeqNo": 19,
    "noteTypeId": 18,
    "noteTypenoteTypeDesc": "ABCDE",
    "noteText": "My notes here."
}

Update the data

When the process is reversed during an update, the API controller maps the dto to the entity

Mapper.Map<Note>(noteDto)

Then when it is passed to EF by the repository code, EF tries to add a NoteType row with id 0. The unflattened object tree looks like this:

Note
    NoteBookId = 29
    NoteSeqNo = 19
    NoteTypeId = 18
    NoteTypeNoteTypeDesc = "ABCDE"
    NoteText = "My notes updated."
    NoteType.NoteTypeDesc = "ABCDE"
    NoteType.NoteTypeId = 0

The parent id column (NoteType.NoteTypeId) value is 0 and is not assigned the value of 18 which is what I expected.

(During debugging I manually set NoteType.NoteTypeId to 18 to ensure EF did nothing with it).

To work around this at the moment I nullify the NoteType in the Note in the repository code.

Should I expected AutoMapper to populate all the parent properties with setters or have I missed some configuration? Perhaps there is a glaring flaw in my approach?

回答1:

When AutoMapper reverses the mapping, it has to collect all information for nested objects from the flat object. Your DTO only carries a value for the mapping NoteType -> NoteTypeDesc. Not for NoteType -> NoteTypeId, so AM really doesn't have any idea where to get that value from.

If you want to rely on flattening only, the only way to change that is to add a flattened NoteTypeId to the DTO besides the unflattened one:

public class NoteDto
{
    public int NoteBookId { get; set; }
    public short NoteSeqNo { get; set; }
    public short NoteTypeId { get; set; } // Not flattened
    public short NoteTypeNoteTypeId { get; set; } // Flattened
    public string NoteTypeNoteTypeDesc { get; set; }
    public string NoteText { get; set; }
}

The alternative is to add this to your mapping:

config.CreateMap<Note, NoteDto>()
    .ForMember(dest => dest.NoteTypeId,
        e => e.MapFrom(src => src.NoteType.NoteTypeId))
    .ReverseMap();


回答2:

MapFrom-s (including the default unflattening) are reversed now. You can drop ReverseMap and create the maps, ignore Note.NoteType or ignore the offending path, Note.NoteType.NoteTypeDesc.