I've the following code:
using System;
using AutoMapper;
namespace AutoMapperPlayground
{
public class Program
{
public static void Main()
{
var derived = new Derived {
Title = "a",
Base = new Base {
Id = 1,
Test = "b"
}
};
Mapper.CreateMap<Derived, DerivedDTO>();
var derivedDTO = Mapper.Map<DerivedDTO>(derived);
Console.WriteLine("{0},{1},{2}", derivedDTO.Test, derivedDTO.Id, derivedDTO.Title);
}
}
public class Base
{
public int Id { get; set; }
public string Test {get; set; }
}
public class Derived
{
public Base Base {get; set; }
public string Title {get; set; }
}
public class BaseDTO
{
public int Id { get; set; }
public string Test {get; set; }
}
public class DerivedDTO : BaseDTO
{
public string Title {get; set; }
}
}
The output is ,0,a.
Would it be possible to have id and title properties populated from Base?
Sample .net fiddle
Thanks
Flattening is one of the core concepts of AutoMapper, and is done by convention; your DTO property names need to be prefixed with the property name of the composed object in your source type:
public class DerivedDTO
{
public int BaseId { get; set; }
public string BaseTest { get; set; }
public string Title { get; set; }
}
However, if you want to retain your existing structure (DerivedDTO inheriting from BaseDTO), you would need to define those mappings manually:
Mapper.CreateMap<Derived, DerivedDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Base.Id)),
.ForMember(dest => dest.Test, opt => opt.MapFrom(src => src.Base.Test));
Or you could create a mapping from Base to DerivedDTO, and include that in your Derived mapping:
Mapper.CreateMap<Base, DerivedDTO>(); // Map Id and Test
Mapper.CreateMap<Derived, DerivedDTO>() // Map Title
.IncludeMembers(src => src.Base); // Reuse above mapping to include Base Id and Test
Related
I have tried using a custom converted
CreateMap<ChildB, string>().ConvertUsing(src => src.Name);
But I run into the scenario when I want to sometimes get the string like above or sometimes I want to just get the Guid:
CreateMap<ChildB, Guid>().ConvertUsing(src => src.Id);
It seems to throw and error as it always converts the Name. The Objects are like this:
public class ParentA
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<ChildB>? {get;set;}
...
}
public class ChildB
{
public Guid Id {get;set;}
public string Name {get;set;}
...
}
public class ParentADTO1
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<string> ChildNames{get;set;}
...
}
public class ParentADTO2
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<Guid> ChildIds {get;set;}
...
}
So the question is, can I use the CreateMap function like so:
CreateMap<ParentA,ParentADTO1>()
...
.ForMember(ent => ent.ChildNames, opt => opt.MapFrom(???))
CreateMap<ParentA,ParentADTO2>()
...
.ForMember(ent => ent.ChildIds, opt => opt.MapFrom(???))
Your help is greatly appreciated!!
Thanks
Jon
You can set up the mapping configuration like this (I suppose the property is named Children):
public class ParentA
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<ChildB> Children {get;set;}
// ...
}
CreateMap<ParentA,ParentADTO1>()
// ...
.ForMember(ent => ent.ChildNames, opt => opt.MapFrom(x => x.Children.Select(y => y.Name).ToArray()))
CreateMap<ParentA,ParentADTO2>()
// ...
.ForMember(ent => ent.ChildIds, opt => opt.MapFrom(x => x.Children.Select(y => y.Id).ToArray()))
#Markus answer is certainly valid and is an excellent solution. FWIW, here is another approach you could by using the built-in AutoMapper's Flattening pattern. Simply add a Get[PropertyName] method to the source class to combine the child objects in whatever way you want it. Here is the complete example:
public class Parent
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
public ICollection<string> GetChildNames()
{
return Children.Select(x => x.Name).ToArray();
}
public ICollection<Guid> GetChildIds()
{
return Children.Select(x => x.Id).ToArray();
}
}
public class Child
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ParentWithChildNames
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<string> ChildNames { get; set; }
}
public class ParentWithChildIds
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Guid> ChildIds { get; set; }
}
//No need to map the member, you could use Get[PropertyName] approach to automatically map it
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Parent, ParentWithChildIds>();
cfg.CreateMap<Parent, ParentWithChildNames>();
});
var mapper = config.CreateMapper();
I'm pretty new using AutoMapper and i run into an issue
I have a model like this.
public class StepUp {
public string Example {get;set;}
public string Example2 {get;set;}
public decimal? auxValue { get;set; }
}
But i have two ViewModels as destination
public class NuevoStepUpViewModel()
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
}
public class CargaDatosElectricos {
public CargaDatosElectricos(bool tieneAuxiliar)
{
TieneAuxiliar = tieneAuxiliar;
}
public readonly bool TieneAuxiliar;
public string Example2 { get; set; }
}
I think some like this:
CreateMap<StepUp,NuevoStepUpViewModel()
.ForMember(x => x.TieneAuxiliar, x => x.MapFrom(c => c.auxValue.HasValue))
.ForMember(x => x.Example, x => x.MapFrom(c => c.Example))
.ForMember(x => x.CargaDatosElectricos.Example2, x => x.MapFrom(c => c.Example2))
.BeforeMap((x,y) => {
x.CargaDatosElectricos = new CargaDatosElectricos(c.auxValue.HasValue);
});
But i'm getting
Expression 'x => x.CargaDatosElectricos.Example2' must resolve to
top-level member and not any child object's properties
How should i create my mapper configuration to do this type of mapping?
There are some errors on your code. You could configure better your mapping using the AfterMap scope instead of BeforeMap to provide a complex configuration. (I am not sure but I think the) AutoMapper will not instance a property where the type is a class. So, you have to do it on the construtor of the destination class (VIewModel) or do it on AfterMap.
The TieneAuxiliar property will not allow you to set a value when it is readonly, so, you will not able to configure a map to this property. I change it to a public classic property.
See the working sample here:
https://dotnetfiddle.net/HSyUVv
using System;
using AutoMapper;
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<StepUp, NuevoStepUpViewModel>()
.ForMember(vm => vm.TieneAuxiliar, opt => opt.MapFrom(e => e.auxValue.HasValue))
.ForMember(vm => vm.Example, opt => opt.MapFrom(e => e.Example))
.AfterMap((e, vm) =>
{
vm.CargaDatosElectricos.Example2 = e.Example2;
});
});
var mapper = config.CreateMapper();
var stepUp = new StepUp()
{
Example = "Example 1",
Example2 = "Example 2",
auxValue = 10m
};
var viewModel = mapper.Map<StepUp, NuevoStepUpViewModel>(stepUp);
Console.WriteLine("SteUp was converted to ViewModel");
Console.WriteLine("TieneAuxiliar: {0}", viewModel.TieneAuxiliar);
Console.WriteLine("Example: {0}", viewModel.Example);
Console.WriteLine("CargaDatosElectricos.TieneAuxiliar: {0}", viewModel.CargaDatosElectricos.TieneAuxiliar);
Console.WriteLine("CargaDatosElectricos.Exemple2: {0}", viewModel.CargaDatosElectricos.Example2);
}
public class StepUp
{
public string Example { get; set; }
public string Example2 { get; set; }
public decimal? auxValue { get; set; }
}
public class NuevoStepUpViewModel
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
public NuevoStepUpViewModel()
{
this.CargaDatosElectricos = new CargaDatosElectricos();
}
}
public class CargaDatosElectricos
{
public CargaDatosElectricos()
{
}
public bool TieneAuxiliar { get; set; }
public string Example2 { get; set; }
}
}
I want to flatten my entity framework model data to dto for my ASP.NET Core REST Web Service.
My entity classes (simplified) look like:
public class DeliveryNoteEntity
{
public string VehicleNo { get; set; }
public int NotMapped { get; set; }
public List<DeliveryNoteSignature> Signatures { get; set; }
}
public class DeliveryNoteSignature
{
public string Signature { get; set; }
public SignedByRole SignedBy { get; set; }
}
public enum SignedByRole
{
Driver = 1,
Recipient = 2
}
My dto looks like
public class DeliveryNoteDto
{
public int Id { get; set; }
public string VehicleNo { get; set; }
public string DriverSignature { get; set; }
public string RecipientSignature { get; set; }
}
Then I can fill my entity like this
var sourceEntity = new DeliveryNoteEntity
{
VehicleNo = "VehicleNo20",
Signatures = new List<DeliveryNoteSignature> { new DeliveryNoteSignature { Signature = "Driver Mr. Pitz", SignedBy = SignedByRole.Driver} }
};
and map to a dto:
Mapper.Initialize(cfg => cfg.CreateMap<DeliveryNoteEntity, DeliveryNoteDto>()
.ForMember(dest => dest.DriverSignature, opt => opt.MapFrom(src => src.Signatures
.Where(x => x.SignedBy == SignedByRole.Driver)
.Select(x => x.Signature)
.FirstOrDefault()))
.ForMember(dest => dest.RecipientSignature, opt => opt.MapFrom(src => src.Signatures
.Where(x => x.SignedBy == SignedByRole.Recipient)
.Select(x => x.Signature)
.FirstOrDefault()))
.ReverseMap()
);
var dto = Mapper.Map<DeliveryNoteDto>(sourceEntity);
So my question is: how can I do a reverse mapping from dto to entity when it comes back to my service? So by hand I would do something like this:
var entityToSave = new DeliveryNoteEntity()
{
VehicleNo = dto.VehicleNo,
Signatures = new List<DeliveryNoteSignature>
{
new DeliveryNoteSignature {SignedBy = SignedByRole.Driver, Signature = dto.DriverSignature},
new DeliveryNoteSignature {SignedBy = SignedByRole.Recipient, Signature = dto.RecipientSignature}
}
};
It there any way to do it with AutoMapper?
EDIT: my real question is how can I do a mapping from XXXSignature properties in my dto to a list in my entity?
If you use AutoMapper's naming convention, ReverseMap is also done for you.
class CustomerClass
{
public string Name {get; set;}
public string Surname {get;set;}
}
class MainClass
{
public CustomerClass Customer {get; set;}
}
class MainClassDto
{
public string CustomerName {get; set;}
public string CustomerSurname {get; set;}
}
For this example you do not need to make any configuration for mapping and/or reverse mapping. AutoMapper handles it with its default configuration
I ended up doing that by code, so I can clearly detect and define add, delete and update operations. So I use AutoMapper only one way: entity -> dto.
This is my class which holds database data:
public partial class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; }
// other database properties
public virtual ICollection<GroupActionPermission> GroupActionPermissions { get; set; }
}
And that's my dto's:
public class PermissionGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<GroupActionPermissionDTO> ActionPermissions { get; set; }
}
public class GroupActionPermissionDTO
{
public int Id { get; set; }
public int GroupId { get; set; }
public int PermissionActionId { get; set; }
public PermissionGroupDTO Group { get; set; }
}
Now, I am making mapping:
public IEnumerable<PermissionGroupDTO> GetGroups()
{
return OnConnect<IEnumerable<PermissionGroupDTO>>(db =>
{
return db.PermissionGroups
.Include(i => i.GroupActionPermissions)
.ProjectTo<PermissionGroupDTO>()
.ToList();
});
}
And I am getting collection of PermissionGroupDTO which should contains collection of GroupActionPermissionDTO, but that collection stays null. Is there something wrong with my code? I am afraid that automapper can map collections from foreign keys.
Also, thats my automapper initializer:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>();
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
I believe the reason is desribed here http://docs.automapper.org/en/stable/Queryable-Extensions.html
Note that for this feature to work, all type conversions must be explicitly handled in your Mapping.
So that means you should manually configure the mapping:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>()
.ForMember(dto => dto.ActionPermissions , conf => conf.MapFrom(ol => ol.GroupActionPermissions )));;
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
BTW, note that fields are named differently: GroupActionPermissions vs. ActionPermissions. This is also the reason why automapper doesn't map it automatically and then you should use the manual configuration I wrote.
I've got a source model defined as
public class SourceRoot
{
...
public Organisation Organisation { get; set; }
...
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
and a destination defined as:
public class DestinationRoot
{
...
public Organisation Organisation { get; set; }
public ContributesTo ContributesTo { get; set; }
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
}
public class ContributesTo
{
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
I want to map from the SourceRoot to the DestinationRoot add copy from the source Organisation to the destination Organisation AND ContributesTo.
I have the following configuration for AutoMapper:
public static class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyProfile>();
});
return config;
}
}
public class MyProfile : Profile
{
protected override void Configure()
{
this.CreateMap<SourceRoot, DestinationRoot>();
this.CreateMap<Source.Organisation, Destination.Organisation>();
this.CreateMap<Source.Organisation, Destination.ContributesTo>();
}
}
Using this current profile the Organisation gets mapped but the ContributesTo comes out as null.
Note that I'm using version 4.2 of AutoMapper where the static methods have been deprecated so trying to steer away from that. Normally I would do:
Mapper.CreateMap<SourceRoot, DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom( s=> Mapper.Map<ContributesTo>(s.Organisation)));
But this is not advised anymore (referencing the static methods). Is there an alternative way of doing this?
Thanks
Just add mapping for ContributesTo destination member:
protected override void Configure ()
{
CreateMap<Source.SourceRoot, Destination.DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom(s => s.Organisation));
CreateMap<Source.Organisation, Destination.Organisation>();
CreateMap<Source.Organisation, Destination.ContributesTo>();
}
Otherwise Automapper finds that both source and destinaton roots have property Organisation and it maps only this property. Automapper cannot understand that it should use one property of source to map several properties of destination (which do not match by name). Note that you don't need to specify mapping for Organisation member, because it matches property name in destination object.