Using Automapper, mapping DTOs back to Entity Framework including referenced entities
Asked Answered
C

3

12

I've got POCO domain entities that are persisted using Entity Framework 5. They are obtained from the DbContext using a repository pattern and are exposed to a RESTful MVC WebApi application through a UoW pattern. The POCO entities are proxies and are lazy loaded.

I am converting my entities to DTOs before sending them to the client. I am using Automapper to do this and it seems to be working fine with Automapper mapping the proxy POCOs to DTOs, keeping the navigation properties intact. I am using the following mapping for this:

    Mapper.CreateMap<Client, ClientDto>();

Example of Domain/DTO objects:

[Serializable]
public class Client : IEntity
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public virtual string Name { get; set; }

    public virtual ICollection<ClientLocation> ClientLocations { get; set; }

    public virtual ICollection<ComplianceRequirement> DefaultComplianceRequirements { get; set; }

    public virtual ICollection<Note> Notes { get; set; }
}

public class ClientDto : DtoBase
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    public ICollection<ClientLocation> ClientLocations { get; set; }

    public ICollection<ComplianceRequirementDto> DefaultComplianceRequirements { get; set; }

    public ICollection<Note> Notes { get; set; }
}

Now I am trying to update my context using DTOs sent back up from the wire. I am having specific trouble with getting the navigational properties/related entities working properly. The mapping for this I'm using is:

    Mapper.CreateMap<ClientDto, Client>()
        .ConstructUsing((Func<ClientDto, Client>)(c => clientUow.Get(c.Id)));

Above, clientUow.Get() refers to DbContext.Set.Find() so that I am getting the tracked proxy POCO object from EF (that contains all of the related entities also as proxies).

In my controller method I am doing the following:

    var client = Mapper.Map<ClientDto, Client>(clientDto);
    uow.Update(client);

client successfully is mapped, as a proxy POCO object, however it's related entities/navigational properties are replaced with a new (non-proxy) POCO entity with property values copied from the DTO.

Above, uow.Update() basically refers to a function that performs the persist logic which I have as:

    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;
    _context.SaveChanges();

The above doesn't persist even persist the entity, let alone related ones. I've tried variations on the mappings and different ways to persist using detaching/states but always get "an object with the same key already exists in the ObjectStateManager" exceptions.

I've had a look at countless other threads and just can't get it all working with Automapper. I can grab a proxy object from the context and manually go through properties updating them from the DTO fine, however I am using Automapper to map domain -> DTO and it would be alot more elegant to use it to do the reverse, since my DTOs resemble my domain objects to a large extent.

Is there a textbook way to handle Automapper with EF, with Domain Objects/DTOs that have navigational properties that also need to be updated at the same time?

UPDATE:

    var originalEntity = _entities.Find(entity.Id);
    _context.Entry<T>(originalEntity).State = System.Data.EntityState.Detached;
    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;

The above persistence logic updates the 'root' EF proxy object in the context, however any related entities are not updated. I'm guessing that this is due to them not being mapped to EF proxy objects but rather plain domain objects. Help would be most appreciated!

UPDATE: It seems that what I'm trying to achieve is not actually possible using the current version of EF(5) and that this is a core limitation of EF and not to do with Automapper:

Link

Link

I guess it's back to doing it manually. Hope this helps someone else who is wondering the same.

Carcinomatosis answered 3/1, 2013 at 19:6 Comment(4)
Should it be ICollection<ClientLocationDto> and ICollection<NoteDto> in your ClientDto class? (As you also have ComplianceRequirementDto). Did you map these Dto's the same way as ClientDto?Viafore
@GertArnold yes thank you, you are correct, but that's not relevant to the problem. I'm working on a standardised way of addressing the original questions, which I may post up as an answer when it's ready.Carcinomatosis
@Carcinomatosis Any progress on your standardised way? It feels like some more Automapper maps are needed, and some kind of recursive move through navigation properties in the Update using reflection.Trichotomy
Hey! Just wondering if you got a chance to try out my answer below? If so, could you mark is as the right answer? Many thanks!!Fults
S
1

What you want to do is get the Entity from the database first:

var centity = _context.Client.First(a=>a.Id = id)

Then you map over this and update (this is what you were looking for, it will only map things it finds in the inputDTO, and leave the other properties alone)

Mapper.Map<UpdateClientInput,  Client>(inputDto, centity);
_context.update();
Samarasamarang answered 5/9, 2017 at 2:45 Comment(0)
C
0

You have allready identified the problem:

The above persistence logic updates the 'root' EF proxy object in the context, however any related entities are not updated

You are setting the modified state on the root node only. You must write code to iterate through all the objects and set the state to modified.

Calvados answered 29/11, 2013 at 22:46 Comment(1)
As I stated, "back to doing it manually". "This is a core limitation of EF". Automapper will not be able to help.Carcinomatosis
F
0

I implemented a pattern to handle this hierarchy model state with EF.

Every entity model class implements an interface like below, as do the view model classes:

public interface IObjectWithState
{
    ObjectState ObjectState { get; set; }
}

The ObjectState enumeration is defined below:

public enum ObjectState
{
    Unchanged  = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3
}

For example when saving a deep hierarchy of objects using EF, I map the view model objects to their equivalent entity objects, including the ObjectState.

I then attach the root entity object to the context (and consequently all child objects):

dbContext.MyCustomEntities.Attach(rootEntityObj);

I then have an extension method on the DbContext that loops through all the items in the context's change tracker and update each entity's state (as you have done above).

    public static int ApplyStateChanges(this DbContext context)
    {
        int count = 0;
        foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        {
            IObjectWithState stateInfo = entry.Entity;
            entry.State = ConvertState(stateInfo.ObjectState);
            if (stateInfo.ObjectState != ObjectState.Unchanged)
                count++;
        }
        return count;
    }

Then we can simply save the changes as normal:

dbContext.SaveChanges();

This way, all the hierarchy of child objects will be updated accordingly in the database.

Fults answered 7/4, 2017 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.