Is it possible to create a default destination mapping in AutoMapper ?
Source classes:
class SourceA {
public string X { get; set; }
}
class SourceB {
public string Y { get; set; }
}
Destination classes:
class DestBase {
public List<string> Z { get; set; }
}
class DestA : DestBase {
public string X { get; set; }
}
class DestB : DestBase {
public string Y { get; set; }
}
And the mapping configuration contains the following:
cfg.CreateMap<SourceA, DestA>()
.ForMember(dest => dest.Z, src => src.MapFrom(s => null));
cfg.CreateMap<SourceB, DestB>()
.ForMember(dest => dest.Z, src => src.MapFrom(s => null));
Is it possible to create a default mapping for all destination classes inheriting the DestBase to avoid the repeated .ForMember(...) lines ?
eg. something like:
cfg.CreateMap<object, DestBase>
.ForMember(dest => dest.Z, src => src.MapFrom(s => new List<string>()));
In principle yes, with the Include method, but there is a caveat.
If you define a map from source type object, this map would match all types. Maybe you can introduce an interface ISource for the source types that should be affected by this mapping.
So it could look like this:
class SourceA : ISource {
public string X { get; set; }
}
class SourceB : ISource {
public string Y { get; set; }
}
cfg.CreateMap<ISource, DestBase>
.Include<SourceA, DestA>
.Include<SourceB, DestB>
.Include<SourceC, DestC>
.ForMember(dest => dest.Z, , o => o.MapFrom(src => new List<string>()));
cfg.CreateMap<SourceA, DestA>()
.ForMember(dest => dest.X, o => o.MapFrom(src => src.X));
cfg.CreateMap<SourceB, DestB>()
.ForMember(dest => dest.Y, o => o.MapFrom(src => src.Y));
// still need to create a map even if no additional properties are to be mapped
cfg.CreateMap<SourceC, DestC>();
Note that you still need to create maps for all included types, even if there are no additional properties to map.
Related
The context around this is that I'd like to map a dynamic dapper result to an object and that object has a property which has an interface type:
public class TargetModel
{
public int Id { get; set; }
public IAddress AbstractAddress { get; set; }
}
The dynamic to object bit is straight forward but I'm unable to configure AutoMapper to tell it how to handle the interface.
dynamic sourceModel = new ExpandoObject();
// flat model - id should map to TargetModel, Address01 will map to a nested type Address on TargetModel
sourceModel.Id = 1;
sourceModel.Address01 = "address01";
// for debugging purposes address maps ok from dynamic
Address address = Mapper.Map<Address>(sourceModel);
// this maps, but AbstractAddress is null - I need to config AutoMapper to understand how to map IAddress to Address
TargetModel target = Mapper.Map<TargetModel>(sourceModel);
I've tried to tell it how to deal with the property:
CreateMap<ExpandoObject, TargetModel>()
.ForMember(y => y.AbstractAddress, opts => opts.MapFrom(f => f));
Which fails with:
System.ArgumentException: Cannot create an instance of interface type
So I attempt to hint at the concrete:
CreateMap<IAddress, Address>().As<Address>();
Which doesn't resolve the issue and the exception remains.
I've looked at the following questions/concepts and tried various configuration options but haven't been able to get the map working:
AutoMapper: Mapping objects with interface properties
https://dotnetfiddle.net/fPQxWx
This is trying to map from a string in the ExpandoObject to an IAddress in your target type. Obviously it can't create an instance of IAddress to fill so you have to fulfill that yourself in the mapping.
If your models look like this:
public class TargetModel
{
public int Id { get; set; }
public IAddress AbstractAddress { get; set; }
}
public interface IAddress
{
string Address01 { get; set; }
}
public class Address : IAddress
{
public string Address01 { get; set; }
}
Then your config and setup can look like this:
public void MappingTests()
{
dynamic sourceModel = new ExpandoObject();
// flat model - id should map to TargetModel, Address01 will map to a nested type Address on TargetModel
sourceModel.Id = 1;
sourceModel.Address01 = "address01";
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ExpandoObject, TargetModel>()
.ForMember(dest => dest.AbstractAddress, opt => opt.MapFrom(src => new Address() { Address01 = src.First(kvp => kvp.Key == "Address01").Value.ToString() }))
.ForMember(destinationMember => destinationMember.Id, opt => opt.MapFrom(src => src.First(kvp => kvp.Key == "Id").Value));
});
TargetModel target = Mapper.Map<TargetModel>(sourceModel);
}
I just used it as an IEnumerable<KeyValuePair<string, object>>, but you can also treat the expando object like a dictionary by casting.
cfg.CreateMap<ExpandoObject, TargetModel>()
.ForMember(dest => dest.AbstractAddress, opt => opt.MapFrom(src => new Address() { Address01 = ((IDictionary<string, object>)src)["Address01"].ToString() }))
.ForMember(destinationMember => destinationMember.Id, opt => opt.MapFrom(src => ((IDictionary<string, object>)src)["Id"]));
I'm using automapper to map my entities. But entities have different structure.
Source:
public class SourceEntity
{
public string Name { get; set; }
public Type Type { get; set; }
public Communication SelectedCommunication { get; set; }
}
public enum Type
{
Type1=1,
Typ2
}
[Flags]
public enum Communication
{
Phone =1,
Email =2,
Post =4
}
Also I have HasFlag() extension method that will return true if flag is selected.
Destination entity:
public class DestinationEntity
{
public string Name { get; set; }
public bool Type1_PhoneSelected { get; set; }
public bool Type1_EmailSelected { get; set; }
public bool Type1_PostSelected { get; set; }
public bool Type2_PhoneSelected { get; set; }
public bool Type2_EmailSelected { get; set; }
public bool Type2_PostSelected { get; set; }
}
My map:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(v => v.Name, opt => opt.MapFrom(i => i.Name));
But I can't figure out the best way to map Types properties.
Is it possible to map it without typing something like:
.ForMemeber(v=>v.Test1_PhoneSelected, opt=>opt.MapFrom(i=>i.SelectedCommunication.HasFlag(Communication.Phone)))
.ForMemeber(v=>v.Test2_PhoneSelected, opt=>opt.MapFrom(i=>i.SelectedCommunication.HasFlag(Communication.Phone)))
For each of this properties.
Is there any way to map by naming convention?
Or any other ways?
You can use custom value resolvers
Although AutoMapper covers quite a few destination member mapping
scenarios, there are the 1 to 5% of destination values that need a
little help in resolving. Many times, this custom value resolution
logic is domain logic that can go straight on our domain. However, if
this logic pertains only to the mapping operation, it would clutter
our source types with unnecessary behavior. In these cases,
AutoMapper allows for configuring custom value resolvers for
destination members.
Example of custom value resolver:
public class YourCustomResolver
: IMemberValueResolver<object, object, Communication, bool>
{
private Communication _communication;
public YourCustomResolver(
Communication communication)
{
}
public bool Resolve(
object source,
object destination,
Communication sourceMember,
bool destMember,
ResolutionContext context)
{
return _communication == sourceMember;
}
}
Your mapping will look like this:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.Type1_PhoneSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Phone), src => src.SelectedCommunication))
.ForMember(dest => dest.Type1_EmailSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Email), src => src.SelectedCommunication))
.ForMember(dest => dest.Type1_PostSelected , opt => opt.ResolveUsing(new YourCustomResolver(Communication.Post) , src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_PhoneSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Phone), src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_EmailSelected, opt => opt.ResolveUsing(new YourCustomResolver(Communication.Email), src => src.SelectedCommunication))
.ForMember(dest => dest.Type2_PostSelected , opt => opt.ResolveUsing(new YourCustomResolver(Communication.Post) , src => src.SelectedCommunication));
I want to create a map for a somewhat complex entity model to a flattened view Model
My entity model is like so
cbItems
has many cbItemsContent
has many cbRegulators
so my viewmodels are like so
for cbItems:
public class ItemViewModel
{
public ItemViewModel()
{
this.CbItemsContents = new HashSet<ItemContentViewModel>();
}
public int ItemID { get; set; }
......
public virtual ICollection<ItemContentViewModel> CbItemsContents { get; set; }
}
}
for cbItemsContent:
public class ItemContentViewModel
{
public int ItemContentID { get; set; }
public int ItemID { get; set; }
....
public ItemContentRegulatorsViewModel RegulatedBy { get; set; }
}
}
for cbRegulators:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IEnumerable<int> RegulatorIds { get; set; }
}
}
I had hoped it would be as easy as this:
config.CreateMap<CbItem, ItemViewModel>();
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy.ItemContentId,
m => m.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatedBy.RegulatorIds,
n => n.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
from teh following query:
ItemViewModel item =
_context.cbItems.Where(u => u.ItemId = id)
.ProjectTo<ItemViewModel>()
.first();
But this results in an error:
Expression 'dest => dest.RegulatedBy.ItemContentId' must resolve to
top-level member and not any child object's properties. Use a custom
resolver on the child type or the AfterMap option instead. Parameter
name: lambdaExpression
HOw can I achieve my desired model layout?
You have to map ItemContentRegulatorsViewModel, then you don't need to set it from the ViewModel above.
#Rabban probably means something like this:
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy, o => o.MapFrom(src => src));
config.CreateMap<CbItemsContent, ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.ItemContentId, o => o.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatorIds, o => o.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
In the application I am busy writing, all my mapping destination objects derive from a base class like this:
public class CatalogObject<TObject>
{
TObject InnerObject { get; set; }
}
public class CatalogTable : CatalogObject<table>
{
public string Name { get; set; }
public int ObjectId { get; set; }
}
Now, after mapping a table object to a CatalogTable object, I want the InnerObject property of that destination to be a reference to the source table object.
You could do it with a Custom Resolver:
Mapper.CreateMap<Table, CatalogTable>()
.ForMember(dest => dest.InnerObject,
opt => opt.ResolveUsing<InnerObjectResolver>());
Where the resolver would look something like:
public class InnerObjectResolver : ValueResolver<Table, Table>
{
protected override Table ResolveCore(Table source)
{
return source;
}
}
Full details can be found in the custom resolver documentation.
You might also be able to do it directly, but I haven't tried that. Something like this maybe:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.InnerObject, opt => opt.MapFrom(src => src));
I have something like this
public class ProductViewModel
{
public int SelectedProductId { get; set; }
public string ProductName {get; set;}
public int Qty {get; set;}
public List<SelectListItem> Products { get; set};
}
I have a domain like this
public class Product
{
public int ProductId {get; set;}
public string ProductName {get; set;}
public int Qty {get; set;}
}
public class Store
{
public Product() {get; set;}
}
Now I need to do the mapping.
// in my controller
var result = Mapper.Map<ProductViewModel, Store>(Product);
this won't bind anything since it can't figure out how to put the ProductId in since it
is
Store.Product.ProductId;
My map is like this
Mapper.CreateMap<ProductViewModel, Store>().ForMember(dest => dest.Product.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
I get this error
Expression 'dest =>
Convert(dest.Product.SelectedProductId' must
resolve to top-level member. Parameter
name: lambdaExpression
I am unsure how to do this.
To Map nested structures, you just need to create a new object in the MapFrom argument.
Example
Mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.MyNestedType, o => o.MapFrom(t => new NestedType { Id = t.Id }));
Mapper.AssertConfigurationIsValid();
Test Code:
var source = new Source { Id = 5 };
var destination = Mapper.Map<Source, Destination>(source);
Classes:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public NestedType MyNestedType { get; set; }
}
public class NestedType
{
public int Id { get; set; }
}
You can use Resolver.
Create a resolver class like that :
class StoreResolver : ValueResolver<Store, int>
{
protected override int ResolveCore(Store store)
{
return store.Product.ProductId;
}
}
And use it like that :
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.SelectedProductId, opt => opt.ResolveUsing<StoreResolver >());
Hope it will help ...
The error your getting is because you cannot declare mapping declarations more than one level deep in your object graph.
Because you've only posted one property its hard for me to give you the codes that will make this work. One option is to change your viewmodel property to MyTestTestId and the conventions will automatically pick up on that.
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src));
Mapper.CreateMap<ProductviewModel, Product>()
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.