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();
}
}
}
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>();
I have a need to use a delimited string in order by. EG "Product.Reference".
I seem to be having trouble as the result is ordered the same way it was before the method was called.
For example I have this xUnit test that shows my issue.
The asserts show that the order is still the same.
EDIT:
to be clear, I am not testing Order by, but the method PathToProperty.
This test is for demonstration purposes only.
As you can see from the test I am using reflection in method private static object PathToProperty(object t, string path) So I am assuming I am doing something wrong in there?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
namespace Pip17PFinanceApi.Tests.Workers
{
public class InputViewModel
{
public List<OrderViewModel> OrderViewModels { get; set; }
}
public class OrderViewModel
{
public Product Product { get; set; }
public decimal Price { get; set; }
}
public class Product
{
public string Description { get; set; }
public string Reference { get; set; }
}
public class OrderByWithReflection
{
[Fact]
public void OrderByTest()
{
//Arrrange
var model = new InputViewModel
{
OrderViewModels = new List<OrderViewModel>
{
new OrderViewModel{
Product = new Product
{
Reference = "02"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "03"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "01"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "04"
}
},
}
};
//Act
var query = model.OrderViewModels.OrderBy(t => PathToProperty(t, "Product.Reference"));
var result = query.ToList();
//Assert
Assert.Equal("01", result[0].Product.Reference);
Assert.Equal("02", result[1].Product.Reference);
Assert.Equal("03", result[2].Product.Reference);
Assert.Equal("04", result[3].Product.Reference);
}
private static object PathToProperty(object t, string path)
{
Type currentType = t.GetType();
foreach (string propertyName in path.Split('.'))
{
PropertyInfo property = currentType.GetProperty(propertyName);
t = property;
currentType = property.PropertyType;
}
return t;
}
}
}
Your PathToProperty isn't correct. Think about it's return value - the last time through, you set t = property and then return t. But property is a PropertyInfo, so you are just comparing identical objects in the OrderBy.
I have a similar extension method I use:
public static object GetPropertyPathValue(this object curObject, string propsPath) {
foreach (var propName in propsPath.Split('.'))
curObject = curObject.GetType().GetProperty(propName).GetValue(curObject);
return curObject;
}
If used in place of your PathToProperty method, the OrderBy will work.
var query = model.OrderViewModels.OrderBy(t => t.GetPropertyPathValue("Product.Reference"));
You could update your method to be something like:
private static object PathToProperty(object curObject, string path) {
foreach (string propertyName in path.Split('.')) {
var property = curObject.GetType().GetProperty(propertyName);
curObject = property.GetValue(curObject);
}
return curObject;
}
for the same result.
PS Actually, using some other extension methods and LINQ, my normal method handles properties or fields:
public static object GetPathValue(this object curObject, string memberPath)
=> memberPath.Split('.').Aggregate(curObject, (curObject, memberName) => curObject.GetType().GetPropertyOrField(memberName).GetValue(curObject));
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 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();
Automapper easily handles mapping one list of object types to another list of different objects types, but is it possible to have it map to an existing list using an ID as a key?
I have not found better way than the following.
Here are source and destination.
public class Source
{
public int Id { get; set; }
public string Foo { get; set; }
}
public class Destination
{
public int Id { get; set; }
public string Foo { get; set; }
}
Define converter (You should change List<> to whatever type you are using).
public class CollectionConverter: ITypeConverter<List<Source>, List<Destination>>
{
public List<Destination> Convert(ResolutionContext context)
{
var destinationCollection = (List<Destination>)context.DestinationValue;
if(destinationCollection == null)
destinationCollection = new List<Destination>();
var sourceCollection = (List<Source>)context.SourceValue;
foreach(var source in sourceCollection)
{
Destination matchedDestination = null;
foreach(var destination in destinationCollection)
{
if(destination.Id == source.Id)
{
Mapper.Map(source, destination);
matchedDestination = destination;
break;
}
}
if(matchedDestination == null)
destinationCollection.Add(Mapper.Map<Destination>(source));
}
return destinationCollection;
}
}
And here is actual mapping configuration and example.
Mapper.CreateMap<Source,Destination>();
Mapper.CreateMap<List<Source>,List<Destination>>().ConvertUsing(new CollectionConverter());
var sourceCollection = new List<Source>
{
new Source{ Id = 1, Foo = "Match"},
new Source{ Id = 2, Foo = "DoesNotMatchWithDestination"}
};
var destinationCollection = new List<Destination>
{
new Destination{ Id = 1, Foo = "Match"},
new Destination{ Id = 3, Foo = "DoeNotMatchWithSource"}
};
var mergedCollection = Mapper.Map(sourceCollection, destinationCollection);
You should get the following result.
I found this article very useful and as such I thought I would feed back in my generic version of the type converter which you can use to select the property to match on from each object.
Using it all you need to do is:
// Example of usage
Mapper.CreateMap<UserModel, User>();
var converter = CollectionConverterWithIdentityMatching<UserModel, User>.Instance(model => model.Id, user => user.Id);
Mapper.CreateMap<List<UserModel>, List<User>>().ConvertUsing(converter);
//The actual converter
public class CollectionConverterWithIdentityMatching<TSource, TDestination> :
ITypeConverter<List<TSource>, List<TDestination>> where TDestination : class
{
private readonly Func<TSource, object> sourcePrimaryKeyExpression;
private readonly Func<TDestination, object> destinationPrimaryKeyExpression;
private CollectionConverterWithIdentityMatching(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
{
this.sourcePrimaryKeyExpression = sourcePrimaryKey.Compile();
this.destinationPrimaryKeyExpression = destinationPrimaryKey.Compile();
}
public static CollectionConverterWithIdentityMatching<TSource, TDestination>
Instance(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
{
return new CollectionConverterWithIdentityMatching<TSource, TDestination>(
sourcePrimaryKey, destinationPrimaryKey);
}
public List<TDestination> Convert(ResolutionContext context)
{
var destinationCollection = (List<TDestination>)context.DestinationValue ?? new List<TDestination>();
var sourceCollection = (List<TSource>)context.SourceValue;
foreach (var source in sourceCollection)
{
TDestination matchedDestination = default(TDestination);
foreach (var destination in destinationCollection)
{
var sourcePrimaryKey = GetPrimaryKey(source, this.sourcePrimaryKeyExpression);
var destinationPrimaryKey = GetPrimaryKey(destination, this.destinationPrimaryKeyExpression);
if (string.Equals(sourcePrimaryKey, destinationPrimaryKey, StringComparison.OrdinalIgnoreCase))
{
Mapper.Map(source, destination);
matchedDestination = destination;
break;
}
}
if (matchedDestination == null)
{
destinationCollection.Add(Mapper.Map<TDestination>(source));
}
}
return destinationCollection;
}
private string GetPrimaryKey<TObject>(object entity, Func<TObject, object> expression)
{
var tempId = expression.Invoke((TObject)entity);
var id = System.Convert.ToString(tempId);
return id;
}
}