Automapper - Unidirectional communication of objects to list - c#

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

Mapping of single container object to list with Automapper

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>();

How do you go from List<> to class in Automapper c#

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; }
}

Map property to property of a collection automapper

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 } }));

how to use automapper instance api in extension method

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();
}
}
}

How to ignore properties based on their type

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.

Categories

Resources