AutoMapper: How to do something if condition fails? - c#

I'm mapping an object using AutoMapper and my destination object has already some filled properties at this point.
My Configuration already looks like this:
// MapperConfiguration
CreateMap<TestClass, TestClass>()
.ForMember(d => d.Property1, c => c.Condition((s, d) => string.IsNullOrWithSpace(d.Property1));
// Test Class
class TestClass {
public string Property1 {get; set;}
}
Now I want to write to a log, if the condition fails/the property is already set. Is there a way to achieve this or an alternative workaround?
I'm using AutoMapper v8.0.0

I have a simple solution but it is in a hacking-way style:
public static bool IsNullOrWithSpaceWithLog(string x){
log.Info("something")
return string.IsNullOrWithSpace(x);
}
CreateMap<TestClass, TestClass>()
.ForMember(d => d.Property1, c => c.Condition((s, d) => IsNullOrWithSpaceWithLog(d.Property1));

Related

Automapper - map list of complex object to list of properties

I have following DomainObject class:
public class MyDomainObj
{
public CUSTOMER customer {get;set;} // This is database entity
public ORDER order {get;set;}
}
My DTO looks like this:
public class MyDTO
{
public string custId{get;set;}
public strinf orderId{get;set;}
}
Let's say in CUSTOMER table I have an attribute with the name: customer_id
same for ORDER table its order_id
Here is my automapper configuration:
m.CreateMap<CUSTOMER, MyDTO>().ForMember(d => d.custId, o => o.MapFrom(s => s.customer_id));
m.CreateMap<ORDER, MyDTO>().ForMember(d => d.orderId, o => o.MapFrom(s => s.order_id));
I wrote an extension method for mapper in order to work:
public static class ExtensionAutoMapper
{
public static TDestination Map<TSource, TDestination>(this TDestination destination, TSource source)
{
return Mapper.Map(source, destination);
}
}
Usage is:
var response = Mapper.Map<MyDTO>(myDomainObj.customer)
.Map(myDomainObj.order);
This works fine.
Question:
How do I change my mapping configuration in order to map list of domain objects to list of dto?
something along these lines
var response = Mapper.Map<List<MyDomainObj>, List<MyDTO>>(myDomainObj);
Edit:
I would like to map fields in database entity to dto properties automatically if they have the same name.
Answer provided by #jmoerdyk, solve my problem. However, in that approach, I have to map all fields of a database entity to dto even if they have the same name.
You just provide the Mapping from MyDomainObj to MyDto, and it should be able to handle mapping the collections:
Mapper.CreateMap<MyDomainObj,MyDTO>()
.ForMember(d => d.custId, o => o.MapFrom(s => s.customer.customer_id))
.ForMember(d => d.orderId, o => o.MapFrom(s => s.order.order_id));
Then call it just like you had (assuming myDomainObj is a List<MyDominObj>):
var response = Mapper.Map<List<MyDomainObj>, List<MyDTO>>(myDomainObjList);

Automapper: Flattening

I've tried everything to map from Item class to ItemDto class (basically a flattening map) but I keep getting a null for ItemDto.NestedItemName:
public class Item
{
public NestedItem NestedItem{get;set;}
}
public class NestedItem
{
public string Name{get;set;}
}
public class ItemDto
{
public string NestedItemName{get;set;}
}
I would have thought this would work:
CreateMap<NestedItem, ItemDto>()
.ForMember(dest => dest.NestedItemName, opt => opt.MapFrom(src => src.Name));
but it returns null. Any ideas?
I'm using AutoMapper 7.0.1 in a .Net Core 2.1 app.
You are using the wrong mapping. More than likely it would be the item being converted to the dto so the map should be created using that
CreateMap<Item, ItemDto>()
.ForMember(
dest => dest.NestedItemName,
opt => opt.MapFrom(src => src.NestedItem.Name)
);
From comments
There is be no need for the custom mapping, the default naming conventions covers this

delegating Member mapping to child object with AutoMapper

I have a destination class that combines properties from a source class and an inner class of that source class.
class Source {
public int Id {get;set;}
public int UseThisInt {get;set;}
public InnerType Inner {get;set;}
// other properties that the Destination class is not interested in
}
class InnerType {
public int Id {get;set;}
public int Height {get;set;}
// more inner properties
}
my destination class should combine UseThisInt and all properties of the InnerType.
class Destination {
public int Id {get;set;}
public int UseThisInt {get;set;}
public int Height {get;set;}
// more inner properties that should map to InnerType
}
Now my AutoMapper configuration looks like this:
CreatMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.Height, o => o.MapFrom(s => s.Inner.Height));
AutoMapper will correctly map UseThisInt between Source and Destination, but I would like to be able to let it map all the other properties in Destination like Height without an explicit ForMember configuration.
I tried using
Mapper.Initialize(cfg => cfg.CreateMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.UseThisInt, o => o.MapFrom(s => s.UseThisInt))
.ForAllOtherMembers(o => o.MapFrom(source=> source.Inner))
);
, but that did not achieve the intended result and left Destination.Height untouched.
Most examples of AutoMapper demonstrate creating a new Destination object from some source object, but AutoMapper can also be used to update an existing object taking those properties from the source object that are mapped and leaving any remaining properties untouched.
Consequently it is possible to map from the source to the destination in multiple steps.
So if you create a mapping configuration from InnerType like so:-
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>();
cfg.CreateMap<InnerType, Destination>();
});
Then you can make use of this ability to overlay mappings by mapping into the destination object twice.
var dest = Mapper.Map<Destination>(src);
Mapper.Map(src.Inner, dest);
One downside to this approach is that you need to be mindful of this when using the Mapper to generate a Destination object. However, you have the option of declaring this second mapping step within your AutoMapper configuration as an AfterMap instruction.
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.AfterMap((src, dest) => Mapper.Map(src.Inner, dest));
cfg.CreateMap<InnerType, Destination>();
});
With this updated configuration you can perform the mapping with a single Map call:-
var dest = Mapper.Map<Destination>(src);
CreateMap<Source, Destination>()
.AfterMap((src, dest) =>
{
dest.Height = src.Inner.Height;
});

How can i set the external data that not exist dto object to domain on automapper

I am using Dto for data transfer view to domain and use automapper for mapping.
My problem is, the property that not exist dto but i need to set before mapping to domain.
I have tried to use Linq query to get external data from db on before and after mapping methods but linq query giving error.
Sample below
FooDto
public class FooDto
{
public int MyProperty1 {get;set;}
}
FooDomain
public class Foo
{
public int MyProperty1 {get;set;}
public int MyProperty2 {get;set;}
public int Foo2ID {get;set;}
public virtual Foo2 Foo2 {get;set;}
}
Foo2Domain
public class Foo2
{
public int ID {get;set;}
public int MyProperty1 {get;set;}
}
**AutoMapper*
Mapper.Initialize(x =>
{
x.CreateMap<FooDto, Foo>().BeforeMap(
(src, dest) =>dest.MyProperty2 = dest.Foo2.MyProperty1);
}
I want to set Foo2.MyProperty1 to Foo.MyProperty2 using mapping.
This answer might need to be edited if my assumptions are wrong. The assumption I am making is that the source object has the right data. Based on your sample it looks like your source object's MyProperty2 can be set in the destination object so do map this all you would need to do is:
Mapper.Initialize(x =>
{
x.CreateMap<FooDto, Foo>()
.ForMember(dest => dest.MyProperty2, opt => opt.MapFrom(src => src.MyProperty1))
.ForMember(dest => dest.Foo2.MyProperty1, opt => opt.MapFrom(src => src.MyProperty1));
}
What this code does is it tells AutoMapper when I give you an object of Type FooDto and I am requesting an object of Type Foo. For the destination objects property 'Foo2.MyProperty1' and 'MyProperty2', use the options method MapFrom. Go to the source Object get the MyProperty1 and assign it's value to my destination objects MyProperty2 and Foo2.MyProperty1.
I think this would fix you up.
Right sorry I corrected the answer

A better way to use AutoMapper to flatten nested objects?

I have been flattening domain objects into DTOs as shown in the example below:
public class Root
{
public string AParentProperty { get; set; }
public Nested TheNestedClass { get; set; }
}
public class Nested
{
public string ANestedProperty { get; set; }
}
public class Flattened
{
public string AParentProperty { get; set; }
public string ANestedProperty { get; set; }
}
// I put the equivalent of the following in a profile, configured at application start
// as suggested by others:
Mapper.CreateMap<Root, Flattened>()
.ForMember
(
dest => dest.ANestedProperty
, opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)
);
// This is in my controller:
Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot);
I have looked at a number of examples, and so far this seems to be the way to flatten a nested hierarchy. If the child object has a number of properties, however, this approach doesn't save much coding.
I found this example:
http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx
but it requires instances of the mapped objects, required by the Map() function, which won't work with a profile as I understand it.
I am new to AutoMapper, so I would like to know if there is a better way to do this.
I much prefer avoiding the older Static methods and do it like this.
Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the Context.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Root, Flattened>()
.AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
CreateMap<Nested, Flattened>();
}
}
The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.
An XUnit test:
[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
// ARRANGE
var myRoot = new Root
{
AParentProperty = "my AParentProperty",
TheNestedClass = new Nested
{
ANestedProperty = "my ANestedProperty"
}
};
// Manually create the mapper using the Profile
var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();
// ACT
var myFlattened = mapper.Map<Root, Flattened>(myRoot);
// ASSERT
Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}
By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.
In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple .ForMember statements.
In your example, if you update your Flattened class to be:
public class Flattened
{
public string AParentProperty { get; set; }
public string TheNestedClassANestedProperty { get; set; }
}
You can avoid the use of the ForMember statement:
Mapper.CreateMap<Root, Flattened>();
Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case. It looks less ugly when you're using real class names, honest!
2 more possible solutions:
Mapper.CreateMap<Nested, Flattened>()
.ForMember(s=>s.AParentProperty, o=>o.Ignore());
Mapper.CreateMap<Root, Flattened>()
.ForMember(d => d.ANestedProperty, o => o.MapFrom(s => s.TheNestedClass));
An alternative approach would be the below, but it would not pass the Mapper.AssertConfigurationIsValid().
Mapper.CreateMap<Nested, Flattened>()
//.ForMember map your properties here
Mapper.CreateMap<Root, Flattened>()
//.ForMember... map you properties here
.AfterMap((s, d) => Mapper.Map(s.TheNestedClass, d));
Not sure if this adds value to the previous solutions, but you could do it as a two-step mapping. Be careful to map in correct order if there are naming conflicts between the parent and child (last wins).
Mapper.CreateMap<Root, Flattened>();
Mapper.CreateMap<Nested, Flattened>();
var flattened = new Flattened();
Mapper.Map(root, flattened);
Mapper.Map(root.TheNestedClass, flattened);
To improve upon another answer, specify MemberList.Source for both mappings and set the nested property to be ignored. Validation then passes OK.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SrcNested, DestFlat>(MemberList.Source);
cfg.CreateMap<SrcRoot, DestFlat>(MemberList.Source)
.ForSourceMember(s => s.Nested, x => x.Ignore())
.AfterMap((s, d) => Mapper.Map(s.Nested, d));
});
Mapper.AssertConfigurationIsValid();
var dest = Mapper.Map<SrcRoot, DestFlat>(src);
I wrote extension method to solve similar problem:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression,
Expression<Func<TSource, TNestedSource>> nestedSelector,
IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);
var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm => pm.IsMapped() && !pm.IsIgnored())
.ToDictionary(pm => pm.DestinationProperty.Name,
pm => Expression.Lambda(
Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
nestedSelector.Parameters[0]));
foreach (var property in dstProperties)
{
if (!flattenedMappings.ContainsKey(property))
continue;
expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
}
return expression;
}
So in your case it can be used like this:
var nestedMap = Mapper.CreateMap<Nested, Flattened>()
.IgnoreAllNonExisting();
Mapper.CreateMap<Root, Flattened>()
.FlattenNested(s => s.TheNestedClass, nestedMap);
IgnoreAllNonExisting() is from here.
Though it's not universal solution it should be enough for simple cases.
So,
You don't need to follow flattening convention in destination's properties
Mapper.AssertConfigurationIsValid() will pass
You can use this method in non-static API (a Profile) as well
I created a simple example using AutoMappers new naming convention rules to map from flattened to nested objects, hope this helps
https://dotnetfiddle.net/i55UFK

Categories

Resources