AutoMapper map using linked maps / Transitive mapping / Chain maps - c#

In C# dotnet, using AutoMapper how can I create a map that uses chained mappings / map using other maps to intermediary types?
In the example a map from type A to type B exists, and also from B to C.
So a map from A to C could be created using the above two: combining them or using the first to map to an object of type B and then the second for the final map, through the path: A -> B -> C.
In more complex scenarios there could be more than a single path so one needs to be specified, but how?
public class A
{
public string TestA { get; set; }
}
public class B
{
public string TestB { get; set; }
}
public class C
{
public string TestC { get; set; }
}
this.CreateMap<A, B>()
.ForMember(dst => dst.TestB, opts => opts.MapFrom(src => src.TestA));
this.CreateMap<B, C>()
.ForMember(dst => dst.TestC, opts => opts.MapFrom(src => src.TestB));
this.CreateMap<A, C>();// A -> B -> C
// something like .UsingMap<A, B>().ThenUsingMap<B, C>()
How can we express this?

Ok, I found this question trying to do the same. If I'm not wrong, you have A->B and B->C, and you want A->C.
To do this, what I did was to create this two maps and then I used ConvertUsing like this:
CreateMap<A, B>();
CreateMap<B, C>();
CreateMap<A, C>()
.ConvertUsing((entity, c, context) =>
{
var intermediate = context.Mapper.Map<B>(entity);
return context.Mapper.Map<C>(intermediate);
});
It just worked for me. I don't know if what you are looking for is more complex or has a different needs/end but for me it was a pretty simple map but the first one (A->B) had legacy logic I didn't want to repeat in B->C so I did this to reuse the previous mapping. Let me know what you think and if it works for you.

Related

Automapper Dynamic Resolver's

I'm trying to create an abstract layer on top of automapper which enables users to dynamically add custom rules to each property they map.
Given the Model
public class Entity
{
public int Index { get; set; }
}
public class DTO
{
public int Count { get; set; }
}
we may configure Automapper to map the entities like so:
//sorry this is pseudo coded
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.ResolveUsing<IndexResolver>());
public class IndexResolver: ValueResolver<DTO, int>,
{
protected override string ResolveCore(DTO source)
{
return source.Count - 1;
}
}
This works since we map the rule to a Value resolver, However if I wanted to create a rule at run time is that possible. I would like to be able to configure things like so:
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.Resolver(d => d.Count - 1);
Is there a way I can Add a resolver with an expression so I do not need to inherit from ValueResolver?
My first thought is to create a generic custom resolver that takes an expression in its constructor. You should then be able to do:
.ResolveUsing(new LambdaResolver(d => d.Count - 1))

Automapper create map from custom method

I have the following classes:
public class Entity
{
public string Name { get; set; }
}
public class SomethingDto
{
public string NameChanged { get; set; }
public void Mapping(Entity something)
{
NameChanged = something.Name;
}
}
I want to use the Mapping Method of the DTO to create the map as the following way:
conf.CreateMap<Entity, SomethingDto>().ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name));
There is a way in AutoMapper to create the maps with custom methods, who works with his projection?
You don't want to do it like that, because that makes the DTO aware of the entity and that would throw out the separation you'd get.
Now in this case, the line ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name)) will work because Name and NameChanged are both of type string. Say you'd like to do something along the lines of mapping identifier of type string with value '20180120-00123456' to two properties on the destination: a DateTime property and a ProductId property. You can do this two ways.
Simple
You would write two mapping functions in the class where you make the mapping and do it along the lines of:
ForMember(t => t.Date, opt => opt.MapFrom(t => RetrieveDate(t.Identifier)))
ForMember(t => t.ProductId, opt => opt.MapFrom(t => RetrieveProductId(t.Identifier)))
Complex
You would make a custom class OrderIdentifier (now I'm assuming the identifier is for an order) with only the Id property as string. Then you'd make two custom type converters, like the article describes.

Can NHibernate be configured to automatically call .CustomType<T> for all columns of a certain type?

I have a model that has a property of an Enum type:
public virtual Units Unit { get; set; } // Units is an enum
I have a class called EnumMapper which handles some custom mapping logic I have for use with my database. In my mapping, I have:
Map(x => x.Unit).CustomType<EnumMapper<Units>>();
This works great. No problems at all. However, I have quite a few models that have properties of type Units as well. Rather than call .CustomType<T>() on each one of these, I'm wondering if I can add something to my FluentConfiguration object to tell NHibernate to use this type mapping on any and all properties of type Units. Here's how I configure NHibernate so far:
private ISessionFactory InitializeSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(DatabaseConfiguration)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<DatabaseAdapter>()
.Conventions.Add(Table.Is(x => x.EntityType.Name.ToLowerInvariant())) // All table names are lower case
.Conventions.Add(ForeignKey.EndsWith("Id")) // Foreign key references end with Id
.Conventions.Add(DefaultLazy.Always()))
.BuildSessionFactory();
return sessionFactory;
}
I have a feeling that it's as simple as calling .Conventions.Add() on something else, but I can't seem to get it right.
Figured out one solution using ConventionBuilder:
.Conventions.Add(ConventionBuilder.Property.When(
c => c.Expect(x => x.Type == typeof(GenericEnumMapper<Units>)),
x => x.CustomType<EnumMapper<Units>>()
))
It's not super pretty, but it works. I think a better solution might be to write my own convention that automatically maps an enum to its correct custom type. I will post that here if I get it working.
Update: Cleaned this up a bit. I've added a static method to my EnumMapper<T> class:
public class EnumMapper<T> : NHibernate.Type.EnumStringType<T>
{
// Regular mapping code here
public static IPropertyConvention Convention
{
get
{
return ConventionBuilder.Property.When(
c => c.Expect(x => x.Type == typeof (GenericEnumMapper<T>)),
x => x.CustomType<EnumMapper<T>>()
);
}
}
}
Now I can just configure it with:
.Conventions.Add(EnumMapper<Units>.Convention)

Circular reference causing stack overflow with Automapper

I'm using Automapper to map my NHibernate proxy objects (DTO) to my CSLA business objects
I'm using Fluent NHibernate to create the mappings - this is working fine
The problem I have is that the Order has a collection of OrderLines and each of these has a reference to Order.
public class OrderMapping : ClassMap<OrderDTO>
{
public OrderMapping()
{
// Standard properties
Id(x => x.OrderId);
Map(x => x.OrderDate);
Map(x => x.Address);
HasMany<OrderLineDTO>(x => x.OrderLines).KeyColumn("OrderId").Inverse();
Table("`Order`");
}
}
public class OrderDTO
{
// Standard properties
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual string Address { get; set; }
// Child collection properties
public virtual IList<OrderLineDTO> OrderLines { get; set; } <-- this refs the lines
}
and:
public class OrderLineMapping : ClassMap<OrderLineDTO>
{
public OrderLineMapping()
{
// Standard properties
Id(x => x.OrderLineId);
References<OrderDTO>(x => x.Order).Column("OrderId");
Map(x => x.Description);
Map(x => x.Amount);
Table("`OrderLine`");
}
}
public class OrderLineDTO
{
// Standard properties
public virtual int OrderLineId { get; set; }
public virtual string Description { get; set; }
public virtual decimal Amount { get; set; }
public virtual OrderDTO Order { get; set; } // <-- this refs the order
}
These DTO objects map to Order and OrderLines CSLA objects respectively
When auto-mapping to OrderLines a list of OrderLinesDTO is mapped. Auto mapper is then mapping the "Order" property on of the lines, which maps back to Order which then circularly maps back to OrderLine, then to Order and so on
Does anyone know if Automapper can avoid this circular reference?
In your Automapper configuration:
Mapper.Map<OrderLine, OrderLineDTO>()
.ForMember(m => m.Order, opt => opt.Ignore());
Mapper.Map<Order, OrderDTO>()
.AfterMap((src, dest) => {
foreach(var i in dest.OrderLines)
i.Order = dest;
});
I was having the same issue using EF 6 and AutoMapper 6. Apparently what Kenny Lucero posted led me to the solution. Here's an extract from AM site:
// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();
Adding PreserveReferences() to both models made it work.
I was having the same issue and solved it by downgrading to version 4.2.1.
apparently the checks for circular references was expensive so they made it default to not check.
Migrating to AutoMapper 5 - Circular references
Supposedly these are supposed to be the settings methods for v 5+ but it didn't work for my data model because we opt'd for complex dto relationships instead of single use dtos for each action.
// Self-referential mapping
cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();
http://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html#circular-references
Automapper is supposed to be able to statically determine if the circular reference settings in v6.1+, So if it doesn't work for you automatically in version v6.1+ contact the automapper team.
Since this is the #1 google search result, I think there might be some people, like me, coming here who don't get a stackoverflow exception, but find trouble when sending the object (via ASP.NET) to the client, and thus it being JSON serialized.
So I had the same structure in place, Invoices has multiple InvoiceLines, when I load an Invoice and use the Linq-to-SQL .Include(x => x.InvoiceLines) I get errors when I try to load the object from the Api because each InvoiceLine contains the same Invoice again.
To solve this, do the following in ASP.NET Core Startup class:
services.AddMvc().AddJsonOptions(o =>
{
o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
o.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// ^^ IMPORTANT PART ^^
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
So include o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; in your JsonConfiguration when adding MVC to your application.
JSON.Net is taking the extra step to setup each reference with an additional meta-property called “$id”. When JSON.Net encounters the same instance in another place in the object graph, it simply drops a reference to the original instance, instead of duplicating the data, and thus not causing circular reference issues!
Source: https://johnnycode.com/2012/04/10/serializing-circular-references-with-json-net-and-entity-framework/
So now I don't have to further edit my AutoMapper configuration.
If anyone using Mapster (a mapping library for C# same as AutoMapper)
TypeAdapterConfig<TSource, TDestination>
.NewConfig()
.PreserveReference(true);
need to be used for preventing stack overflow error.
Not sure if I should post it here:
I had the same error after doing an automapper.map in a method.
The answer of CularBytes got me thinking that the issue was not automapper related but json related.
I did:
Return ok(_service.getDataById(id));
instead of
Return ok(await _service.getDataById(id));
(I forgot to await an asyc call... rookie mistake I know)

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