I've set my mapping up as follows:
CreateMap<SourceClass, DestinationClass>().ForMember(destinationMember => destinationMember.Provider,
memberOptions => memberOptions.MapFrom(src => src.Providers.FirstOrDefault()));
Where I'm mapping from a List in my SourceClass to a string in my destination class.
My question is, how can I handle the case where "Providers" is null?
I've tried using:
src?.Providers?.FirstOrDefault()
but I get an error saying I can't use null propagators in a lambda.
I've been reading up on Automapper and am still unsure if AM automatically handles the null case or not. I tried to build the expression tree, but was not able to see any information that provided additional informations.
If it helps, I'm using automapper v 6.1.1.
You could try using a ValueConverter with AutoMapper. That might look something like this
public class ListFormatter : IValueConverter<string, List<string>>
{
public List<string> Convert(string source)
{
if (source != null)
{
return new List<string> { source };
}
return new List<string>();
}
}
And then you can use it like this
CreateMap<SourceClass, DestinationClass>()
.ForMember(destinationMember => destinationMember.Provider,
memberOptions => memberOptions.ConvertUsing(new ListFormatter()));
This would allow you to change your value converter in the future if you need to switch logic or do something more complex.
Edit
Since you are using an older version, you could use a private/static/extension method to do the same thing. So something like
List<string> ConvertStringToList(string source)
{
if (source != null)
{
return new List<string> { source };
}
return new List<string>();
}
and then call it like so
CreateMap<SourceClass, DestinationClass>()
.ForMember(destinationMember => destinationMember.Provider,
memberOptions => memberOptions.MapFrom(src => ConvertStringToList(src.Provider)));
I generally prefer this to doing something inline as things get more complex, for the sake of readability
Try to use NullSubstitution option from AutoMapper
you can read here
Related
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.) ...
});
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));
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:)
I have been trying to solve this issue for a day now and have got no where so am hoping someone might have solved this before. The closest I found to a solution was How to simply map an NHibernate ISet to IList using AutoMapper and Map IList to ICollection through AutoMapper but still no joy.
I have a data object that looks like:
public class Parent
{
public virtual ISet<Child> Children {get; set; }
}
And a business object that looks like:
public class ParentDto
{
public IList<ChildDto> Children {get; set; }
}
Using AutoMapper to map from Data to Business works fine:
...
Mapper.CreateMap<Parent, ParentDto>();
Mapper.CreateMap<Child, ChildDto>();
...
ParentDto destination = CWMapper.Map<Parent, ParentDto>(source);
But when I come to Mapping from Business to Data I get the error:
...
Mapper.CreateMap<ParentDto, Parent>();
Mapper.CreateMap<ChildDto, Child>();
...
Parent destination = CWMapper.Map<ParentDto, Parent>(source);
Unable to cast object of type 'System.Collections.Generic.List' to ''Iesi.Collections.Generic.ISet'
I added a custom mapping:
Mapper.CreateMap<ParentDto, Parent>()
.ForMember(m => m.Children, o => o.MapFrom(s => ToISet<ChildDto>(s.Children)));
private static ISet<T> ToISet<T>(IEnumerable<T> list)
{
Iesi.Collections.Generic.ISet<T> set = null;
if (list != null)
{
set = new Iesi.Collections.Generic.HashedSet<T>();
foreach (T item in list)
{
set.Add(item);
}
}
return set;
}
But I still get the same error. Any help would be greatly apriciated!
You can use the AfterMap() function of AutoMapper, like this:
Mapper.CreateMap<ParentDto, Parent>()
.ForMember(m => m.Children, o => o.Ignore()) // To avoid automapping attempt
.AfterMap((p,o) => { o.Children = ToISet<ChildDto, Child>(p.Children); });
AfterMap() allows for more fine-grained control of some important aspects of NHibernate child collections handling (like replacing existing collections content instead of overwriting the collections reference as in this simplified example).
This is because the source and destination generic type parameters are not the same in the source and target properties that you are mapping. The mapping you need is from IEnumerable<ChildDto> to ISet<Child>, which can be generalized to a mapping from IEnumerable<TSource> to ISet<TDestination> and not IEnumerable<T> to ISet<T>. You need to take this into account in your conversion function (actually you have the correct answer in the title of your question..).
The ToISet method should be something like the one posted below. It uses AutoMapper as well to map ChildDto to Child.
private static ISet<TDestination> ToISet<TSource, TDestination>(IEnumerable<TSource> source)
{
ISet<TDestination> set = null;
if (source != null)
{
set = new HashSet<TDestination>();
foreach (TSource item in source)
{
set.Add(Mapper.Map<TSource, TDestination>(item));
}
}
return set;
}
You can then change the map definition as follows:
Mapper.CreateMap<ParentDto, Parent>().ForMember(m => m.Children,
o => o.MapFrom(p => ToISet<ChildDto, Child>(p.Children)));
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