Automapper Map multiple optional properties to list - c#

I have a class OrderLineRequest that I want to map to an OrderLine class with a list of barcodes. The properties Barcode1,2,3 needs to be mapped to Barcodes only if the contain a value. Barcode1 is always filled, Barcode2 and Barcode3 are optional. I have created a mapping but this gives me always 3 barcodes in the list. If Barcode1 or 2 is an empty string i don't want to add them to the list. How can i do this?
public class OrderLineRequest
{
public string OrderLineId { get; set; }
public string Barcode1 { get; set; }
public string Barcode2 { get; set; }
public string Barcode3 { get; set; }
public int Quantity { get; set; }
}
public class OrderLine
{
public int Id { get;set;}
public int OrderId { get;set;}
public string OrderLineNumber { get; set; }
public int Qty { get; set; }
public List<Barcode> Barcodes { get;set;}
}
public class Barcode
{
public int Id { get;set;}
public int OrderLineId { get;set;}
public string Code { get;set;}
}
CreateMap<OrderLineRequest, OrderLine>()
.ForMember(b => b.Id, e => e.Ignore())
.ForMember(d => d.OrderId, p => p.Ignore())
.ForMember(d => d.OrderLineNumber, p => p.MapFrom(s => s.OrderLineId))
.ForMember(d => d.Qty, p => p.MapFrom(s => s.Quantity))
.ForMember(d => d.BarCodes, p => p.MapFrom(s => new List<EanCode>() { new EanCode(){Code = s.Barcode1}, new EanCode() { Code = s.Barcode2 }, new EanCode() { Code = s.Barcode3 } }));

Why are you always creating those three barcodes?
I would suggest you to create a function for the predicate
that accepts OrderLineRequest and returns your List and handle the creation within the function. Like that:
private List<EanCode> Foo(OrderLineRequest orderLineRequest)
{
var result = new List<EanCode>();
if(!string.IsNullOrEmpty(orderLineRequest.Barcode1)
result.Add(new EanCode {Code = orderLineRequest.Barcode1});
//...
return result;
}
And then you could use it like:
.ForMember(d => d.BarCodes, p => p.MapFrom(s => Foo(s)));

If you're using Automapper, the step of adding three specific properties from the source to a list in the destination can't be accomplished with a simple function. You have to tell Automapper how to accomplish it.
You can do that by telling it to ignore those properties during the initial mapping, and then add items to the destination list after that mapping is complete.
For brevity this includes only those properties:
var configuration = new MapperConfiguration(
cfg => cfg.CreateMap<OrderLineRequest, OrderLine>()
.ForMember(d => d.Barcodes, opt => opt.Ignore())
.ForSourceMember(s => s.Barcode1, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode2, opt => opt.DoNotValidate())
.ForSourceMember(s => s.Barcode3, opt => opt.DoNotValidate())
.AfterMap((source, destination) =>
{
destination.Barcodes = new List<Barcode>
{
new Barcode { Code = source.Barcode1 }
};
if (source.Barcode2 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode2 });
if (source.Barcode3 != null)
destination.Barcodes.Add(new Barcode { Code = source.Barcode3 });
}));
It could be said that this makes a case for just writing your own extension instead of using Automapper. It's convenient when the mapping is simple, but if it's not then using it could arguably be more trouble than it's worth. That's a matter of preference.

Create an array of the three properties. Once it's in an array, you can use Where to remove the nulls and Select to instantiate the EanCode instances. Once the data are in good shape, call ToList().
.ForMember
(
d => d.BarCodes,
p => p.MapFrom
(
s =>
(new [] { s.BarCode1, s.BarCode2, s.BarCode3 })
.Where( x => x != null)
.Select( x => new EanCode { Code = x } )
.ToList()
)
);

Related

Automapper. How to map custom type property of inner class (source) to string and string array?

I want to map source inner classes to string and in other case string array but in both cases they are mapped to null. I want MapNoteFromInnerSourceEntity1 to hold a value of InnerSourceEntity1 Id property and MapValueFromInnerSourceEntity2 to hold values of InnerSourceEntity2 value properties. So far automapper is quite difficult for me to understand.
Code:
internal class Program
{
public class InnerSourceEntity1
{
public string Id { get; set; }
public string Note { get; set; }
}
public class InnerSourceEntity2
{
public string Id { get; set; }
public string Value { get; set; }
}
public class SourceEntity
{
public InnerSourceEntity1 A { get; set; }
public IList<InnerSourceEntity2> B { get; set; }
}
public class DestinationEntity
{
public string MapNoteFromInnerSourceEntity1 { get; set; }
public string[] MapValueFromInnerSourceEntity2 { get; set; }
}
static void Main()
{
var source = new SourceEntity
{
A = new InnerSourceEntity1 { Note = "Note", Id = "Id"},
B = new List<InnerSourceEntity2> { new InnerSourceEntity2 { Id = "Id", Value = "Value" }, new InnerSourceEntity2 { Id = "Id", Value = "Value" } }
};
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceEntity, DestinationEntity>();
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>();
});
var mapper = new Mapper(config);
DestinationEntity destination = mapper.Map<SourceEntity, DestinationEntity>(source);
Console.ReadLine();
}
}
Since the names of properties between source type SourceEntity and destination type DestinationEntity don't match, you'll have to explicitly indicate them, otherwise AutoMapper will not know how to fill the properties:
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
Also, don't map between concrete collection types:
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>(); // <== Don't do that.
Instead, see what the docs say about mapping collections:
(...) it’s not necessary to explicitly configure list types, only their member types. ~ AutoMapper Docs
So, you only need to specify map like this:
cfg.CreateMap<InnerSourceEntity2, string>();
And since we are mapping to string we also need to instruct the AutoMapper from where it can get the string value. So, we'll use ConvertUsing() again:
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
Final configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
});

AutoMapper advance mapping

I want to like merge those Source objects into a List<Destination>. Notice that SourceParent and Destination Id property MUST be the same.
var parent = new SourceParent
{
Id = 1,
Childs = new List<SourceChild>
{
new SourceChild { ChildId = 12, OtherProperty = "prop1" },
new SourceChild { ChildId = 13, OtherProperty = "prop2" },
new SourceChild { ChildId = 14, OtherProperty = "prop3" },
}
};
Mapper.Initialize(cfb =>
{
cfb.CreateMap<SourceParent, List<Destination>>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.Childs));
cfb.ValidateInlineMaps = false;
});
List<Destination> destination = Mapper.Map<SourceParent, List<Destination>>(parent);
Classes:
public class SourceParent
{
public int Id { get; set; }
public List<SourceChild> Childs { get; set; }
}
public class SourceChild
{
public string OtherProperty { get; set; }
public int ChildId { get; set; }
}
public class Destination
{
public int SourceParentId { get; set; }
public string OtherProperty { get; set; }
public int ChildId { get; set; }
}
Is there a way to create a mapping rule for this case? Is it even possible?
I think your best option here is to define a TypeConverter.
You can do TypeConverters inline like I've done below or you can define a class that implements the ITypeConverter<TIn, TOut> interface.
cfb.CreateMap<SourceParent, List<Destination>>().ConvertUsing((src, dest, context) =>
{
return src.Childs.Select(x =>
{
var destination = context.mapper.Map<Destination>(x);
destination.SourceParentId = src.Id;
return destination;
}
});
If you wanted to (I usually stay away from this because it can get unruly fast) you could define another custom mapping using a tuple or a wrapper class like this.
cfb.CreateMap<SourceParent, List<Destination>>().ConvertUsing((src, dest, context) =>
{
return src.Childs.Select(x => context.mapper.Map<Destination>((src.Id, x)))
});
cfb.CreateMap<(int partentId, SourceChild child), Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.parentId))
.ForMember(dest => dest.ChildId, opt => opt.MapFrom(src => src.child.Id))
.ForMember(dest => dest.OtherProperty , opt => opt.MapFrom(src => src.child.OtherProperty ));
This can be nice for small examples but if you are doing it often it can lead to really cluttered mapper configurations (in my opinion), but it does simplify your type converter.

Flatten nested object

I am using AutoMapper 6.2.2, I have two source models that share an Id property:
using System.Diagnostics;
using AutoMapper;
public class Outer
{
public int Id { get; set; }
public string Foo { get; set; }
public Inner Bar { get; set; }
}
public class Inner
{
public int Id { get; set; }
public string Baz { get; set; }
public string Qux { get; set; }
public string Bof { get; set; }
}
public class FlatDto
{
public int Id { get; set; }
public string Foo { get; set; }
public string Baz { get; set; }
public string Qux { get; set; }
public string Bof { get; set; }
}
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
this.CreateMap<Outer, FlatDto>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(s => s.Id))
.ForMember(dst => dst.Foo, opt => opt.MapFrom(s => s.Foo))
.ForMember(dst => dst.Baz, opt => opt.MapFrom(s => s.Bar.Baz))
.ForMember(dst => dst.Qux, opt => opt.MapFrom(s => s.Bar.Qux))
.ForMember(dst => dst.Bof, opt => opt.MapFrom(s => s.Bar.Bof));
}
}
class Program
{
static void Main(string[] args)
{
Outer model = new Outer
{
Id = 1,
Foo = "FooString",
Bar = new Inner
{
Id = 2,
Baz = "BazString",
Qux = "QuxString",
Bof = "BofString"
}
};
var config = new MapperConfiguration(cfg => cfg.AddProfiles(typeof(Program).Assembly));
config.AssertConfigurationIsValid();
IMapper mapper = new Mapper(config);
FlatDto dto = mapper.Map<Outer, FlatDto>(model);
Trace.Assert(model.Id == dto.Id);
Trace.Assert(model.Foo == dto.Foo);
Trace.Assert(model.Bar.Baz == dto.Baz);
Trace.Assert(model.Bar.Qux == dto.Qux);
Trace.Assert(model.Bar.Bof == dto.Bof);
}
}
I want FlatDto.Id to come from Outer and the other parameters all by name. AutoMapper's convention in this case is pretty clear however I cannot modify these properties. It's currently mapped explicitly with ForMember for every dest property. The solution for a similar question actually is even longer.
Does a more elegant solution exist for this case where both models contain several fields and only one overlaps and requires explicit handling?
The simplest solution (without changing code even you modified Outer/Inner in the future) is:
Mapper.Initialize(c =>
{
c.CreateMap<Inner, FlatDto>();
c.CreateMap<Outer, FlatDto>().BeforeMap((s, t) => Mapper.Map(s.Bar, t));
});
Be aware that:
You need to change if you are using instance mappers instead of static .Map method of "global" mapper.
Properties with same name in both Inner and Outer will be mapped twice, and the Outer has a higher priority, be careful with possible side effects.
EDIT Since you are using instance mappers and profiles, the instance IMapper can't be accessed inside a profile, we need to register the mappings dynamic. The following code, just like the code snippet in the question, essentially uses .ForMember, with the arguments built in dynamic expressions.
class TestProfile : Profile
{
public TestProfile()
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
Func<PropertyInfo, bool> filter = p => p.CanRead && p.CanWrite;
var outerProperties = typeof(Outer).GetProperties(flags).Where(filter).ToDictionary(p => p.Name);
var innerProperties = typeof(Inner).GetProperties(flags).Where(filter);
var mappingProperties = innerProperties.Where(p => !outerProperties.ContainsKey(p.Name));
//code above gets the properties of Inner that needs to be mapped
var outerParameter = Expression.Parameter(typeof(Outer));
var accessBar = Expression.Property(outerParameter, nameof(Outer.Bar));
var map = CreateMap<Outer, FlatDto>();
var mapExp = Expression.Constant(map);
foreach (var property in mappingProperties)
{
var accessProperty = Expression.MakeMemberAccess(accessBar, property);
var funcType = typeof(Func<,>).MakeGenericType(typeof(Outer), property.PropertyType);
var funcExp = Expression.Lambda(funcType, accessProperty, outerParameter);
//above code builds s => s.Bar.Qux
var configType = typeof(IMemberConfigurationExpression<,,>).MakeGenericType(typeof(Outer), typeof(FlatDto), typeof(object));
var configParameter = Expression.Parameter(configType);
var mapFromMethod = configType
.GetMethods()
.Single(m => m.Name == "MapFrom" && m.IsGenericMethod)
.MakeGenericMethod(property.PropertyType);
var invokeMapFrom = Expression.Call(configParameter, mapFromMethod, funcExp);
var configExp = Expression.Lambda(typeof(Action<>).MakeGenericType(configType), invokeMapFrom, configParameter);
//above code builds opt => opt.MapFrom(s => s.Bar.Qux)
var forMemberMethod = map.GetType()
.GetMethods()
.Single(m => m.Name == "ForMember" && !m.IsGenericMethod);
var invokeForMember = Expression.Call(mapExp, forMemberMethod, Expression.Constant(property.Name), configExp);
//above code builds map.ForMember("Qux", opt => opt.MapFrom(s => s.Bar.Qux))
var configAction = Expression.Lambda<Action>(invokeForMember);
configAction.Compile().Invoke();
}
}
}
Looks very huge code but in fact you can(and should) put the get property/method snippet somewhere else, the foreach loop itself uses them to build an expression to invoke. It's quite clean and effective.

resolving complex entity model to flat model view using AutoMapper

I want to create a map for a somewhat complex entity model to a flattened view Model
My entity model is like so
cbItems
has many cbItemsContent
has many cbRegulators
so my viewmodels are like so
for cbItems:
public class ItemViewModel
{
public ItemViewModel()
{
this.CbItemsContents = new HashSet<ItemContentViewModel>();
}
public int ItemID { get; set; }
......
public virtual ICollection<ItemContentViewModel> CbItemsContents { get; set; }
}
}
for cbItemsContent:
public class ItemContentViewModel
{
public int ItemContentID { get; set; }
public int ItemID { get; set; }
....
public ItemContentRegulatorsViewModel RegulatedBy { get; set; }
}
}
for cbRegulators:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IEnumerable<int> RegulatorIds { get; set; }
}
}
I had hoped it would be as easy as this:
config.CreateMap<CbItem, ItemViewModel>();
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy.ItemContentId,
m => m.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatedBy.RegulatorIds,
n => n.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
from teh following query:
ItemViewModel item =
_context.cbItems.Where(u => u.ItemId = id)
.ProjectTo<ItemViewModel>()
.first();
But this results in an error:
Expression 'dest => dest.RegulatedBy.ItemContentId' 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 achieve my desired model layout?
You have to map ItemContentRegulatorsViewModel, then you don't need to set it from the ViewModel above.
#Rabban probably means something like this:
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy, o => o.MapFrom(src => src));
config.CreateMap<CbItemsContent, ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.ItemContentId, o => o.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatorIds, o => o.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));

Cast EF query result to extended type with extended property taken from EF query

I've got a Tag object:
public class Tag
{
public int TagID { get; set; }
public string Name { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public Tag()
{
Jobs = new HashSet<Job>();
}
}
and extended:
public class RecentTag : Tag
{
public int Count { get; set; }
}
...and I'm trying to retrieve a list of RecentTag objects with Count from the query added to each object:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
var tags = Jobs
.Where(j => j.DatePosted > DateTime.Now.AddDays(-(numberofdays)))
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new
{
RecentTag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
// return RecentTags { TagID, Name, Count, Jobs }
}
So, how do I cast results of the query to RecentTag type and return the list of extended objects?
Any help would be appreciated. Thanks in advance!
if Jobs is actually a collection of Tags, and they are in fact RecentTag objects, then you can simply use the Cast method.
var rtags = tags.Cast<RecentTags>;
However, if Jobs is not a collection of tags, then you need to project into a RecentTags objects..
var rtags = tags.Select(x => new RecentTags() { // assign the members });
I ended up doing:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
DateTime startdate = DateTime.Now.AddDays(-(numberofdays));
IEnumerable<RecentTag> tags = Jobs
.Where(j => j.DatePosted > startdate) // Can't use DateTime.Now.AddDays in Entity query apparently
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new RecentTag
{
TagID = k.TagID,
Name = k.Name,
Count = g.Count()
})
.OrderByDescending(g => g.Count)
.Select(a => a);
return tags;
}

Categories