AutoMapper: Map single object to IEnumerable/Collection of children while passing properties

1.7k views Asked by At

I have the following two ShopifySharp objects.

public class Product
{            
    public string Title { get; set; }

    public string BodyHtml { get; set; }

    public IEnumerable<ProductVariant> Variants { get; set; }
}


public class ProductVariant 
{
    public string Title { get; set; }

    public string Barcode { get; set; }

}

I then have the following model

public class ShopifyVariant 
{
    public long Id { get; set; }

    public long ProductId { get; set; }

    public string ProductName { get; set; }

    public string VariantName { get; set; }

    public string Price { get; set; }
}

I want to map an instance of ShopifySharp.Product to IEnumerable<ShopifyVariant> because each ShopifySharp.Product will always have have AT LEAST a single ProductVariant.

Usually this would be a trivial problem because you could simply create a map between the two objects along the lines of:

this.CreateMap<ShopifySharp.ProductVariant, ShopifyVariant>()

BUT I need to get the ProductName and ProductId for each variant which can only be obtained from the ShopifySharp.Product object.

My initial thought was to try something like this

this.CreateMap<ShopifySharp.ProductVariant, ShopifyVariant>()
.ForMember(o => o.Id, o => o.MapFrom(f => f.Id))
.ForMember(o => o.ProductId, o => o.MapFrom((src, dest, m, c) => c.Items["ProductId"]))
.ForMember(o => o.ProductName, o => o.MapFrom((src, dest, m, c) => c.Items["ProductName"]))
.ForMember(o => o.VariantName, o => o.MapFrom(f => f.Title));

this.CreateMap<ShopifySharp.Product, IEnumerable<ShopifyVariant>>()

But it was unclear to me how to actually create the projection from Product to IEnumerable.

I'm currently playing with using a Custom ITypeConverter but haven't worked it out yet.

Can anyone help? How do you map a single instance of an object to a collection of reduced entities?

2

There are 2 answers

0
Maxim Gershkovich On

I have solved the problem (suboptimally) using the following mappings.

this.CreateMap<ShopifySharp.ProductVariant, ShopifyVariant>().ForMember(o => o.Id, o => o.MapFrom(f => f.Id))
.ForMember(o => o.ProductId, o => o.MapFrom(f => f.ProductId))
.ForMember(o => o.VariantName, o => o.MapFrom(f => f.Title))
.ForMember(o => o.Barcode, o => o.MapFrom(f => f.Barcode))
.ForMember(o => o.Price, o => o.MapFrom(f => f.Price))
.ForMember(o => o.Grams, o => o.MapFrom(f => f.Grams))
.ForMember(o => o.Taxable, o => o.MapFrom(f => f.Taxable))
.ForMember(o => o.Weight, o => o.MapFrom(f => f.Weight))
.ForMember(o => o.WeightUnit, o => o.MapFrom(f => f.WeightUnit))
.ForMember(o => o.SKU, o => o.MapFrom(f => f.SKU));

this.CreateMap<ShopifySharp.Product, ShopifyVariant>().ForMember(o => o.ProductName, o => o.MapFrom(f => f.Title));

this.CreateMap<IEnumerable<ShopifySharp.Product>, IEnumerable<ShopifyVariant>>()
.ConvertUsing<ShopifyProductsToVariantsCollectionTypeConverter>();

internal class ShopifyProductsToVariantsCollectionTypeConverter : ITypeConverter<IEnumerable<ShopifySharp.Product>, IEnumerable<ShopifyVariant>>
{
    public IEnumerable<ShopifyVariant> Convert(IEnumerable<ShopifySharp.Product> source, IEnumerable<ShopifyVariant> destination, ResolutionContext context)
    {
        var result = new List<ShopifyVariant>();
        foreach (var product in source)
        {
            foreach (var variant in product.Variants)
            {
                var mappedVariant = context.Mapper.Map<ShopifyVariant>(variant);
                context.Mapper.Map(product, mappedVariant);
                result.Add(mappedVariant);
            }
        }

        return result;
    }
}

Would love to see a better approach.

2
Lucian Bargaoanu On

As I've already said in the comments, some LINQ would help a lot here. But here it is. You can apply the same idea to your version. Most of the MapFroms you have are useless.

c.CreateMap<ProductVariant, ShopifyVariant>()
    .ForMember(o => o.ProductId, o => o.MapFrom((src, dest, m, context) => context.Items["ProductId"]))
    .ForMember(o => o.ProductName, o => o.MapFrom((src, dest, m, context) => context.Items["ProductName"]))
    .ForMember(o => o.VariantName, o => o.MapFrom(f => f.Title));
c.CreateMap<Product, IEnumerable<ShopifyVariant>>().ConvertUsing((product,variant,context)=>
{
    context.Items["ProductId"] = product.Id;
    context.Items["ProductName"] = product.Name;
    return context.Mapper.Map<List<ShopifyVariant>>(product.Variants);
});

new []{new Product{Id=1,Variants=new[]{new ProductVariant{Id=1},new ProductVariant{Id=2}}}, new Product{Id=2,Variants=new[]{new ProductVariant{Id=3},new ProductVariant{Id=4}}}}
.SelectMany(product=>mapper.Map<List<ShopifyVariant>>(product,_=>{}));

The IEnumerable map can be omitted and replaced with the same code inline, when calling Map.