Automapper not invoking constructor - c#

I updated my app to Automapper 5.1.1 and I am seeing different behavior now. When I instantiate an object like this:
PolicyEntity entity = mapper.Map<PolicyEntity>(template);
The PolicyEntity default constructor is no longer being invoked.
I couldn't find anything in the upgrade guide or release notes that seemed related; what am I missing?
I created my mapping like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PolicyTemplate, PolicyEntity>()
.ForAllMembers(o => o.Condition(c => c != null));
});
mapper = config.CreateMapper();
PolicyEntity has the following constructors:
public PolicyEntity() : base() { }
public PolicyEntity(string name)
{
...
}

I ended up finding my answer here.
Apparently the default parameter passed to the ForAllMembers(o => o.Condition()) anonymous function changed, so my condition was failing, which meant AutoMapper was populating the properties of the target object even when the source value was null.
Changing my map definition to this fixed it:
.ForAllMembers(o => o.Condition((source, target, sourceValue, targetValue) => sourceValue != null));

Related

Automapper custom value resolver reuse for multiple types

I have a project which I am trying to use AutoMapper to map from multiple classes in each of these classes there are properties where I would like to use some custom logic to parse the source value to the destination.
I have tried to use custom resolver methods as documented on the AutoMapper docs.
Here is my code:
public class CustomDateTextHandler : IValueResolver<object, object, string>
{
public string Resolve(object source, object destination, string destMember, ResolutionContext context)
{
string txt = source.ToString();
txt.Replace("AM/PM", "tt");
txt.Replace("HH:MM", "hh:mm");
if (txt.Contains("format"))
{
txt.Replace("mmm", "MMM");
}
return txt;
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
.ForMember(dest => dest.id, opt => opt.Ignore())
.ForMember(dest => dest.cell_text, opt => opt.MapFrom<CustomDateTextHandler>())
.ForMember(dest => dest.fn_def, opt => opt.MapFrom<CustomCodeTextResolver>());
}
What I am trying to get is the cell_text value processed with my replace logic in the resolver method but the issue I am facing is that what is being passed to the resolver is the entire SMapping instance, I would like to be able to reuse the resolver code across different classes where the property names will be different, however looking at what it going on at the moment I could not really use the resolver code across my different classes.
Can someone help me?
Thank you in advance.
Use IMemberValueResolver instead of IValueResolver.
Compared to IValueResolver, its Resolve function gets one more parameter: value.
Registering mapping with IMemberValueResolver requires you to pass 1 extra parameter - not the 'value' directly, but a lambda that will produce a 'value' from given source object.
public class CustomDateTextHandler :
IMemberValueResolver< // note: different interface
object, object,
string, string // note: 1 more parameter
>
{
public string Resolve(
object source, object destination,
string sourceValue, string destMember, // note: 1 more parameter
ResolutionContext context
)
{
// here, see the difference:
// source - source object, whole
// sourceMember - value produced by extra lambda passed in mapping
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
...
.ForMember(
dest => dest.cell_text,
opt => opt.MapFrom<CustomDateTextHandler, string>(source => source.PROPERTY11)) // note: this produces that sourceValue
.ForMember(
dest => dest.fn_def,
opt => opt.MapFrom<CustomCodeTextResolver, string>(source => source.PROPERTY22)); // note: this produces that sourceValue
}

EF Core: Detached lazy-loading navigation property

I have the following query:
var query = _context.QuestOrders.Include(a => a.Driver).ThenInclude(i => i.DlStateProvince)
.Where(p => carrierIds.Contains(p.Driver.CarrierId))
....
;
and then try to call the following:
var queryDto = query.AsNoTracking().ProjectTo<DcReportDonorResultDto>(_mapperConfiguration);
var reports = new PagedList<DcReportDonorResultDto>(queryDto, pageIndex, pageSize);
where DcReportDonorResultDto has a property:
public string PrimaryId { get; set; }
which mapped the following:
CreateMap<QuestOrder, DcReportDonorResultDto>()
.ForMember(destinationMember => destinationMember.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId))
and PrimaryId is defined in QuestOrder as:
public string PrimaryId
{
get
{
if (!string.IsNullOrWhiteSpace(DlNumber) && DlStateProvinceId.HasValue)
return DlStateProvince.Abbreviation + DlNumber.Replace("-", "");
else
return string.Empty;
}
}
I received the following error:
System.InvalidOperationException: 'Error generated for warning
'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning:
An attempt was made to lazy-load navigation property 'DlStateProvince'
on detached entity of type ''. Lazy-loading is not supported for
detached entities or entities that are loaded with 'AsNoTracking()'.'.
This exception can be suppressed or logged by passing event ID
'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings'
method in 'DbContext.OnConfiguring' or 'AddDbContext'.'
How to solve this problem?
The problem is caused by the computed QuestOrder.PrimaryId property.
When used in LINQ to Entities queries, such properties cannot be translated to SQL and require client evaluation. And even when supported, client evaluation does not play well when accessing navigation properties inside - both eager or lazy loading do not function properly and lead to either runtime exceptions or wrong return value.
So the best is to make them translatable, which requires dealing with translatable expressions.
In all the cases, start by converting the computed property body from block to conditional operator (to make it translatable):
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
Now, short turn, quick-and-dirty solution is to extract the actual expression for the computed property, copy/paste it in the mapping and replace this with src.Driver:
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src =>
//src.Driver.PrimaryId
!string.IsNullOrWhiteSpace(src.Driver.DlNumber) && src.Driver.DlStateProvinceId.HasValue) ?
src.Driver.DlStateProvince.Abbreviation + src.Driver.DlNumber.Replace("-", "") :
string.Empty
))
Long term, or if you have many properties like this, or you need to use it in other mappings/queries, or just because of the code duplication, this is not a good solution. You need a way to replace the computed property accessor inside query expression tree with the corresponding expression extracted from the body.
Neither C#, nor BCL or EF Core help in that regard. Several 3rd party packages are trying to address this problem to some sort of degree - LinqKit, NeinLinq etc., but there is a little not very well known gem called DelegateDecompiler which does that with minimum code changes.
All you need is to install the DelegateDecompiler or DelegateDecompiler.EntityFrameworkCore package, mark your computed properties with [Computed] attribute
[Computed] // <--
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
and then call Decompile (or DecompileAsync) on the top level queryable
var queryDto = query.AsNoTracking()
.ProjectTo<DcReportDonorResultDto>(_mapperConfiguration)
.Decompile(); // <--
No special mappings are needed for AutoMapper, e.g. you can keep the usual
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId)
For AutoMapper projection queries (produced with ProjectTo) you can even eliminate the need for calling Decompile / DecompileAsync by providing the following little `"bridge" between the two libraries:
namespace AutoMapper
{
using DelegateDecompiler;
using QueryableExtensions;
public static class AutoMapperExtensions
{
public static IMapperConfigurationExpression UseDecompiler(this IMapperConfigurationExpression config)
{
var resultConverters = config.Advanced.QueryableResultConverters;
for (int i = 0; i < resultConverters.Count; i++)
{
if (!(resultConverters[i] is ExpressionResultDecompiler))
resultConverters[i] = new ExpressionResultDecompiler(resultConverters[i]);
}
return config;
}
class ExpressionResultDecompiler : IExpressionResultConverter
{
IExpressionResultConverter baseConverter;
public ExpressionResultDecompiler(IExpressionResultConverter baseConverter) => this.baseConverter = baseConverter;
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap, LetPropertyMaps letPropertyMaps) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap, letPropertyMaps));
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap));
static ExpressionResolutionResult Decompile(ExpressionResolutionResult result)
{
var decompiled = DecompileExpressionVisitor.Decompile(result.ResolutionExpression);
if (decompiled != result.ResolutionExpression)
result = new ExpressionResolutionResult(decompiled, result.Type);
return result;
}
}
}
}
and just call UseDecompiler() during AutoMapper initialization, for instance
var mapperConfig = new MapperConfiguration(config =>
{
config.UseDecompiler(); // <--
// the rest (add profiles, create maps etc.) ...
});

ASP.NET Core with EF Core - DTO Collection mapping

I am trying to use (POST/PUT) a DTO object with a collection of child objects from JavaScript to an ASP.NET Core (Web API) with an EF Core context as my data source.
The main DTO class is something like this (simplified of course):
public class CustomerDto {
public int Id { get;set }
...
public IList<PersonDto> SomePersons { get; set; }
...
}
What I don't really know is how to map this to the Customer entity class in a way that does not include a lot of code just for finding out which Persons had been added/updated/removed etc.
I have played around a bit with AutoMapper but it does not really seem to play nice with EF Core in this scenario (complex object structure) and collections.
After googling for some advice around this I haven't found any good resources around what a good approach would be. My questions is basically: should I redesign the JS-client to not use "complex" DTOs or is this something that "should" be handled by a mapping layer between my DTOs and Entity model or are there any other good solution that I am not aware of?
I have been able to solve it with both AutoMapper and and by manually mapping between the objects but none of the solutions feels right and quickly become pretty complex with much boilerplate code.
EDIT:
The following article describes what I am referring to regarding AutoMapper and EF Core. Its not complicated code but I just want to know if it's the "best" way to manage this.
(Code from the article is edited to fit the code example above)
http://cpratt.co/using-automapper-mapping-instances/
var updatedPersons = new List<Person>();
foreach (var personDto in customerDto.SomePersons)
{
var existingPerson = customer.SomePersons.SingleOrDefault(m => m.Id == pet.Id);
// No existing person with this id, so add a new one
if (existingPerson == null)
{
updatedPersons.Add(AutoMapper.Mapper.Map<Person>(personDto));
}
// Existing person found, so map to existing instance
else
{
AutoMapper.Mapper.Map(personDto, existingPerson);
updatedPersons.Add(existingPerson);
}
}
// Set SomePersons to updated list (any removed items drop out naturally)
customer.SomePersons = updatedPersons;
Code above written as a generic extension method.
public static void MapCollection<TSourceType, TTargetType>(this IMapper mapper, Func<ICollection<TSourceType>> getSourceCollection, Func<TSourceType, TTargetType> getFromTargetCollection, Action<List<TTargetType>> setTargetCollection)
{
var updatedTargetObjects = new List<TTargetType>();
foreach (var sourceObject in getSourceCollection())
{
TTargetType existingTargetObject = getFromTargetCollection(sourceObject);
updatedTargetObjects.Add(existingTargetObject == null
? mapper.Map<TTargetType>(sourceObject)
: mapper.Map(sourceObject, existingTargetObject));
}
setTargetCollection(updatedTargetObjects);
}
.....
_mapper.MapCollection(
() => customerDto.SomePersons,
dto => customer.SomePersons.SingleOrDefault(e => e.Id == dto.Id),
targetCollection => customer.SomePersons = targetCollection as IList<Person>);
Edit:
One thing I really want is to delcare the AutoMapper configuration in one place (Profile) not have to use the MapCollection() extension every time I use the mapper (or any other solution that requires complicating the mapping code).
So I created an extension method like this
public static class AutoMapperExtensions
{
public static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(this IMapper mapper,
ICollection<TSourceType> sourceCollection,
ICollection<TTargetType> targetCollection,
Func<ICollection<TTargetType>, TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull)
{
var existing = targetCollection.ToList();
targetCollection.Clear();
return ResolveCollection(mapper, sourceCollection, s => getMappingTargetFromTargetCollectionOrNull(existing, s), t => t);
}
private static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(
IMapper mapper,
ICollection<TSourceType> sourceCollection,
Func<TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull,
Func<IList<TTargetType>, ICollection<TTargetType>> updateTargetCollection)
{
var updatedTargetObjects = new List<TTargetType>();
foreach (var sourceObject in sourceCollection ?? Enumerable.Empty<TSourceType>())
{
TTargetType existingTargetObject = getMappingTargetFromTargetCollectionOrNull(sourceObject);
updatedTargetObjects.Add(existingTargetObject == null
? mapper.Map<TTargetType>(sourceObject)
: mapper.Map(sourceObject, existingTargetObject));
}
return updateTargetCollection(updatedTargetObjects);
}
}
Then when I create the mappings I us it like this:
CreateMap<CustomerDto, Customer>()
.ForMember(m => m.SomePersons, o =>
{
o.ResolveUsing((source, target, member, ctx) =>
{
return ctx.Mapper.ResolveCollection(
source.SomePersons,
target.SomePersons,
(targetCollection, sourceObject) => targetCollection.SingleOrDefault(t => t.Id == sourceObject.Id));
});
});
Which allow me to use it like this when mapping:
_mapper.Map(customerDto, customer);
And the resolver takes care of the mapping.
AutoMapper is the best solution.
You can do it very easily like this :
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerDto, Customer>();
Mapper.CreateMap<Person, PersonDto>();
Mapper.CreateMap<PersonDto, Person>();
Note : Because AutoMapper will automatically map the List<Person> to List<PersonDto>.since they have same name, and there is already a mapping from Person to PersonDto.
If you need to know how to inject it to ASP.net core,you have to see this article : Integrating AutoMapper with ASP.NET Core DI
Auto mapping between DTOs and entities
Mapping using attributes and extension methods
First I would recommend using JsonPatchDocument for your update:
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<CustomerDTO> patchDocument)
{
var customer = context.EntityWithRelationships.SingleOrDefault(e => e.Id == id);
var dto = mapper.Map<CustomerDTO>(customer);
patchDocument.ApplyTo(dto);
var updated = mapper.Map(dto, customer);
context.Entry(entity).CurrentValues.SetValues(updated);
context.SaveChanges();
return NoContent();
}
And secound you should take advantage of AutoMapper.Collections.EFCore. This is how I configured AutoMapper in Startup.cs with an extension method, so that I´m able to call services.AddAutoMapper() without the whole configuration-code:
public static IServiceCollection AddAutoMapper(this IServiceCollection services)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.UseEntityFrameworkCoreModel<MyContext>(services);
cfg.AddProfile(new YourProfile()); // <- you can do this however you like
});
IMapper mapper = config.CreateMapper();
return services.AddSingleton(mapper);
}
This is what YourProfile should look like:
public YourProfile()
{
CreateMap<Person, PersonDTO>(MemberList.Destination)
.EqualityComparison((p, dto) => p.Id == dto.Id)
.ReverseMap();
CreateMap<Customer, CustomerDTO>(MemberList.Destination)
.ReverseMap();
}
I have a similar object-graph an this works fine for me.
EDIT
I use LazyLoading, if you don´t you have to explicitly load navigationProperties/Collections.
I was struggling with the very same issue for quite some time. After digging through many articles I've came up with my own implementation which I'm sharing with you.
First of all I've created a custom IMemberValueResolver.
using System;
using System.Collections.Generic;
using System.Linq;
namespace AutoMapper
{
public class CollectionValueResolver<TDto, TItemDto, TModel, TItemModel> : IMemberValueResolver<TDto, TModel, IEnumerable<TItemDto>, IEnumerable<TItemModel>>
where TDto : class
where TModel : class
{
private readonly Func<TItemDto, TItemModel, bool> _keyMatch;
private readonly Func<TItemDto, bool> _saveOnlyIf;
public CollectionValueResolver(Func<TItemDto, TItemModel, bool> keyMatch, Func<TItemDto, bool> saveOnlyIf = null)
{
_keyMatch = keyMatch;
_saveOnlyIf = saveOnlyIf;
}
public IEnumerable<TItemModel> Resolve(TDto sourceDto, TModel destinationModel, IEnumerable<TItemDto> sourceDtos, IEnumerable<TItemModel> destinationModels, ResolutionContext context)
{
var mapper = context.Mapper;
var models = new List<TItemModel>();
foreach (var dto in sourceDtos)
{
if (_saveOnlyIf == null || _saveOnlyIf(dto))
{
var existingModel = destinationModels.SingleOrDefault(model => _keyMatch(dto, model));
if (EqualityComparer<TItemModel>.Default.Equals(existingModel, default(TItemModel)))
{
models.Add(mapper.Map<TItemModel>(dto));
}
else
{
mapper.Map(dto, existingModel);
models.Add(existingModel);
}
}
}
return models;
}
}
}
Then I configure AutoMapper and add my specific mapping:
cfg.CreateMap<TDto, TModel>()
.ForMember(dst => dst.DestinationCollection, opts =>
opts.ResolveUsing(new CollectionValueResolver<TDto, TItemDto, TModel, TItemModel>((src, dst) => src.Id == dst.SomeOtherId, src => !string.IsNullOrEmpty(src.ThisValueShouldntBeEmpty)), src => src.SourceCollection));
This implementation allows me to fully customize my object matching logic due to keyMatch function that is passed in constructor. You can also pass an additional saveOnlyIf function if you for some reason need to verify passed objects if they are suitable for mapping (in my case there were some objects that shouldn't be mapped and added to collection if they didn't pass an extra validation).
Then e.g. in your controller if you want to update your disconnected graph you should do the following:
var model = await Service.GetAsync(dto.Id); // obtain existing object from db
Mapper.Map(dto, model);
await Service.UpdateAsync(model);
This works for me. It's up to you if this implementation suits you better than what author of this question proposed in his edited post:)

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)

AutoMapper problem mapping Entity to Dictionary<Guid, string>

I'm having a problem with one of my automapping configurations that I can't seem to solve.
I have an entity of type contact and I'm trying to map a list of these to a dictionary. However, the mapping just doesn't do anything. The source dictionary remains empty. Can anyone offer any suggestions?
Below is a simplified version of the Contact type
public class Contact
{
public Guid Id { get; set ;}
public string FullName { get; set; }
}
My automapping configuration looks as follows
Mapper.CreateMap<Contact, KeyValuePair<Guid, string>>()
.ConstructUsing(x => new KeyValuePair<Guid, string>(x.Id, x.FullName));
And my calling code looks as follows
var contacts = ContactRepository.GetAll(); // Returns IList<Contact>
var options = new Dictionary<Guid, string>();
Mapper.Map(contacts, options);
This should work with the following, no Mapper needed...
var dictionary = contacts.ToDictionary(k => k.Id, v => v.FullName);
The documentation is very sketchy on the AutoMapper website. From what I can tell, the second parameter in Mapper.Map is used only to determine what type the return value should be, and is not actually modified. This is because it allows you to perform dynamic mapping based on an existing object whose type is only known at runtime instead of hard-coding a type in the generics.
So the problem in your code is that you are not making use of the return value of Mapper.Map, which actually contains the final converted object. Here is a modified version of your code that I've tested and correctly returns the converted objects as you expect.
var contacts = ContactRepository.GetAll();
var options = Mapper.Map(contacts, new Dictionary<Guid, string>());
// options should now contain the mapped version of contacts
Although it would be more efficient to take advantage of the generic version instead of constructing an unnecessary object just to specify the type:
var options = Mapper.Map<List<Contact>, Dictionary<Guid, string>>(contacts);
Here is a working code sample that can be run in LinqPad (an assembly reference to AutoMapper.dll is needed to run the sample.)
Hope this helps!
Another solution in GitHub AutoMapper:
https://github.com/AutoMapper/AutoMapper/issues/51
oakinger[CodePlex] just wrote a small extension method that solves this:
public static class IMappingExpressionExtensions
{
public static IMappingExpression<IDictionary, TDestination> ConvertFromDictionary<TDestination>(this IMappingExpression<IDictionary, TDestination> exp, Func<string, string> propertyNameMapper)
{
foreach (PropertyInfo pi in typeof(Invoice).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (!pi.CanWrite ||
pi.GetCustomAttributes(typeof(PersistentAttribute), false).Length == 0)
{
continue;
}
string propertyName = pi.Name;
propertyName = propertyNameMapper(propertyName);
exp.ForMember(propertyName, cfg => cfg.MapFrom(r => r[propertyName]));
}
return exp;
}
}
Usage:
Mapper.CreateMap<IDictionary, MyType>()
.ConvertFromDictionary(propName => propName) // map property names to dictionary keys

Categories

Resources