Search code examples
c#asp.net-coreasp.net-core-mvcautomapper-5

AutoMapper 5.2.0 VIewModel to Model issue in Core MVC


Models:

 public class Client{
    public int Id {get;set;}

    public string Name {get;set;}

    public Address Address {get;set;}

    public int AddressId  {get;set;}
}

public class Address{
   public int Id

   public string Address1 {get;set;}

   public string PostCode {get;set;}
}

View Models:

public class ClientViewNodel{
        public int Id {get;set;}

        public string Name {get;set;}

        public Address Address {get;set;}

        public int AddressId  {get;set;}
    }

    public class AddressViewModel{
       public int Id

       public string Address1 {get;set;}

       public string PostCode {get;set;}
    }

Mapping:

 Mapper.Initialize(config =>
    {
        config.CreateMap<ClientViewModel, Client>().ReverseMap();
        config.CreateMap<AddressViewModel, Address>().ReverseMap();
    });

Controller Update action:

[HttpPost]
public async Task<IActionResult> Update(cLIENTViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        return View("Client",viewModel);
    }

    var client= _clientRepository.GetClient(viewModel.Id);
    if (client == null) 
        return NotFound();

    client= Mapper.Map<ClientViewModel, Client>(viewModel);

    _clientRepository.Update(client);
    var result = await _clientRepository.SaveChangesAsync();

    if (result.Success)
    {
        return RedirectToAction("Index");
    }

    ModelState.AddModelError("", result.Message);

    return View("Client",viewModel);
}

The problem is that when _clientRepository.Update(client) is called I get an error message saying that:

The instance of entity type 'Client' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

When I debugged the code I can see that when I map the viewModel to model The AddressID in client model is set to 0. I am guessing that this is causing the problem.

How can I map the viewModel back to model where the details of address will be updated such as Address1 and Postcode instead of the Id.

I have also tried to ignore mapping for Id for Address in the mapping with .ForMember(x => x.AddressId, opt => opt.Ignore())

But still it sets the AddressId to 0.

What am I missing?


Solution

  • When you do Mapper.Map<ClientViewModel, Client>(viewModel), AutoMapper creates a new Client object, with the same ID as the existing Client object.

    You then instruct EntityFramework to Update this object graph. Entity Framework is not tracking your new Client object, so it attaches the object to its internal magic cache/tracking sauce. This fails because of an ID collission. Hence the error "The instance of entity type 'Client' cannot be tracked because another instance of this type with the same key is already being tracked".

    This is also the source of the 0 AddressId. The Address object is also a brand new object, created by AutoMapper, and the property has the value default(int), because it's never assigned another value by AutoMapper after it's been created.