Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving AutoMapper mapped Collections of Entities using Entity Framework

I have the following Entity Framework Entities:

public class Region
{
    public int RegionId { get; set; } // Primary Key
    public string Name { get; set; }
    public virtual ICollection<Country> Countries { get; set; } // Link Table
}
public class Country
{
    public int CountryId { get; set; } // Primary Key
    public string Name { get; set; }
    public int RegionId { get; set; } // Foreign Key
}

I map these using AutoMapper to the following ViewModels:

public class RegionViewModel
{
    public int RegionId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<int> Countries { get; set; }
}
public class CountryViewModel
{
    public int CountryId { get; set; }
    public string Name { get; set; }
}

I want to translate my ViewModels to Entities using AutoMapper so I can save a new Region. This is my mapping code:

Mapper.CreateMap<RegionViewModel, Region>()
    .ForMember(x => x.Countries, x => x.MapFrom(y => y.Countries.Select(z => new Country() { CountryId = z }).ToArray()));

This causes an exception when adding the region in the repository as it also tries to create a new instance of Country with a null Name. One solution is to change the Add method in my repository to set the State of the country objects to Unchanged.

public async Task Add(Region region)
{
    foreach (Country country in region.Countries)
    {
        this.Context.Entry(country).State = EntityState.Unchanged;
    }
    await base.Add(region);
}

The other alternative solution is to use more complicated translation logic which uses another repository to get the real country objects. This approach has slower performance because it has to make an extra call to the database but you also get a more complete Region object.

Mapper.CreateMap<RegionViewModel, Region>();
Mapper.CreateMap<int[], Country[]>().ConvertUsing(x => countryRepository.GetAll().Result.Where(y => x.Contains(y.CountryId)).ToArray());

I lean to the first one but what is the correct approach?

like image 922
Muhammad Rehan Saeed Avatar asked Mar 18 '15 13:03

Muhammad Rehan Saeed


People also ask

Can AutoMapper map collections?

AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

What is AutoMapper in Entity Framework?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.

Is AutoMapper faster than manual mapping?

Automapper is considerably faster when mapping a List<T> of objects on . NET Core (It's still slower on full . NET Framework).


1 Answers

The first method, together with the loop to set the states to UnChanged, is definitely the best one. It is lightweight, because you don't needlessly fetch Countrys from the database. Instead, by the mapper part...

y.Countries.Select(z => new Country() { CountryId = z })

...you create stub entities, i.e. incomplete entities that serve as a placeholders for the real things. That's a commonly recommended approach to reduce network traffic.

Setting the states to UnChanged is one of several ways to attach the stub Countrys to the context. You have to attach them before calling base.Add(region) (which I assume adds the region to the Regions of the context), because Add marks all entities in an object graph off the added entity as new (Added) when they're not yet attached to the context.

like image 138
Gert Arnold Avatar answered Oct 08 '22 05:10

Gert Arnold