0

In my current .net core mvc project with entity framework, I use automapper with DI. In my edit method I need to update the domain model using a viewmodel. This is where I use automapper. But the viewmodel contains only a few properties of the domainmodel. The other properties should be retained, however, they are set to null or defaults when I apply the _context.Update(modelToSubmit); How can I update only the fields in the domain model that I actually want to change (those that are in the viewmodel)?

The way its currently done in this project is by adding all the properties in the viewmodel, and using the @Html.HiddenFor tag in the view, which I regard as a bad practice, and I want to get rid of.

I have a single line of code for the mapping:

DomainModel model = _mapper.Map<DomainModel>(viewModel);

Edit: I now have (based on answer below):

DatabaseModel originalRecord = _unitOfWork.DatabaseModel.Get(id);
var mappedModel = _mapper.Map<ViewModel>(originalRecord);

var newDto = _mapper.Map<ViewModel, DataBaseModel>(returnedViewModel, originalRecord);

originalRecord = _mapper.Map<ViewModel, DatabaseModel>(mappedModel);
_unitOfWork.DatabaseModel.Update(originalRecord);

My mappingprofile:

CreateMap<ViewModel, DatabaseModel>()
            .IgnoreAllPropertiesWithAnInaccessibleSetter()
            .ForPath(dest => dest.ID, opt => opt.Ignore())
            .ForPath(dest => dest.Deadline, opt => opt.Ignore())
            .ForPath(dest => dest.RecordCreated, opt => opt.Ignore())
        .ForPath(dest => dest.Status, opt => opt.Ignore())
        .ReverseMap();

When I save the model on the website, I get this error:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).

The query to retrieve originalRecord has a AsNoTracking() method, but it still throws this error.

3
  • I would normally just get the existing model from the database, update it with the relevant fields from the view model manually, then save. Although I'm aware that this approach doesn't really make use of automapper.. Commented Jan 22, 2020 at 16:59
  • yes, I have been doing this, but it gets kind of annoying and tedious, especially if the model has a lot of fields. Commented Jan 22, 2020 at 17:02
  • If you're using this same view model a lot, then you could extract the manual mapping into a reusable helper function, which will make it slightly less tedious. Commented Jan 22, 2020 at 17:07

1 Answer 1

2

First you need to create a ViewModel that only has the properties that user can change. Then you should configure your mapper to ignore non existent properties on the source.

configuration.CreateMap<MyViewModel, MyModel>()
                        .IgnoreAllPropertiesWithAnInaccessibleSetter()
                        .ForPath(s => s.Unmapped, opt => opt.Ignore())
                        .ForPath(s => s.Id, opt => opt.Ignore())
                        .ReverseMap();

In the example above I use .Ignore() on the Id property because EnityFramework will not accept your SaveChanges() if you change the value of the primary key. Even it was changed with the same value.

Also when you use _mapper.Map<DomainModel>(viewModel);you are creating a very new destination object, and all properties that does not exists is the source object will be null or default.

You should use

model = _mapper.Map<MyViewModel, MyModel>(viewModel, model);

You see an see a example in this .net fiddle: https://dotnetfiddle.net/oyFupE

And this link can give you more information about ignore properties on AutoMapper: https://dotnettutorials.net/lesson/ignore-using-automapper-in-csharp/

EDIT: You have more code than you need in the new code you added on your question. As you already has a ViewModel and get the Model from the database. Now you only need to call automapper once.

public void Update(ViewModel returnedViewModel)
{
    // Gets the orininal Model from DataBase.
    DatabaseModel originalRecord = _unitOfWork.DatabaseModel.Get(id);

    // Merge original model with the ViewModel   
    originalRecord = _mapper.Map<ViewModel, DatabaseModel>(mappedModel, originalRecord);

    // SaveChanges
    _unitOfWork.DatabaseModel.Update(originalRecord);
}

Probably the error you got happens because in one of the mappers your entity are losing the value of the Id. You can check it on debug.

Sign up to request clarification or add additional context in comments.

2 Comments

In the Fiddle example: var newDto = MyMapper.GetMapper().Map<ReturnedViewModel, MyModel>(viewModel, databaseModel); gives me an error that args do not mach models. Switching doesn't help. I post new updated code in OP
I post new information on the answer to help you with the updated question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.