简体   繁体   中英

Map list to existing list in Automapper using a key

Automapper可以轻松处理将一个对象类型列表映射到另一个不同对象类型的列表,但是是否可以使用ID作为键将其映射到现有列表?

I have not found better way than the following.

Here are source and destination.

public class Source
{
    public int Id { get; set; } 
    public string Foo { get; set; }
}

public class Destination 
{ 
    public int Id { get; set; }
    public string Foo { get; set; }
}

Define converter (You should change List<> to whatever type you are using).

public class CollectionConverter: ITypeConverter<List<Source>, List<Destination>>
{
    public List<Destination> Convert(ResolutionContext context)
    {
        var destinationCollection = (List<Destination>)context.DestinationValue;
        if(destinationCollection == null)
            destinationCollection = new List<Destination>();
        var sourceCollection = (List<Source>)context.SourceValue;
        foreach(var source in sourceCollection)
        {
            Destination matchedDestination = null;

            foreach(var destination in destinationCollection)
            {
                if(destination.Id == source.Id)
                {
                    Mapper.Map(source, destination);
                    matchedDestination = destination;
                    break;
                }
            }
            if(matchedDestination == null)
                destinationCollection.Add(Mapper.Map<Destination>(source));
        }
        return destinationCollection;
    }
}

And here is actual mapping configuration and example.

Mapper.CreateMap<Source,Destination>();
Mapper.CreateMap<List<Source>,List<Destination>>().ConvertUsing(new CollectionConverter());

var sourceCollection = new List<Source>
{
    new Source{ Id = 1, Foo = "Match"},
    new Source{ Id = 2, Foo = "DoesNotMatchWithDestination"}
};
var destinationCollection = new List<Destination>
{
    new Destination{ Id = 1, Foo = "Match"},
    new Destination{ Id = 3, Foo = "DoeNotMatchWithSource"}
};
var mergedCollection = Mapper.Map(sourceCollection, destinationCollection);

You should get the following result.

映射结果

I found this article very useful and as such I thought I would feed back in my generic version of the type converter which you can use to select the property to match on from each object.

Using it all you need to do is:

// Example of usage
Mapper.CreateMap<UserModel, User>();
var converter = CollectionConverterWithIdentityMatching<UserModel, User>.Instance(model => model.Id, user => user.Id);
Mapper.CreateMap<List<UserModel>, List<User>>().ConvertUsing(converter);

//The actual converter
public class CollectionConverterWithIdentityMatching<TSource, TDestination> : 
    ITypeConverter<List<TSource>, List<TDestination>> where TDestination : class
{
    private readonly Func<TSource, object> sourcePrimaryKeyExpression;
    private readonly Func<TDestination, object> destinationPrimaryKeyExpression;

    private CollectionConverterWithIdentityMatching(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
    {
        this.sourcePrimaryKeyExpression = sourcePrimaryKey.Compile();
        this.destinationPrimaryKeyExpression = destinationPrimaryKey.Compile();
    }

    public static CollectionConverterWithIdentityMatching<TSource, TDestination> 
        Instance(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
    {
        return new CollectionConverterWithIdentityMatching<TSource, TDestination>(
            sourcePrimaryKey, destinationPrimaryKey);
    }

    public List<TDestination> Convert(ResolutionContext context)
    {
        var destinationCollection = (List<TDestination>)context.DestinationValue ?? new List<TDestination>();
        var sourceCollection = (List<TSource>)context.SourceValue;
        foreach (var source in sourceCollection)
        {
            TDestination matchedDestination = default(TDestination);

            foreach (var destination in destinationCollection)
            {
                var sourcePrimaryKey = GetPrimaryKey(source, this.sourcePrimaryKeyExpression);
                var destinationPrimaryKey = GetPrimaryKey(destination, this.destinationPrimaryKeyExpression);

                if (string.Equals(sourcePrimaryKey, destinationPrimaryKey, StringComparison.OrdinalIgnoreCase))
                {
                    Mapper.Map(source, destination);
                    matchedDestination = destination;
                    break;
                }
            }

            if (matchedDestination == null)
            {
                destinationCollection.Add(Mapper.Map<TDestination>(source));
            }
        }

        return destinationCollection;
    }

    private string GetPrimaryKey<TObject>(object entity, Func<TObject, object> expression)
    {
        var tempId = expression.Invoke((TObject)entity);
        var id = System.Convert.ToString(tempId);
        return id;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM