10

I am having a heck of a time converting from older mapping standards to automapper.

Here are my classes

// Models
public class BaseModel
{
    public Int64 Id { get; set; }
    public Guid UniqueId { get; set; }
    public DateTime? CreateDate { get; set; }
    public DateTime? LastUpdate { get; set; }
} 

public class LibraryItemModel : BaseModel
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string URL { get; set; }
    public bool IsActive { get; set; }
    public List<LibraryCategoryModel> Categories { get; set; }
}   

public class LibraryCategoryModel : BaseModel
{
    public string Description { get; set; }
}


// Entity Classes

public partial class LibraryItem
{
    public LibraryItem()
    {
        this.LibraryItemCategories = new HashSet<LibraryItemCategory>();
    }

    public long Id { get; set; }
    public System.Guid UniqueId { get; set; }
    public string Description { get; set; }
    public string URL { get; set; }
    public System.DateTime CreateDate { get; set; }
    public System.DateTime LastUpdate { get; set; }
    public bool IsActive { get; set; }
    public string Name { get; set; }

    public virtual ICollection<LibraryItemCategory> LibraryItemCategories { get; set; }
}
// comes from a ternary table in DB... many to many
public partial class LibraryItemCategory
{
    public long LibraryItemId { get; set; }
    public long LibraryCategoryId { get; set; }
    public System.DateTime CreateDate { get; set; }
    public System.DateTime LastUpdate { get; set; }

    public virtual LibraryCategory LibraryCategory { get; set; }
    public virtual LibraryItem LibraryItem { get; set; }
}

public partial class LibraryCategory
{
    public LibraryCategory()
    {
        this.LibraryCategoryRoles = new HashSet<LibraryCategoryRole>();
        this.LibraryItemCategories = new HashSet<LibraryItemCategory>();
    }

    public long id { get; set; }
    public System.Guid UniqueId { get; set; }
    public string Description { get; set; }
    public System.DateTime CreateDate { get; set; }
    public System.DateTime LastUpdate { get; set; }

    public virtual ICollection<LibraryCategoryRole> LibraryCategoryRoles { get; set; }
    public virtual ICollection<LibraryItemCategory> LibraryItemCategories { get; set; }
}


    // Old Conversion code doing it the long way

    private LibraryItemModel Convert(Entities.LibraryItem libraryItem)
    {

        var newLibraryItem = new LibraryItemModel
        {
            UniqueId = libraryItem.UniqueId,
            Name = libraryItem.Name,
            Description = libraryItem.Description,
            URL = libraryItem.URL,
            CreateDate = libraryItem.CreateDate,
            LastUpdate = libraryItem.LastUpdate,
            IsActive = libraryItem.IsActive,
            Categories = new List<LibraryCategoryModel>()
        };

        foreach (var lc in libraryItem.LibraryItemCategories)
        {
            var newCategory = new LibraryCategoryModel
            {
                UniqueId = lc.LibraryCategory.UniqueId,
                Description = lc.LibraryCategory.Description,
                CreateDate = lc.LibraryCategory.CreateDate,
                LastUpdate = lc.LibraryCategory.LastUpdate
            };

            newLibraryItem.Categories.Add(newCategory);
        }

        return newLibraryItem;
    }


    // My attempt at automapper to go between the models and entities
    Mapper.CreateMap<EF.Entities.LibraryItem, LibraryItemModel>();
    Mapper.CreateMap<LibraryItemModel, EF.Entities.LibraryItem>();
          .ForMember(lim => lim.LibraryItemCategories, o => o.Ignore()
    Mapper.CreateMap<EF.Entities.LibraryCategory, LibraryCategoryModel>();
    Mapper.CreateMap<LibraryCategoryModel, EF.Entities.LibraryCategory>()
          .ForMember(lcm => lcm.LibraryCategoryRoles, o => o.Ignore())
          .ForMember(lcm => lcm.LibraryItemCategories, o => o.Ignore());

No matter how I configure ignores or custom mappings it seems to not like this nesting. Any Automapper experts out there who could tell me how a mapping with a complex object like this could be done. The enitity classes are being generated via an EF6 edmx file.

DRobertE
  • 3,478
  • 3
  • 26
  • 43
  • What do you mean when you say "seems not to like"? Is it not mapping the properties or do you get an error of some kind? – Andrew Whitaker Jul 17 '14 at 17:54
  • 1
    You're right, "seems not to like" is not clear. The root of the problem is, LibraryItemModel has a list of LibraryCategoryModels but has nothing to map to in LibraryItem Entity because the entity has LibraryItemCategories... Notice in the foreach loop it's nested to contain another foreach where I create another LibraryCategory from the list of LibraryItemCategories in LibraryItem... what I am looking for is a way to map the Categories property in LibraryItemModel so it is filled with LibraryCategory objects which are created from the list of LibraryItemCategories. Make sense? – DRobertE Jul 17 '14 at 17:59
  • Yep, that clears it up, thanks. – Andrew Whitaker Jul 17 '14 at 18:03
  • I assume some special projection or customresolver is needed but I'm at a loss on how to do this one... LibraryItemCategories is essentially a many to many ternary db table which is why the entities were generated the way they were – DRobertE Jul 17 '14 at 18:06

2 Answers2

13

So basically the problem here is that you want to map from each LibraryItemCategory that belongs to a LibraryItem to a LibraryCategoryModel that includes properties from each LibraryItemCatalog's LibraryCatalog property.

First you want to correctly map the collections to each other:

Mapper.CreateMap<LibraryItem, LibraryItemModel>()
    .ForMember(
        dest => dest.Categories, 
        opt => opt.MapFrom(src => src.LibraryItemCategories));

Next you need to worry about mapping each LibraryItemCategory inside of LibraryItem.LibraryItemCategories to a LibraryCatalogModel. As stated in the problem, you need to access each LibraryItemCategory's LibraryCatalog property and actually map from that instead. The way this looks is:

Mapper.CreateMap<LibraryItemCategory, LibraryCategoryModel>()
    .ConstructUsing(ct => Mapper.Map<LibraryCategoryModel>(ct.LibraryCategory))
    .ForAllMembers(opt => opt.Ignore());

Here, we're telling AutoMapper that to map from a LibraryItemCategory to a LibraryCategoryModel, we need to construct LibraryCategoryModel's using another call to Mapper.Map on the inner LibraryCategory property.

Next, all that's left to do is define the mapping from LibraryCategory to LibraryCategoryModel:

Mapper.CreateMap<LibraryCategory, LibraryCategoryModel>();

Now a call to Mapper.Map on the LibraryItem should take care of everything for you.


Alternatively, you could remove the map from LibraryItemCategory to LibraryCategoryModel and use LINQ to create the collection of LibraryCategorys that you actually want to map from in the mapping definition from LibraryItem to LibraryItemModel:

Mapper.CreateMap<LibraryItem, LibraryItemModel>()
    .ForMember(
        dest => dest.Categories, 
        opt => opt.MapFrom(
            src => src.LibraryItemCategories.Select(lb => lb.LibraryCategory)));

You'd obviously still need the mapping from LibraryCategory to LibraryCategoryViewModel, but you might prefer this since it involves fewer mappings.

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
  • Error is: Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type ============================================================================================================== LibraryItemCategory -> LibraryCategoryModel (Destination member list) EF.Entities.LibraryItemCategory -> Shared.Models.Library.LibraryCategoryModel (Destination member list) -------------------------------------------------------------------------------------------------------------- Description Id UniqueId – DRobertE Jul 17 '14 at 18:24
  • @denas: You should be okay ignoring all members since you're using `.ConstructUsing` and mapping the inner property to the outer model instead of mapping directly from class to class. I'll update my answer. – Andrew Whitaker Jul 17 '14 at 18:28
  • HOWEVER, I just tried your Alternatively suggestion and that seemed to work great! BUt I'm not sure how the alternative worked and the original suggestion didn't cause the alternative caught every field fine, even description, id, and uniqueid – DRobertE Jul 17 '14 at 18:36
  • Because in the alternative answer there's a mapping *directly* from `LibraryCategory` to `LibraryCategoryModel` without the intermediate step of mapping from `LibraryItemCategory` to `LibraryCategoryModel`. AutoMapper "knows" we've mapped all of the properties since it's doing the mapping automatically. Using `ConstructUsing`, AutoMapper didn't know that that call actually maps all of the properties and we can just ignore the property mappings that don't match. Did adding `.ForAllMembers(opt => opt.Ignore())` work with the first suggestion? – Andrew Whitaker Jul 17 '14 at 18:38
  • hmmmm ok that makes some sense, but why do you have to map in your first line the collections dest => dest.Categories, opt => opt.MapFrom(src => src.LibraryItemCategories) when dest.Categories and src.LibraryItemCategories are not the same return type, that threw me – DRobertE Jul 17 '14 at 18:40
  • The alternative reads very easy as I'm familiar with the lambda notation.. it reads like "Select each LibraryCategory from LibraryItemCategories and put them into dest.Categories" – DRobertE Jul 17 '14 at 18:42
  • Your edited answer including the ForAllMembers Ignore yields the same result as the alternative. This code handles going from Entities to Models, but will automapper know how to go the other direction with this code??? From the Model to the Entitiy – DRobertE Jul 17 '14 at 18:45
  • No, you have to configure mappings both ways. – Andrew Whitaker Jul 17 '14 at 18:50
  • Bummer... Thanks though!! I don't need the other direction yet as I'm not doing saves but I'm assuming it would be the reverse of what is here – DRobertE Jul 17 '14 at 18:53
  • Thank you, ConstructUsing was just what I needed ! – Michel Amorosa Jan 18 '17 at 15:14
2

Try the something like

Mapper
    .CreateMap<LibraryItemModel, EF.Entities.LibraryItem>()
    .ForMember(
        dest => dest.LibraryItemCategories,
        opt => opt.MapFrom(src => src.LibraryItemCategories )
    );

so you declare where your nested property will be mapped.

You can find another example on the documentation site

pollirrata
  • 5,188
  • 2
  • 32
  • 50