AutoMapper - Flattening/Unflattening to list - c#

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.

Related

Mapping CHILD collection to a separate DTO (asp net core API automapper projection)

my first StackOverflow question so please bear with me, and thank you for your help in advance! :)
How on earth can I get my asp.net core controller to respond with the DTO of the child collection within the DTO of the parent response? I need two separate DTOs because of some business logic constraints that call for this many-to-many relationship situation.
Tried automapper and spent the last two days of my life researching this to no avail.
I tried the following in my Controller but always get an empty child collection. I can get the child collection to display if I return the entity class which is not great with many to many relationships.
I want to end up with JSON that looks like...
[
{ prop : ..,
prop: ..,
collection[
{
prop:..,
prop:..
}
]
}
]
This is what I have in my controller:
public ActionResult<IEnumerable<LogEntryDto>> GetAllEntries()
{
var entryList = _context.Entries.ToList();
return Ok(_mapper.Map<IEnumerable<RiskGetDto>>(entryList));
}
My Automapper profile classes contains simple mapping between the entities and DTOs
CreateMap<LogEntry, LogEntryDto>();
CreateMap<Tag, TagDto>();
I have a the following class
public class LogEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Tag> Tags { get; set; } = new List<Tag>();
}
And another
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public List<LogEntry> LogEntries{ get; set; } = new List<LogEntry>();
}
And the following DTOs for each class
public class LogEntryDto
{
public string Name { get; set; }
public string Description { get; set; }
public List<Tag> Tags { get; set; } = new List<Tag>();
}
and...
public class TagDto
{
public string Name { get; set; }
public string Value { get; set; }
}
Thank you for your help.
Ok, just in case someone will be helped by this fix. My silly mistake was driven by my lack of lambda expression mileage(!!);
I needed to retrieve the full set of properties in the GetAllEntries() method in the service class like so (notice the ToList() on the Tags collection):
public List<Risk> GetAllEntries()
{
var entryList = _context.Entries.Select(e=> new Risk
{
Id = e.Id,
RiskName = e.RiskName,
RiskDescription = e.RiskDescription,
---->>> **Tags = e.Tags.ToList()**
}).ToList();
return entryList;
}
This is then passed to the controller and mapped like so:
[HttpGet]
public ActionResult<List<LogEntryDto>> GetAllEntries()
{
var entries = _entryService.GetAllEntries();
return Ok(_mapper.Map<List<LogEntryDto>>(entries));
}
And then let AutoMapper do its magic with the simple mapping profile...:
public EntriesProfile()
{
CreateMap<LogEntry, LogEntryDto>()
.ForMember
(dto => dto.EntryId,
dbEntity => dbEntity.MapFrom(src => ....)
.ReverseMap();
CreateMap<Tag, TagDto>()
.ForMember
(dto => dto.Name,
dbEntity => dbEntity.MapFrom(src => src.Name))
.ForMember
(dto => dto.Value,
dbEntity => dbEntity.MapFrom(src => src.Value))
.ReverseMap();
}

Automapper include all properties from parent class

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

Map components of DTO which are DTOs as well

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.

How to use Automapper to map an object to an interface at a different level?

I'm new to Automapper and am trying to map the data from an object in a list to an interface in a list. Add to that the lists are in different levels of the containing objects:
// class definitions
public class MyViewModel
{
public int ViewModelId { get; set; }
public IList<ViewItem> Page1Selections { get; set; }
public IList<ViewItem> Page2Selections { get; set; }
}
public class ViewItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
public class MyDbModel
{
public int DbModelId { get; set; }
public IPageSelection Page1Selections { get; set; }
public IPageSelection Page2Selections { get; set; }
}
public interface IPageSelection
{
int PageNumber { get; set; }
IList<IMyDbItem> PageSelections { get; set; }
}
public class PageSelection : IPageSelection
{
public int PageNumber { get; set; }
public IList<IMyDbItem> PageSelections { get; set; }
}
public interface IMyDbItem
{
int ItemId { get; set; }
bool IsSelected { get; set; }
}
public class MyDbItem : IMyDbItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
// mapping code
MyViewModel myVm = new MyViewModel();
myVm.ViewModelId = 123;
myVm.Page1Selections = new List<ViewItem>();
myVm.Page1Selections.Add(new ViewItem() { ItemId = 1, IsSelected = true });
myVm.Page2Selections = new List<ViewItem>();
myVm.Page2Selections.Add(new ViewItem() { ItemId = 2, IsSelected = true });
Mapper.Initialize(cfg =>
{
cfg.BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
cfg.CreateMap<MyViewModel, MyDbModel>();
cfg.CreateMap<ViewItem, IMyDbItem>();
});
MyDbModel myDb = new MyDbModel();
myDb = Mapper.Map<MyDbModel>(myVm); //<== exception here
Sorry for the long attachment. But I'm fighting both an object to interface and hierarchical mismatch mapping problem.
Here's one way you could accomplish this:
cfg.CreateMap<MyViewModel, MyDbModel>()
.ForMember(dest => dest.DbModelId, opt => opt.MapFrom(src => src.ViewModelId));
cfg.CreateMap<ViewItem, IMyDbItem>()
.ConstructUsing((ViewItem src) => new MyDbItem());
cfg.CreateMap<IList<ViewItem>, IPageSelection>()
.ConstructUsing((IList<ViewItem> src) => new PageSelection())
.ForMember(dest => dest.PageSelections, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.PageNumber, opt => opt.Ignore());
The error you were getting is because no mapping existed from IList<ViewItem> to IPageSelection. I've created that mapping above.
An important bit is the ConstructUsing calls. I added those here because I assumed you wanted to use the concrete types that implement the interfaces you're mapping to. Otherwise, AutoMapper will create a proxy class that implements the destination interface.
The part that enables mapping to the IPageSelection.PageSelections from IList<ViewItem> is the opt.MapFrom(src => src) part. This essentially tells AutoMapper to map from the list to the inner PageSelections property, ultimately using the mapping from ViewItem to IMyDbItem.
Example: https://dotnetfiddle.net/DW9dfO

AutoMapper: map DTO back to domain object with child objects [duplicate]

I've been trying to use AutoMapper to save some time going from my DTOs to my domain objects, but I'm having trouble configuring the map so that it works, and I'm beginning to wonder if AutoMapper might be the wrong tool for the job.
Consider this example of domain objects (one entity and one value):
public class Person
{
public string Name { get; set; }
public StreetAddress Address { get; set; }
}
public class StreetAddress
{
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
My DTO (from a Linq-to-SQL object) is coming out looking roughly like this:
public class PersonDTO
{
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
I'd like to be able to do this in my repository:
return Mapper.Map<PersonDTO, Person>(result);
I've tried configuring AutoMapper every way I can figure, but I keep getting the generic Missing type map configuration or unsupported mapping error, with no details to tell me where I'm failing.
I've tried a number of different configurations, but here are a few:
Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));
and
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
.ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
.ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));
I've read that flattening objects with AutoMapper is easy, but unflattening them isn't easy...or even possible. Can anyone tell me whether I'm trying to do the impossible, and if not what I'm doing wrong?
Note that my actual objects are a little more complicated, so it's possible I'm leaving out info that is the key to the error...if what I'm doing looks right I can provide more info or start simplifying my objects for testing.
This also seems to work for me:
Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));
Basically, create a mapping from the dto to both objects, and then use it as the source for the child object.
Can't post a comment, so posting an answer. I guess there were some changes in AutoMapper implementation so answer https://stackoverflow.com/a/5154321/2164198 proposed by HansoS is no longer compilable. Though there is another method that can be used in such scenarios - ResolveUsing:
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
In addition to sydneyos answer and according to Trevor de Koekkoek comment, two way mapping is possible this way
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
public class PersonViewModel
{
public string Name { get; set; }
public string AddressStreet { get; set; }
public string AddressCity { get; set; }
public string AddressState { get; set; }
}
Automapper mappings
Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));
If you implement NameOf class, you can get rid of prefix magic string
Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));
EDIT: In C# 6
Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));
use https://github.com/omuleanu/ValueInjecter, it does flattening and unflattening, and anything else you need, there is an asp.net mvc sample application in the download where all the features are demonstrated (also unit tests)
This might be late but you can solve this by using lambda expressions to create the object like this:
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
I have another solution. The main idea is that AutoMapper know how to flatten nested objects when you name properly properties in flattened object: adding nested object property name as a prefix. For your case Address is prefix:
public class PersonDTO
{
public string Name { get; set; }
public string AddressCity { get; set; }
public string AddressState { get; set; }
...
}
So creating familiar mapping from nested to flattened and then using ReverseMap method allows AutomMapper to understand how to unflatten nested object.
CreateMap<Person, PersonDTO>()
.ReverseMap();
That's all!
I'm using this
public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
var prefix = opt.DestinationMember.Name;
var memberProps = typeof(TMember).GetProperties();
var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
.Select(sourceProp => new
{
SourceProp = sourceProp,
MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
})
.Where(x => x.MemberProp != null);
var parameter = Expression.Parameter(typeof(TSource));
var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
var resolver = Expression.Lambda<Func<TSource, TMember>>(
Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
parameter);
opt.ResolveUsing(resolver.Compile());
}
Configuration
new MapperConfiguration(cfg =>
{
cfg.CreateMap<Person, PersonDTO>();
cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});
Models
public class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
}
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Following AutoMapper flattening conventions
public class PersonDTO
{
public string Name { get; set; }
public string HomeAddressLine1 { get; set; }
public string HomeAddressLine2 { get; set; }
public string HomeAddressCity { get; set; }
public string HomeAddressState { get; set; }
public string HomeAddressZipCode { get; set; }
}
Probably needs many improvements but it works...

Categories