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
Related
In my project I'm trying to use automapper to unflatten my command objects to my domain objects by convention as much as possible.
It works when I explicitly map the two members in the mapping profile, but according to the automapper documentation I think this should also work by convention.
I created a dotnetfiddle to demonstrate the minimal case.
Related questions end up with people explicitly adding the mapping, but that kind of goes against what Automapper is built for and contradicts the documentation, no?
It doesn't work with flattening either, so the reversemap is a red herring I think.
The mapping
public class Mapping: Profile
{
public Mapping()
{
this.CreateMap<CreateSelectionCommand, Selection>();
// .ForMember(selection => selection.Name, opt => opt.MapFrom(x => x.SelectionName))
.reverseMap()
}
}
What I expect to work
[Fact]
public void ShouldMapName()
{
var cmd = new CreateSelectionCommand {SelectionName = "selectionName"};
var selection = _mapper.Map<Selection>(cmd);
Assert.Equal(cmd.SelectionName, selection.Name); <== selection.Name == ""
}
Classes for context
public class Selection
{
public string Name { get; set; }
}
public class CreateSelectionCommand
{
public string SelectionName { get; set; }
}
Did I misread the docs or am I missing something?
Flattening is about mapping nested "complex" objects to properties on "higher" level i.e. in your case if CreateSelectionCommand had property Selection of type which had Name property it would be mapped to SelectionName in destination type (see this fiddle).
You can try to use prefixes by adding:
cfg.RecognizePrefixes("Selection");
to your configuration (see this fiddle) but I doubt that it is suitable option for convention based handling.
Also it seems that you can add custom name convention using ISourceToDestinationNameMapper and AddMemberConfiguration:
class TypeNamePrefixedSourceToDestinationNameMapper : ISourceToDestinationNameMapper
{
public MemberInfo GetMatchingMemberInfo(IGetTypeInfoMembers getTypeInfoMembers, TypeDetails typeInfo,
Type destType,
Type destMemberType, string nameToSearch)
{
return getTypeInfoMembers.GetMemberInfos(typeInfo)
.FirstOrDefault(mi => mi.Name == destType.Name + nameToSearch);
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddMemberConfiguration().AddName<TypeNamePrefixedSourceToDestinationNameMapper>();
// ...
}
At least it works in this simple case, see this fiddle.
Tried to search a lot for this but couldn't come up with an answer that works. Here's what I am trying to do:
I have Entity ObjectA that has a Navigation property ObjectB (not a Collection type, it's a virtual property that gets loaded via lazy loading). When I do a Where query for A, I want to also expand property B in the expression using another Expression that maps B.
Here's some code to demonstrate, question is in the ToObjectADto() function
public static Expression<Func<ObjectB, ObjectBDto>> ToObjectBDto()
{
return b => new ObjectBDto
{
Prop1 = b.Prop1,
Prop2 = b.Prop2;
};
}
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = /* How can I call the ToObjectBDto Expression here without re-writing it? */
};
}
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).Select(ToObjectADto());
I tried to create a compiled Expression:
private static _toBDtoCompiled = ToObjectBDto().Compile();
and then call it in ToObjectADto() like below but I get the API Error There is already an open DataReader associated error because it's doing it client side.
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = _toBDto().Invoke(a.ObjectB)
};
}
My recommendation would be to save yourself the work and leverage AutoMapper. The benefit here is that Automapper can feed off EF's IQueryable implementation via ProjectTo to build queries and populate DTO graphs.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ObjectA, ObjectADto>();
cfg.CreateMap<ObjectB, ObjectBDto>();
});
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).ProjectTo<ObjectADto>(config);
Any particular mapping that cannot be inferred between Object and DTO can be set up in the mapping. The benefit of ProjectTo vs. custom mapping is that it will build a related query without tripping out lazy load hits or risking triggering code that EF cannot translate into SQL. (One query to populate all relevant DTOs)
Automapper can assist with copying values from a DTO back into either a new entity or update an existing entity:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NewObjectADto, ObjectA>();
cfg.CreateMap<UpdateObjectADto, ObjectA>();
});
var mapper = config.CreateMapper();
New..
var objectA = mapper.Map<ObjectA>(dto);
_dbContext.ObjectAs.Add(objectA);
... or Update existing.
var objectA = _dbContext.ObjectAs.Single(x => x.ObjectAId == dto.ObjectAId);
mapper.Map(objectA, dto);
Where the DTO reflects either the data needed to create a new object, or the data that the client is allowed to update for updating the existing object. The goal being to keep update/add/delete operations as atomic as possible vs. passing large complex objects /w relatives to be updated all at once. I.e. actions like "AddObjectBToA" "RemoveObjectBFromA" etc. rather than resolving all operations via a single "UpdateObjectA".
It's a pity that C# doesn't handle compiling a lambda into an expression, where one expression calls another. Particularly since an expression tree can represent this case. But then EF Core 3 or higher won't look through invoke expressions anyway.
Automapper is probably easier. But if you don't want to use 3rd party code, you'll have to inline the expression yourself. Including replacing any ParameterExpression with the argument to the method.
public static R Invoke<T, R>(this Expression<Func<T, R>> expression, T argument) => throw new NotImplementedException();
// etc for expressions with more parameters
public class InlineVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Invoke"
&& node.Object == null
&& node.Arguments.Count >= 1
&& node.Arguments[0] is LambdaExpression expr)
return Visit(
new ReplacingExpressionVisitor(
expr.Parameters.ToArray(),
node.Arguments.Skip(1).ToArray())
.Visit(expr.Body)
);
return base.VisitMethodCall(node);
}
}
// usage;
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
var ToBorNotToB = ToObjectBDto();
Expression<Func<ObjectA, ObjectADto>> expr = a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = ToBorNotToB.Invoke(a.ObjectB)
};
return new InlineVisitor().VisitAndConvert(expr), "");
}
I would like to selectively ignore a property from a table.
I have an API which exposes the following methods.
public interface IReadService
{
FullDTO Get();
HeaderDTO[] GetList();
}
My data structure looks like so:
public ServiceDTO : ServiceHeaderDTO
{
public string LargeXMLData { get; set; }
}
public ServiceHeaderDTO
{
public int Id { get; set; }
public string Description { get; set; }
//.... Other properties
}
I have a few services which have similar issues, So I would like to be able to ignore the XML property in some cases, so I'm not using extra time to send a large string property which will be ignored.
Normally you might write something like this to hide a property
var entities = context.Services.Select(x =>
new Service { Id = Id, Description = Description, LargeXMLData = "" }).ToArray();
var dtos = this.AdaptToDTO(entities);
Now this would be fine if I had to do this in a single service, but when you have 20 services duplicating the logic it gets annoying.
I would like the be able to just say:
var entities = context.Services.Excluding(x => x.LargeXMLData).ToArray();
var dtos = this.AdaptToHeaderDTO(entities);
Edit: I'm not using automapper. Alot of our code has mappings which cannot translate to expressions. I do not want to have to specify maps
Is there a simple way I can exclude a property from a query? Without having to manually build maps.
Preferably a way which uses the existing mappings internal to EF which maps the entity to the db object
Normally you might write something like this to hide a property
var entities = context.Services.Select(x =>
new Service { Id = Id, Description = Description, LargeXMLData = "" })
If you can do that manually, it should be doable automatically using the exact same concept, with little reflection and Expression APIs.
But note that this woult work only for EF Core, since EF6 does not support projecting to entity types, like new Service { ... } here, and projecting to dynamic types at runtime is not trivial and also will break the DTO mapping.
With that being said, following is a sample implementation of the aforementioned concept:
public static partial class QueryableExtensions
{
public static IQueryable<T> Excluding<T>(this IQueryable<T> source, params Expression<Func<T, object>>[] excludeProperties)
{
var excludeMembers = excludeProperties
.Select(p => ExtractMember(p.Body).Name)
.ToList();
if (excludeMembers.Count == 0) return source;
// Build selector like (T e) => new T { Prop1 = e.Prop1, Prop2 = e.Prop2, ... }
// for each public property of T not included in the excludeMembers list,
// which then will be used as argument for LINQ Select
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = typeof(T).GetProperties()
.Where(p => p.CanWrite && !excludeMembers.Contains(p.Name))
.Select(p => Expression.Bind(p, Expression.MakeMemberAccess(parameter, p)));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
static MemberInfo ExtractMember(Expression source)
{
// Remove Convert if present (for value type properties cast to object)
if (source.NodeType == ExpressionType.Convert)
source = ((UnaryExpression)source).Operand;
return ((MemberExpression)source).Member;
}
}
The usage would be exactly as desired:
var entities = context.Services.Excluding(x => x.LargeXMLData).ToArray();
The problem with this though is that it will automatically "include" navigation properties and/or unmapped properties.
So it would be better to use EF model metadata instead of reflection. The problem is that currently EF Core does not provide a good public way of plugging into their infrastructure, or to get access to DbContext (thus Model) from IQueryble, so it has to be passed as argument to the custom method:
public static IQueryable<T> Excluding<T>(this IQueryable<T> source, DbContext context, params Expression<Func<T, object>>[] excludeProperties)
{
var excludeMembers = excludeProperties
.Select(p => ExtractMember(p.Body).Name)
.ToList();
if (excludeMembers.Count == 0) return source;
// Build selector like (T e) => new T { Prop1 = e.Prop1, Prop2 = e.Prop2, ... }
// for each property of T not included in the excludeMembers list,
// which then will be used as argument for LINQ Select
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = context.Model.FindEntityType(typeof(T)).GetProperties()
.Where(p => p.PropertyInfo != null && !excludeMembers.Contains(p.Name))
.Select(p => Expression.Bind(p.PropertyInfo, Expression.MakeMemberAccess(parameter, p.PropertyInfo)));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
which makes the usage not so elegant (but doing the job):
var entities = context.Services.Excluding(context, x => x.LargeXMLData).ToArray();
Now the only remaining potential problem are shadow properties, but they cannot be handled with projection, so this technique simply cannot be used for entities with shadow properties.
Finally, the pure EF Core alternative of the above is to put the LargeXMLData into separate single property "entity" and use table splitting to map it to the same table. Then you can use the regular Include method to include it where needed (by default it would be excluded).
I needed to double-check this before answering, but are you using Automapper or some other mapping provider for the ProjectTo implementation? Automapper's ProjectTo extension method requires a mapper configuration, so it may be that your mapping implementation is materializing the entities prematurely.
With Automapper, your example projecting to a DTO that does not contain the large XML field would result in a query to the database that does not return the large XML without needing any new "Exclude" method.
For instance, if I were to use:
var config = new MappingConfiguration<Service, ServiceHeaderDTO>();
var services = context.Services
.ProjectTo<ServiceHeaderDTO>(config)
.ToList();
The resulting SQL would not return the XMLData because ServiceHeaderDTO does not request it.
It is equivalent to doing:
var services = context.Services
.Select(x => new ServiceHeaderDTO
{
ServiceId = x.ServiceId,
// ... remaining fields, excluding the XML Data
}).ToList();
As long as I don't reference x.LargeXMLData, it will not be returned by my resulting query. Where you can run into big data coming back is if something like the following happens behind the scenes:
var services = context.Services
.ToList()
.Select(x => new ServiceHeaderDTO
{
ServiceId = x.ServiceId,
// ... remaining fields, excluding the XML Data
}).ToList();
That extra .ToList() call will materialize the complete Service entity to memory including the XMLData field. Now Automapper's ProjectTo does not work against IEnumerable, only IQueryable so it is unlikely that any query fed to it was doing this, but if you are using a home-grown mapping implementation where ProjectTo is materializing the entities before mapping, then I would strongly recommend using Automapper as it's IQueryable implementation avoids this problem for you automatically.
Edit: Tested with EF Core and Automapper just in case the behaviour changed, but it also excludes anything not referenced in the mapped DTO.
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)));
Using AutoMapper, is it possible to map only the changed properties from the View Model to the Domain Object?
The problem I am coming across is that if there are properties on the View Model that are not changed (null), then they are overwriting the Domain Objects and getting persisted to the database.
Yes, it can be done, but you have to specify when to skip the destination property using Condition() in your mapping configuration.
Here's an example. Consider the following classes:
public class Source
{
public string Text { get; set; }
public bool Map { get; set; }
}
public class Destination
{
public string Text { get; set; }
}
The first map won't overwrite destination.Text, but the second will.
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Text, opt => opt.Condition(src => src.Map));
var source = new Source { Text = "Do not map", Map = false };
var destination = new Destination { Text = "Leave me alone" };
Mapper.Map(source, destination);
source.Map = true;
var destination2 = new Destination { Text = "I'll be overwritten" };
Mapper.Map(source, destination2);
#Matthew Steven Monkan is correct, but seems AutoMapper changed API. I will put new one for others refer.
public static IMappingExpression<TSource, TDestination> MapOnlyIfChanged<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map)
{
map.ForAllMembers(source =>
{
source.Condition((sourceObject, destObject, sourceProperty, destProperty) =>
{
if (sourceProperty == null)
return !(destProperty == null);
return !sourceProperty.Equals(destProperty);
});
});
return map;
}
that's all
For Automapper version < 6.0
Yes; I wrote this extension method to map only dirty values from a model to Entity Framework.
public static IMappingExpression<TSource, TDestination> MapOnlyIfDirty<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map)
{
map.ForAllMembers(source =>
{
source.Condition(resolutionContext =>
{
if (resolutionContext.SourceValue == null)
return !(resolutionContext.DestinationValue == null);
return !resolutionContext.SourceValue.Equals(resolutionContext.DestinationValue);
});
});
return map;
}
Example:
Mapper.CreateMap<Model, Domain>().MapOnlyIfDirty();
No.
This is exactly one of the reasons you never map from viewmodel to domain model. Domain/business model changes are too important for a tool to handle.
Automapper is not designed to handle this scenario:
AutoMapper works because it enforces a convention. It assumes that
your destination types are a subset of the source type. It assumes
that everything on your destination type is meant to be mapped. It
assumes that the destination member names follow the exact name of the
source type. It assumes that you want to flatten complex models into
simple ones.
From: https://jimmybogard.com/automappers-design-philosophy/
Manually:
customer.LastName = viewModel.LastName
Changing business state is too important to do otherwise.