I have the following model:
public class Foo
{
// some fields or properties
public Foo Parent { get; set; }
}
What I wanna to see after:
public class Bar
{
public List<FooDTO> Foos { get; set; }
}
public class FooDTO
{
// some fields or properties
}
I want to be able to use AutoMapper to map the Parent property of the Foo type to a generic List<> (or any other IEnumerable type) of FooDTO property.
It is possible with AfterMap and ResolutionMapper.Mapper.Map calls:
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Foo, FooDTO>();
cfg.CreateMap<Foo, Bar>()
.AfterMap((src, dest, ctx) =>
{
dest.Foos = new List<FooDTO>();
var node = src.Parent;
while (node != null)
{
dest.Foos.Add(ctx.Mapper.Map<FooDTO>(node));
node = node.Parent;
};
});
});
var mapper = configuration.CreateMapper();
Related
I have a structure with a single top level node RootSource with child objects Source needing to be mapped to a list of child objects List<Destination>.
public class RootSource
{
public List<Source> Sources { get; set; }
}
public class Source
{
public string SourceData { get; set; }
}
public class Destination
{
public string DestinationData { get; set; }
}
This is what I would like to achive:
[Test]
public void MapperTest()
{
var mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(x => x.DestinationData, opt => opt.MapFrom(x => x.SourceData));
cfg.CreateMap<RootSource, List<Destination>>()
???;
})
.CreateMapper();
var rootSource = new RootSource
{
Sources = new List<Source> {
new Source { SourceData = "A" },
new Source { SourceData = "B" },
}
};
var destinations = mapper.Map<List<Destination>>(rootSource);
Assert.AreEqual(2, destinations.Count);
Assert.AreEqual(rootSource.Sources[0].SourceData, destinations[0].DestinationData);
Assert.AreEqual(rootSource.Sources[1].SourceData, destinations[1].DestinationData);
}
I have found ONE solution, but it feels less than ideal, relaying on use of converter. Is there some less complex way of resolving this with AutoMapper?
public class RootSourceToListDestinationConverter : ITypeConverter<RootSource, List<Destination>>
{
public List<Destination> Convert(RootSource rootSource, List<Destination> destination, ResolutionContext context)
{
var result = new List<Destination>();
if (rootSource != null)
{
foreach (var source in rootSource.Sources)
{
result.Add(context.Mapper.Map<Source, Destination>(source));
}
}
return result;
}
}
with the addition of this mapper entry:
cfg.CreateMap<RootSource, List<Destination>>().ConvertUsing<RootSourceToListDestinationConverter>();
In the current example I find it easy going HeaderPayload => Header since I just use the source.Data.Values directly.
What let's me construct the other way Header => HeaderPayload?
The current config is wrong and fails in the Map call.
[Test]
public void TestAutoMapper2()
{
// arrange
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Header, HeaderPayload>()
.ForMember(dest => dest.Data, map => map.MapFrom(src => src.Values))
;
});
var a = new Header { Values = new List<string> { "foo", "bar" } };
var mapper = new Mapper(config);
// act
var b = mapper.Map<Header>(a);
// assert
Assert.IsNotNull(b.Data);
Assert.IsNotEmpty(b.Data.Values);
Assert.AreEqual("foo", b.Data.Values.FirstOrDefault());
}
public class Data
{
public List<string> Values { get; set; }
}
public class Header
{
public List<string> Values { get; set; }
}
public class HeaderPayload
{
public Data Data { get; set; }
}
I have two entities
public class A{
public string Example { get; set; }
public ICollection<B> BCollection { get;set; } = new HashSet<B>();
}
public class B {
public string MyProperty { get; set; }
}
And a simple ViewModel
public class AFirstLoadViewModel {
public string Example { get; set; }
public string MyProperty { get; set; }
}
The thing, is, this viewmodel will be use only in the first data entry, when A will only have one B object inside.
So, i'm trying to map a object like this:
var source = new AFirstLoadViewModel
{
Example = "example",
MyProperty = "myproperty"
}
to this
var destination = new A {
Example = "example"
BCollection = new List<B> {
new B { MyProperty = "myproperty" }
}
}
I try to do the trick using ForPath and BeforeMap without luck
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForPath(x => x.BCollection.First().MyProperty, c => c.MapFrom(x => x.MyProperty))
.BeforeMap((viewModel, entity) => {
if(!entity.BCollection.Any())
BCollection.Add(new B());
});
But i get
System.ArgumentOutOfRangeException: Only member accesses are allowed.
How can i deal with it?
I clarify: both, view model and model have many more properties, the question classes are by way of example
Edit:
I try the solution proposed by Johnatan, and it works, the problem here, is that i cant Unit Testing anymore.
I'm testing with
var config = new MapperConfiguration(cfg => cfg.CreateMap<AFirstLoadViewModel, A>(MemberList.Source));
And when i call config.AssertConfigurationIsValid() fails because the MyProperty property is not mapped
The problem is you are trying to map to .First(). First does not yet exist because the query is on a null / empty collection. You can't assign to the .First() element in a collection if one does not exist already. Instead just map as a collection directly.
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));
I have a complex domain object structure:
public class CustomerDomainObj
{
public CUSTOMER customer {get;set;}
public ORDER order {get;set;}
public PRODUCT product {get;set;}
}
DTO:
public class CustomerDTO
{
public string cust_name {get;set;}
public string price {get;set;}
public string description {get;set;}
}
I need to map properties of CUSTOMER,PRODUCT and ORDER objects to CustomerDTO
For that I have created an extension method:
public static TDestination Map<TSource, TDestination>(this TDestination destination, TSource source, IMapper mapper)
{
return mapper.Map(source, destination);
}
Configuration:
m.CreateMap<CUSTOMER, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<PRODUCT, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<ORDER, CustomerDTO>().ForMember(/*Some Code*/);
Usage:
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj, _mapper);
.Map(repoCust.orderObj, _mapper);
Everything works fine!
Question:
I am looking for a way to skip passing the _mapper instance every time when I try to do some mapping.
Something like :
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj);
.Map(repoCust.orderObj);
maybe you are looking for an approach like this...
using AutoMapper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace sandcastle1
{
class Program
{
static void Main(string[] args)
{
//we create a mapper instance
var _mapper = new MapperConfiguration(m =>
{
m.CreateMap<A, ADto>().ForMember(x => x.Y, opt => opt.MapFrom(x => x.X));
m.CreateMap<B, BDto>().ForMember(x => x.bar, opt => opt.MapFrom(x => x.foo));
}).CreateMapper();
//some dummy POCOs that we can map
var a1 = new A { X = 1 };
var a2 = new A { X = 2 };
var a3 = new A { X = 3 };
var b1 = new B { foo = 1 };
var b2 = new B { foo = 2 };
var b3 = new B { foo = 3 };
//mapping objects with fluent syntax:
// -- we do not repeat the mapper as a written parameter
// -- we do not specify the destination type but assume that there is exactly one TypeMap for the source type
var result = _mapper
.Map(a1)
.Map(a2)
.Map(a3)
.Map(b1)
.Map(b2)
.Map(b3);
//presenting the mapping result
var cnt = 1;
foreach (var dto in result)
{
var propinfo = dto.GetType().GetProperties().First();
Console.WriteLine($"Mapped object {cnt++} is of Type {dto.GetType().Name} with {propinfo.Name} = {propinfo.GetValue(dto)}");
}
}
}
//our dummy classes for the mapping
public class A
{
public int X { get; set; }
}
public class ADto
{
public int Y { get; set; }
}
public class B
{
public int foo { get; set; }
}
public class BDto
{
public int bar { get; set; }
}
public static class ExtensionMethodClass
{
//the first extension method ...
//takes the IMapper and maps the first object
//hands down all possible type mappings or throws an exception when we do not have exactly one mapping for the source type
public static MyFluentResult Map(this IMapper _mapper, object source)
{
//build a dictionary that gives us access to information about existing TypeMaps in the Mapper
//(we only want to know from wicht source type to which destination type)
var maps = _mapper.ConfigurationProvider.GetAllTypeMaps().GroupBy(x => x.SourceType).Where(x => x.Count() == 1).ToDictionary(x => x.Key, x => x.First().DestinationType);
Type stype = source.GetType();
Type dtype;
if (!maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//the magic happens here, we aggregate all necessary information for the following steps in this object
return new MyFluentResult
{
Mapper = _mapper,
Maps = maps,
ResultsSoFar = new object[] { _mapper.Map(source, stype, dtype) }
};
}
//this method is called for the second and all subsequent mapping operations
public static MyFluentResult Map(this MyFluentResult _fluentResult, object source)
{
//same as above, but we already have the dictionary...
Type stype = source.GetType();
Type dtype;
if (!_fluentResult.Maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//again we hand down the result and all other information for the next fluent call
return new MyFluentResult
{
Mapper = _fluentResult.Mapper,
Maps = _fluentResult.Maps,
//we can simply concat the results here
ResultsSoFar = _fluentResult.ResultsSoFar.Concat(new object[] { _fluentResult.Mapper.Map(source, stype, dtype) })
};
}
//omitted implementation for IEnumerable sources... but that would look pretty much the same
}
//the class that holds the aggregated data.... the mapper... the possible typemaps ... and the result data...
//wrapped IEnumerable of the results for convinience
public class MyFluentResult : IEnumerable<object>
{
public IMapper Mapper { get; set; }
public IEnumerable<object> ResultsSoFar { get; set; }
public Dictionary<Type, Type> Maps { get; set; }
public IEnumerator<object> GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
}
}
I'm using AutoMapper to copy an entity framework object to another identical database. The problem is that it tries to copy the lookup tables.
I have tried to exclude them with the AddGlobalIgnore and the ShouldMapProperty but it doesn't work. AutoMapper still try to copy those properties.
Here's my code. I would like to ignore the properties that start with "LU"
dynamic newObject= new NewObject();
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.AddGlobalIgnore("LU");
cfg.ShouldMapProperty = p => !p.GetType().ToString().StartsWith("LU");
cfg.ShouldMapField = p => !p.GetType().ToString().StartsWith("LU");
});
IMapper mapper = config.CreateMapper();
newObject = mapper.Map(objectToCopy, objectToCopy.GetType(), newObject.GetType());
I did also tried
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.AddGlobalIgnore("LU");
cfg.ShouldMapProperty = p => !p.PropertyType.Name.StartsWith("LU");
cfg.ShouldMapField = p => !p.FieldType.Name.StartsWith("LU");
});
and
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.AddGlobalIgnore("LU");
cfg.ShouldMapProperty = p => !p.Name.StartsWith("LU");
cfg.ShouldMapField = p => !p.Name.StartsWith("LU");
});
Create your configuration as a separate profile, then add that profile to the mapper configuration.
class Program
{
static void Main(string[] args)
{
dynamic newObject = new NewObject();
var objectToCopy = new ObjectToCopy();
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyProfile>();
});
var mapper = config.CreateMapper();
mapper.Map(objectToCopy, newObject);
// newObject.LU_Ignore = "Original value"
// newObject.DoNotIgnore = "New value"
}
}
class MyProfile : Profile
{
protected override void Configure()
{
CreateMissingTypeMaps = true;
ShouldMapProperty = p => !p.Name.StartsWith("LU"); // this is the correct way to get the property name
}
}
class ObjectToCopy
{
public string LU_Ignore { get; set; } = "New value";
public string DoNotIgnore { get; set; } = "New value";
}
class NewObject
{
public string LU_Ignore { get; set; } = "Original value";
public string DoNotIgnore { get; set; } = "Original value";
}
Something seems goofy about how configurations are applied to the Mapper created form the mapper.CreateMapper call. I'm looking into it to see if I can find out more information and will update this answer if I find anything.
I wont say that this is the best (performant or design-wise) approach, but this works:
public static class AutoExtensions {
public static IMappingExpression Ignore(this IMappingExpression expression, Func<PropertyInfo, bool> filter) {
foreach (var propertyName in expression
.TypeMap
.SourceType
.GetProperties()
.Where(filter)
.Select(x=>x.Name))
{
expression.ForMember(propertyName, behaviour => behaviour.Ignore());
}
return expression;
}
}
You can configure your mapper like this (for these sample classes):
public class Client {
public string LUName { get; set; }
public string Dno { get; set; }
}
public class ClientDTO
{
public string LUName { get; set; }
public string Dno { get; set; }
}
and test it out like this:
private static void ConfigAndTestMapper() {
var config = new MapperConfiguration(cfg =>{
cfg.CreateMap(typeof (Client), typeof (ClientDTO))
.Ignore(x => x.Name.StartsWith("LU"));
});
var mapper = config.CreateMapper();
var result = mapper.Map<ClientDTO>(new Client() {LUName = "Name", Dno = "Dno"});
var isIgnored = result.LUName == null;
}
PS: this is also pretty "hackish" since it tries to map all kind of properties there (readonly/non-public, etc.) so take it with a grain of salt.