Mapping collection of entities in EF with AutoMapper

In my last post I explained why is useful to add base entity class in EF. Today I will write how with use of this base class an AutoMapped map collection of data objects (i.e. DTOs to existing collection of entities).

Problem with doing:

dataColection.MapTo(entitiyCollection);

is that AutoMapper removes all entities from entity collection because data item mapped to entity has different hash code and different reference then original entity. Then when AutoMapper search for same item in original entity collection as mapped entity, it can not find one. That is causing AutoMapper to ads another one entity with the same Id as original, after removing original entity. Entity collection changed in that way cannot be saved to database, because EF complaints that removed entities has to be removed explicitly from database on commit.

To fix that problem we will use custom ValueResolver. To create one we will create class which will derive from IValueResolver available in AutoMapper assembly.

public interface IValueResolver
{
    ResolutionResult Resolve(ResolutionResult source);
}

There is also available ValueResolver<T1,T2>:

public abstract class ValueResolver<TSource, TDestination> : IValueResolver
{
    protected ValueResolver();

    public ResolutionResult Resolve(ResolutionResult source);
    protected abstract TDestination ResolveCore(TSource source);
}

But this class make available to override only ResolveCore method, which will be not sufficient since it does not have information about destination type of entity. Without this information we wont be able to create generic resolver class. So instead this class we will use interface.

Our generic mapping class has to take two type parameters type of data object (DTO) and type of entity. Also ResolutionResult object of auto mapper mapping context does not have information of which source member is being mapped inside ValueResolver. This information has to be passed to. It is best to passed it as expression instead of a string, to make it less error prone. To make it possible we will add third type parameter which will be parent type of data object collection.

public class EntityCollectionValueResolver<TSourceParent, TSource, TDest> : IValueResolver
    where TSource : DTOBase
    where TDest : BaseEntity, new()
{
    private Expression<Func<TSourceParent, ICollection>> sourceMember;

    public EntityCollectionValueResolver(Expression<Func<TSourceParent, ICollection>> sourceMember)
    {
        this.sourceMember = sourceMember;
    }

    public ResolutionResult Resolve(ResolutionResult source)
    {
        //get source collection
        var sourceCollection = ((TSourceParent)source.Value).GetPropertyValue(sourceMember);
        //if we are mapping to existing collection of entities...
        if (source.Context.DestinationValue != null)
        {
            var destinationCollection = (ICollection<TDest>)
                //get entities collection parent
                source.Context.DestinationValue
                //get entities collection by member name defined in mapping profile
                .GetPropertyValue(source.Context.MemberName);
            //delete entities that are not in source collection
            var sourceIds = sourceCollection.Select(i => i.Id).ToList();
            foreach (var item in destinationCollection.ToList())
            {
                if (!sourceIds.Contains(item.Id))
                {
                    destinationCollection.Remove(item);
                }
            }
            //map entities that are in source collection
            foreach (var sourceItem in sourceCollection)
            {
                //if item is in destination collection...
                var originalItem = destinationCollection.Where(o => o.Id == sourceItem.Id).SingleOrDefault();
                if (originalItem != null)
                {
                    //...map to existing item
                    sourceItem.MapTo(originalItem);
                }
                else
                {
                    //...or create new entity in collection
                    destinationCollection.Add(sourceItem.MapTo<TDest>());
                }
            }
            return source.New(destinationCollection, source.Context.DestinationType);
        }
        //we are mapping to new collection of entities...
        else
        {
            //...then just create new collection
            var value = new HashSet<TDest>();
            //...and map every item from source collection
            foreach (var item in sourceCollection)
            {
                //map item
                value.Add(item.MapTo<TDest>());
            }
            //create new result mapping context
            source = source.New(value, source.Context.DestinationType);
        }
        return source;
    }
}

Expression of type Expression<Func<TSourceParent, ICollection>> help as to make sure that inside Resolve method we will get correct property without necessity of using existing object source or creating new one to pass in inside some lambda.
GetPropertyValue method is extension of object type. It works by taking MemberExpression from our Expression<Func<TSourceParent, ICollection>>, and then property MemberExpression.Member.Name of source member. After that with source property name we can take its value with reflection:

public static TRet GetPropertyValue<TObj, TRet>(this TObj obj,
	Expression<Func<TObj, TRet>> expression,
	bool silent = false)
{
	var propertyPath = ExpressionOperator.GetPropertyPath(expression);
	var objType = obj.GetType();
	var propertyValue = objType.GetProperty(propertyPath).GetValue(obj, null);
	return propertyValue;
}

public static MemberExpression GetMemberExpression(Expression expression)
{
	if (expression is MemberExpression)
	{
                return (MemberExpression)expression;
        }
        else if (expression is LambdaExpression)
        {
                var lambdaExpression = expression as LambdaExpression; 
                if (lambdaExpression.Body is MemberExpression)
                {
                        return (MemberExpression)lambdaExpression.Body;
                }
                else if (lambdaExpression.Body is UnaryExpression)
                {
                        return ((MemberExpression)((UnaryExpression)lambdaExpression.Body).Operand); 
                }
        }
        return null;
}

Whole Resolve method is enclosed in if statement:

if (source.Context.DestinationValue != null)

this will ensure that we cover 2 case when we map data collection to existing collection of entities and to new collection of entities. Second case is inside else and is not complicated since it is simple mapping of all items inside collection.
Interesting part is happening inside if and it is composed from three phases:

1. Deleting of entities

All entities from destination collection, that are not present inside our data collection, are being deleted. That prevents EF from throwing an error mentioned above. Entities and DTOs have both Ids, which are used to find which of items was deleted. This is where base entity class is useful since it has Id defined inside.

2. Mapping changed items.

If entity with the same Id as item in data collection has been found, it is being used as destination of mapping

3. Mapping of new (added) entities, as new objects.

This generic class then can be used as this inside AutoMapper profile:

CreateMap<ParentDTO,ParentEntity>()           
                .ForMember(o => o.DestinationCollection, m =>
                        m.ResolveUsing(new EntityCollectionValueResolver<
                            ParentDTO, SourceDTO, DestEntity>
                            (s => s.SourceCollection))
                           )
            ;

One more thing: this solution will cause StackOverflowException if SourceDTO to DestEntity mapping profile will try to map again ParentDTO -> ParentEntity, from ParentEntity property inside DestEntity. Usually child entities has reference to parent entities. If they are not ignored during mapping, AutoMapper will try do mapping: ParentDTO -> SourceCollection -> SourceDTO -> SourceEntity -> ParentDTO which will cause circular mapping.

Also this resolver will not cover case when Destination Collection is collection of derived items from parent item. For example when you have collection of people with students and teachers inside it, this will try to do mapping only for people. All derived types data will be ignored.

Unfortunately this will be not enough to map collection. Its because even items are removed collection, they are not set for deletion inside DbContext class. This will cause critical error in application during SaveChanges method in DbContext. To correct that issue we have to mark them for deletion from context class.

To do that there are 3 options:

1. Use context class inside EntityCollectionValueResolver class and mark deleted items for deletion. This is less elegant, but much more quick solution.

2. Use custom collection class which will mark deleted items for deletion using context class

3. Use custom collection class with items state tracking. This collection could subscribe an OnSaveChanges event of DBContext class in which event handler delete from context items deleted before from collection.

First and second options (maybe third too, it depends from implementation) will suffer from necessity to synchronize DbContext which will be used to save changes inside parent entity. Entity which was mapped from DTO. Also first solution is less elegant because is mixing AutoMapper and DbContext. Those two should live separately.

In this article I will show second option since third, which is better I think will involve changing of entities classes and repositories. It’s to much for one article.

First, we have to acquire instance of context class. In my application I have IoC container which have single-for-thread instance of this class. This makes sure of synchronization of Context which loaded parent entity, deletes child entities and will save changes to parent entity.

At the beginning of the method Resolve we will add code that will return current instance of Context class (example with using Microsoft Patterns & Practices IServiceLocator implementation):

var context = ServiceLocator.GetInstance<DbContext>();

With this instance we can delete items from context:

if (!sourceIds.Contains(item.Id))
{
    destinationCollection.Remove(item);
    ((IObjectContextAdapter)context).ObjectContext.DeleteObject(item);
}

After that ObjectContextManager private property _entriesWithConceptualNulls, will have 0 items, which is good because any item in this collection will cause EF to throw critical error.

With breakpoint set after line with DeleteObject method call, you can see this collection with expression:

(context.Database._internalContext).ObjectContext.ObjectStateManager._entriesWithConceptualNulls

as like in the image:

This is whole body of Resolve method:

public ResolutionResult Resolve(ResolutionResult source)
{
        var context = ServiceLocator.GetInstance<DbContext>();
        //get source collection
        var sourceCollection = ((TSourceParent)source.Value).GetPropertyValue(sourceMember);
        //if we are mapping to existing collection of entities...
        if (source.Context.DestinationValue != null)
        {
            var destinationCollection = (ICollection)
                //get entities collection parent
                source.Context.DestinationValue
                //get entities collection by member name defined in mapping profile
                .GetPropertyValue(source.Context.MemberName);
            //delete entities that are not in source collection
            var sourceIds = sourceCollection.Select(i => i.Id).ToList();
            foreach (var item in destinationCollection.ToList())
            {
                if (!sourceIds.Contains(item.Id))
                {
                    destinationCollection.Remove(item);
                    ((IObjectContextAdapter)context).ObjectContext.DeleteObject(item);
                }
            }
            //map entities that are in source collection
            foreach (var sourceItem in sourceCollection)
            {
                //if item is in destination collection...
                var originalItem = destinationCollection.Where(o => o.Id == sourceItem.Id).SingleOrDefault();
                if (originalItem != null)
                {
                    //...map to existing item
                    sourceItem.MapTo(originalItem);
                }
                else
                {
                    //...or create new entity in collection
                    destinationCollection.Add(sourceItem.MapTo());
                }
            }
            return source.New(destinationCollection, source.Context.DestinationType);
        }
        //we are mapping to new collection of entities...
        else
        {
            //...then just create new collection
            var value = new HashSet();
            //...and map every item from source collection
            foreach (var item in sourceCollection)
            {
                //map item
                value.Add(item.MapTo());
            }
            //create new result mapping context
            source = source.New(value, source.Context.DestinationType);
        }
        return source;
    }
}

From now on after mapping from DTO to entity with automapper and saving mapped entity to database should work just fine.

That is all! 🙂

2 Replies to “Mapping collection of entities in EF with AutoMapper”

  1. Hello, could you please provide working sample? There are some dependencies missing and code is not compilable. Would be really appreciated!

    Regards, Jozef.

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve : *
15 + 4 =