Automapping a wrapped class - automapper

We want to generically map from one database's context.tableType to another
e.g. UatDb.Branch--LiveDb.Branch
The table's are identical so no MapFrom is necessary.
The following generic mapping definition is sufficient
Mapper.CreateMap<TFromContextTableType,TToContextTableType>();
However!!!
We need to wrap the source context.tableType in the following wrapper class:
public class SWrapper<TFrom> where TFrom : class
{
public SWrapper(TFrom model)
{
Model = model;
}
public TFrom Model { get; private set; }
}
Now to perform the mapping we have to map as follows:
Mapper.CreateMap<SWrapper<FromBranchType>, ToBranchType>().ConstructUsing(x => new Live.Branch()))
.ForMember(d => d.BranchID, o => o.MapFrom(x => x.Model.BranchID))
.ForMember(d => d.BranchName, o => o.MapFrom(x => x.Model.BranchName))
.ForMember(d => d.BranchCountry, o => o.MapFrom(x => x.Model.BranchCountry))
This means that we cannot generically map and have to explicitly declare a ForMember for each mapping. I can't figure out any solution using Resolvers or Type Converters.
I thought about perhaps wrapping the target in a SWrapper then resolving the returned Wrapped object to return the internal Product but not sure how to perform this.
All ideas welcome....

Taking from your mapping you could do the following which does not require you to enter all of the property mappings by hand. The helper method in the second test allows for specialized mapping that you may not need.
[TestMethod]
public void Given_a_wrapper_class_I_should_be_able_to_unwrap_it_and_continue_with_mappings()
{
Mapper.CreateMap<Wrapper<Source>, Destination>()
.UnwrapUsing<Wrapper<Source>, Source, Destination>(w => w.Model)
.ForMember(d => d.Bar, o => o.MapFrom(d => d.Baz));
var source = new Source { Baz = 1, Foo = "One" };
var wrapped = new Wrapper<Source> { Model = source };
var destination = Mapper.Map<Destination>(wrapped);
Assert.IsNotNull(destination);
Assert.AreEqual(1, destination.Bar);
Assert.AreEqual("One", destination.Foo);
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDest> UnwrapUsing<TWrapper, TSource, TDest>(this IMappingExpression<TWrapper, TDest> expr, Func<Wrapper<TSource>, TSource> unwrapper)
{
Mapper.CreateMap<Wrapper<TSource>, TDest>()
.ConstructUsing(x => Mapper.Map<TSource, TDest>(unwrapper(x)));
return Mapper.CreateMap<TSource, TDest>();
}
}
public class Source
{
public string Foo { get; set; }
public int Baz { get; set; }
}
public class Destination
{
public string Foo { get; set; }
public int Bar { get; set; }
}
public class Wrapper<T>
{
public T Model { get; set; }
}

How about adding a little utility method to your wrapper class:
public TDest MapTo<TDest>()
{
return Mapper.Map<TFrom, TDest>(this.Model);
}

Related

AutoMapper -- One-to-many mapping of tree

I have the following classes:
public class OneToManySource {
public OneToManySource SourceChild { get;set; } // will sometimes be null
public int Value { get; set; }
}
public interface IDestination
{
}
public class ChildlessDestination: IDestination
{
public int DestValue { get; set; }
}
public class ChildedDestination: IDestination
{
public int DestValue { get; set; }
public IDestination DestinationChild { get; set; } // Never null. If it would be null, use a ChildlessDestination instead.
}
I want to map these back and forth in the sensible way. If a source has a child, it goes to a ChildedDestination. Otherwise, it goes to a ChildlessDestination.
I have the following, which works, but it's ugly. I'm wondering if it can be cleaned up. In particular, the "ConstructUsing" gets the job done, but it also seems to be (understandably) opaque to AutoMapper's internal smarts. So ReverseMap() there doesn't work. Instead of that, we are stuck with two other ReverseMap() calls, and also the last CreateMap().
For example, would I be better off starting with the reverse map?
public MapperConfigurationExpression Configure(MapperConfigurationExpression expression) {
expression.CreateMap<OneToManySource, ChildedDestination>()
.ForMember(d => d.DestValue, cfg => cfg.MapFrom(s => s.Value))
.ForMember(d => d.DestinationChild, opt => opt.MapFrom(s => s.SourceChild))
.ReverseMap();
expression.CreateMap<OneToManySource, ChildlessDestination>()
.ForMember(d => d.DestValue, cfg => cfg.MapFrom(s => s.Value))
.ReverseMap();
expression.CreateMap<OneToManySource, IDestination>()
.ConstructUsing((source, context) =>
{
if (source.SourceChild == null) {
return context.Mapper.Map<ChildlessDestination>(source);
}
return context.Mapper.Map<ChildedDestination>(source);
});
expression.CreateMap<IDestination, OneToManySource>()
.ConstructUsing((source, context) =>
{
return context.Mapper.Map<OneToManySource>(source);
});
return expression;
}

Problems mapping a type that inherits from IEnumerable

I have a problem mapping a property containing a custom list that inherits from IEnumerable (if i remove that inheritance, this example works). I have simplified the problem into this model:
public interface IMyEnumerable<T> : IEnumerable<T> { }
public class MyIEnumerable<T> : IMyEnumerable<T>
{
private readonly IEnumerable<T> _items;
public MyIEnumerable(IEnumerable<T> items)
{
_items = items;
}
public IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class Source
{
public List<SourceItem> Items { get; set; }
}
public class Destination
{
public IMyEnumerable<DestinationItem> Items { get; set; }
}
public class SourceItem
{
public string Name { get; set; }
}
public class DestinationItem
{
public string Name { get; set; }
}
Then i try to use is this way:
public class MyResolver : ValueResolver<Source, IMyEnumerable<DestinationItem>>
{
protected override IMyEnumerable<DestinationItem> ResolveCore(Source source)
{
var destinationItems = Mapper.Map<List<SourceItem>, IEnumerable<DestinationItem>>(source.Items);
return new MyIEnumerable<DestinationItem>(destinationItems);
}
}
// Mappings
Mapper.CreateMap<Source, Destination>()
.ForMember(x => x.Items, m => m.ResolveUsing<MyResolver>());
Mapper.CreateMap<SourceItem, DestinationItem>();
// Using the mappings
var source = // not really relevant
var destination = Mapper.Map<Destination>(source);
This gives me the following exception (slightly edited for readability):
Mapping types:
MyIEnumerable`1 -> IMyEnumerable`1
MyIEnumerable`1[[DestinationItem]] -> IMyEnumerable`1[[DestinationItem]]
Destination path:
Destination.Items.Items
Source value:
MyIEnumerable`1[DestinationItem]
----> System.ArgumentException : Object of type System.Collections.Generic.List`1[DestinationItem] cannot be converted to type IMyEnumerable`1[DestinationItem].
Any idea how i can fix the mapping so that i can get this to work?
Assuming the following:
var source = new Source
{
Items = new List<SourceItem>
{
new SourceItem { Name = "foo" },
new SourceItem { Name = "bar" },
new SourceItem { Name = "cow" },
}
};
Then the following work:
// Method 1: Straight up mapping the collections:
Mapper.CreateMap<List<SourceItem>, IMyEnumerable<DestinationItem>>()
.ConstructUsing(list => new MyEnumerable<DestinationItem>(list.ConvertAll(Mapper.Map<SourceItem, DestinationItem>)));
// Method 2: Ignore the property and do it ourselves after the rest of the mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(q => q.Items, r => r.Ignore())
.AfterMap((s, d) => d.Items = new MyEnumerable<DestinationItem>(
s.Items.Select(Mapper.Map<SourceItem, DestinationItem>)));
Nothing else seems to work due to some combination of covariance and contravariance between List<T>, IEnumerable<T> and IMyEnumerable<T>

What expression is needed for mapping this property?

I am trying to map two objects that are mostly similar with AutoMapper but one member (AudioSummary) raises the following exception :
The following property on EchoNestModel.AudioSummary cannot be mapped: AudioSummary
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type EchoNestModel.AudioSummary.
Context:
- Mapping to property AudioSummary from EchoNest.Api.AudioSummary to EchoNestModel.AudioSummary
- Mapping from type EchoNest.Api.TrackProfile to EchoNestModel.Profile
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
Mapping definition
var map = Mapper.CreateMap<TrackProfile, Profile>();
map.ForMember(dest => dest.ForeignIds, opt => opt.ResolveUsing<ForeignIdResolver>());
map.ForMember(dest => dest.ForeignReleaseIds, opt => opt.ResolveUsing<ForeignReleaseIdResolver>());
map.ForMember(s => s.Media, t => t.Ignore());
map.ForMember(s => s.ProfileId, t => t.Ignore());
map.ForMember(s => s.AudioSummary, t => t.MapFrom(s => s.AudioSummary));
I've added the following two lines but a totally different error occurs :
map.ForMember(s => s.AudioSummary.Profile, t => t.Ignore());
map.ForMember(s => s.AudioSummary.AudioSummaryId, t => t.Ignore());
Expression 's => s.AudioSummary.Profile' must resolve to top-level member and not any child object's properties.
Use a custom resolver on the child type or the AfterMap option instead.
Parameter name: lambdaExpression
How can I successfully map AudioSummary ?
Source object
Target object
EDIT: In general, try AutoMapper.Mapper.AssertConfigurationIsValid();, this will show you all possible problems in your mapper setup.
From the information you provided, it looks like you need to define map for the AudioSummary classes (dest and source) as well:
[TestFixture]
public class MappingTest
{
public class SourceAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class TrackProfile
{
public string Whatever { get; set; }
public SourceAudioSummary AudioSummary { get; set; }
}
public class DestAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class Profile
{
public string Whatever { get; set; }
public DestAudioSummary AudioSummary { get; set; }
}
[Test]
public void Mapping()
{
Mapper.CreateMap<SourceAudioSummary, DestAudioSummary>();
Mapper.CreateMap<TrackProfile, Profile>();
var trackProfile = new TrackProfile
{
Whatever = "something",
AudioSummary = new SourceAudioSummary
{
Id = 1,
OtherData = "other"
}
};
var profile = Mapper.Map<TrackProfile, Profile>(trackProfile);
Assert.That(profile.Whatever == "something");
Assert.That(profile.AudioSummary.Id == 1);
Assert.That(profile.AudioSummary.OtherData == "other");
}
}

Can AutoMapper implicitly flatten this mapping?

I am trying to map between two lists of objects. The source type has a complex property of type A; the destination type is a flattened subset of type A plus an additional scalar property that is in the source type.
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
}
If it's not clear, I'd like Source.MyA.Name to map to Destination.Name and Source.SomeOtherValue to map to Destination.SomeOtherValue.
In reality, type A has a dozen or so properties, about which 80% map over to properties of the same name in Destination. I can get things to work if I explicitly spell out the mappings in CreateMap like so:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.MyA.Name));
The downside here is I want to avoid having to add a ForMember line for each of A's properties that need to get copied over to Destination. I was hoping I could do something like:
CreateMap<Source, Destination>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.MyA));
But if I try the above I get a runtime error when the mapping is registered: "Custom configuration for members is only supported for top-level individual members on a type."
Thanks
create mappings between A and Destination, and Source and Destination, and then use AfterMap() to use first mapping in second
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.AfterMap((s, d) => Mapper.Map<A, Destination>(s.MyA, d));
then use it like this:
var res = Mapper.Map<Source, Destination>(new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } });
As a workaround you can use custom type converter with additional property in the destination type to avoid recursion.
[TestFixture]
public class MapComplexType
{
[Test]
public void Map()
{
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>().ConvertUsing(new TypeConvertor());
var source = new Source
{
MyA = new A
{
Name = "Name"
},
SomeOtherValue = 5
};
var dest = new Destination();
Mapper.Map(source, dest);
Assert.AreEqual(dest.Name, "Name");
}
}
public class TypeConvertor : ITypeConverter<Source, Destination>
{
public Destination Convert(ResolutionContext context)
{
var destination = (Destination) context.DestinationValue;
if (!((Destination)context.DestinationValue).IsMapped || destination == null)
{
destination = destination ?? new Destination();
destination.IsMapped = true; // To avoid recursion
Mapper.Map((Source)context.SourceValue, destination);
destination.IsMapped = false; // If you want to map the same object few times
}
Mapper.Map(((Source)context.SourceValue).MyA, destination);
return (Destination)context.DestinationValue;
}
}
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
// Used only for mapping purposes
internal bool IsMapped { get; set; }
}
Try this,
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.ForMember(destination => destination.Name, options => options.MapFrom(source => Mapper.Map<A, Destination>(source.MyA).Name));
var objSource = new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } };
var result = Mapper.Map<Source, Destination>(objSource);

Automapper: How to leverage a custom INamingConvention?

I am working with a database where the designers really seemed to enjoy capital letters and the underscore key. Since I have a simple ORM, my data models use these names as well. I need to build DTOs and I would prefer to give them standard names since we are exposing them through services.
The code below is now corrected! The test passes so use this as a reference if you need to use multiple naming conventions
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using AutoMapper;
using NUnit.Framework;
namespace AutomapperTest
{
public class DATAMODEL
{
public Guid ID { get; set; }
public string FIRST_NAME { get; set; }
public List<CHILD_DATAMODEL> CHILDREN { get; set; }
}
public class CHILD_DATAMODEL
{
public Guid ID { get; set; }
public int ORDER_ID { get; set; }
}
public class DataModelDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public List<ChildDataModelDto> Children { get; set; }
}
public class ChildDataModelDto
{
public Guid Id { get; set; }
public int OrderId { get; set; }
}
public class UpperUnderscoreNamingConvention : INamingConvention
{
private readonly Regex _splittingExpression = new Regex(#"[\p{Lu}0-9]+(?=_?)");
public Regex SplittingExpression { get { return _splittingExpression; } }
public string SeparatorCharacter { get { return "_"; } }
}
public class Profile1 : Profile
{
protected override void Configure()
{
SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
CreateMap<DATAMODEL, DataModelDto>();
CreateMap<CHILD_DATAMODEL, ChildDataModelDto>();
}
}
[TestFixture]
public class Tests
{
[Test]
public void CanMap()
{
//tell automapper to use my convention
Mapper.Initialize(x => x.AddProfile<Profile1>());
//make a dummy source object
var src = new DATAMODEL();
src.ID = Guid.NewGuid();
src.FIRST_NAME = "foobar";
src.CHILDREN = new List<CHILD_DATAMODEL>
{
new CHILD_DATAMODEL()
{
ID = Guid.NewGuid(),
ORDER_ID = 999
}
};
//map to destination
var dest = Mapper.Map<DATAMODEL, DataModelDto>(src);
Assert.AreEqual(src.ID, dest.Id);
Assert.AreEqual(src.FIRST_NAME, dest.FirstName);
Assert.AreEqual(src.CHILDREN.Count, dest.Children.Count);
Assert.AreEqual(src.CHILDREN[0].ID, dest.Children[0].Id);
Assert.AreEqual(src.CHILDREN[0].ORDER_ID, dest.Children[0].OrderId);
}
}
}
Create your mappings in profiles, and define the INamingConvention parameters as appropriate.
I don't like the global/static, so I prefer using Initialize and define all of my mappings together. This also has the added benefit of allowing a call to AssertConfiguration... which means if I've borked my mapping I'll get the exception at launch instead of whenever my code gets around to using the problematic mapping.
Mapper.Initialize(configuration =>
{
configuration.CreateProfile("Profile1", CreateProfile1);
configuration.CreateProfile("Profile2", CreateProfile2);
});
Mapper.AssertConfigurationIsValid();
in the same class with that initialization method:
public void CreateProfile1(IProfileExpression profile)
{
// this.CreateMap (not Mapper.CreateMap) statements that do the "normal" thing here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile1");
}
public void CreateProfile2(IProfileExpression profile)
{
profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
profile.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
// this.CreateMap (not Mapper.CreateMap) statements that need your special conventions here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile2");
}
if you do it this way, and don't define the same mapping in both profiles, I don't think you need anything to "fill in the blank" from the original question, it should already be setup to do the right thing.
What about
public class DATAMODELProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DATAMODEL, DATAMODEL>();
Mapper.CreateMap<DATAMODEL, SOMETHINGELSE>();
Mapper.CreateMap<DATAMODEL, DataModelDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FIRST_NAME))
.ForMember(dest => dest.ChildDataModels, opt => opt.MapFrom(src => src.CHILD_DATAMODELS));
}
}

Resources