Mapping referenced table with C# automapper - c#

Suppose I have a table Scopes which contains a foreign key to another table (subscopes) and I want to map it. All columns available in my Scopes table as well as some columns from my referenced table (subscopes) are required to be mapped to a DTO.
My questions are:
What should be the content of DTO?
How should I map using c# and Automapper?

this.CreateMap<tblSubScope, Sub2MainScopeDto>()
.ForMember(t => t.IdxSubScope, opt => opt.MapFrom(s => s.idxSubScope))
.ForMember(t => t.IdxMainScope, opt => opt.MapFrom(s => s.idxMainScope))
.ForMember(t => t.SubScopeDescription, opt => opt.MapFrom(s => s.strSubScope))
.ForMember(t => t.MainScopeDescription, opt => opt.MapFrom(s => s.tblMainScope.strMainScope))
.ReverseMap()
.ForMember(t => t.idxSubScope, opt => opt.MapFrom(s => s.IdxSubScope))
.ForMember(t => t.strSubScope, opt => opt.MapFrom(s => s.SubScopeDescription))
.ForMember(t => t.idxMainScope, opt => opt.MapFrom(s => s.IdxMainScope));
Problem Solved.

Related

Automapper. Error when try to map Nested Type

I'm trying to map one object to another:
Mapper.CreateMap<ShippingAddressModel, ShippingAddress>()
.ForMember(x => x.Addresses.Name, opts => opts.MapFrom(x => x.Name));
But I have an error:
Expression 'x => x.Addresses.Name' 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.
This should work:
Mapper.CreateMap<ShippingAddressModel, ShippingAddress>()
.ForMember(x => x.Addresses, opt => opt.MapFrom(model => model));
Mapper.CreateMap<ShippingAddressModel, Addresses>()
.ForMember(x => x.Name, opt => opt.MapFrom(model => model.Name));

Automapper with EF Navigation Properties

I'm trying to map two collections by using EF's navigation property.
Collection.Items is a List<Item>
CollectionDTO has a navigation property to a cross-join table called CollectionItem, which has another navigation property to Item.
I want each CollectionDTO.CollectionItem.Item to map to Collection.Item.
I have tried this but I can't figure it out.
Can someone help?
var mapperConfig = new MapperConfiguration(cfg =>
{
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Where(x => x.CollectionId == src.Id).ToList().ForEach(ci => ci.Item)));
});
You can use Select extension method like this:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Select(ci=>ci.Item).ToList()));
If Item navigation property is a collection, then use SelectMany extension method:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.SelectMany(ci=>ci.Item).ToList()));

How to ReverseMap a complex object in AutoMapper

Here is the CreateMap method:
Mapper.CreateMap<Domain.Models.Organization, OrganizationInputModel>()
.ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Address.Address1))
.ForMember(dest => dest.Address2, opts => opts.MapFrom(src => src.Address.Address2))
.ForMember(dest => dest.City, opts => opts.MapFrom(src => src.Address.City))
.ForMember(dest => dest.State, opts => opts.MapFrom(src => src.Address.State))
.ForMember(dest => dest.Zip, opts => opts.MapFrom(src => src.Address.Zip))
.ReverseMap();
Here Address is a complex object.
I was expecting to see the bi-directional model mapping. But it turns out the complex object mapping is broken:
var entity = AutoMapper.Mapper.Map<Domain.Models.Organization>(model);
I got an error:
{"message":"Null value for non-nullable member. Member: 'Address'."}
Some SO posts says reverse mapping is only for simple object, and we will have to create two Mapper.CreateMap in this case. Is it really so?
Yes, you need to create the reverse mapping in this case. The reason for this is because AutoMapper wouldn't know how to instantiate the target Address property.

Am I using Automapper 2.0's Include functionality correctly?

Either I'm not, or it isn't working... I have a single Source class that I want to map to multiple views that inherit from each other.
Basically the base class is the Detail, and the child class is Edit or Update which use all the same data as Detail, plus a couple other fields to manage their own lists or whatever.
Here are the maps I'm using:
Mapper.CreateMap<Ticket, Detail>()
.Include<Ticket, Update>()
.Include<Ticket, Edit>()
.ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.Priority.Code))
.ForMember(dest => dest.TicketID, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusCode))
.ForMember(dest => dest.Category, opt => opt.MapFrom(src => src.ProblemCategoryCode))
.ForMember(dest => dest.crmBusCode, opt => opt.MapFrom(src => src.Company.crmBusCode))
.ForMember(dest => dest.TeamMembers, opt => opt.MapFrom(src => src.Schedules.Where(s => s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Update>()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules.Where(s => s.EmployeeID == Util.CurrentUserID() && s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>();
Then if I Mapper.Map(ticket) any of the properties that use MapFrom don't get evaluated, they just end up with the values they'd have had if there was no set mapping.
So what's wrong here?
As an alternative solution if you don't want to call Mapper.Map two times. You can move the common mappings of Detail into an extension method:
public static class MappingExtensions
{
public static IMappingExpression<Ticket, TDest> MapDetailProperties<TDest>(
this IMappingExpression<Ticket, TDest> mapBase) where TDest : Detail
{
return mapBase
.ForMember(dest => dest.Priority,
opt => opt.MapFrom(src => src.Priority.Code))
///....
.ForMember(dest => dest.TeamMembers,
opt => opt.MapFrom(src => src
.Schedules.Where(s => s.CompleteTime == null)));
}
}
And then use that extension method when registering the Ticket -> Update and Ticket -> Edit mappers:
Mapper.CreateMap<Ticket, Update>()
.MapDetailProperties()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules
.Where(s => s.EmployeeID == Util.CurrentUserID() &&
s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>()
.MapDetailProperties();
Then you can use Map normally:
Ticket ticket = new Ticket();
var edit = Mapper.Map<Ticket, Edit>(ticket);
var update = Mapper.Map<Ticket, Update>(ticket);
Am I using Automapper 2.0's Include functionality correctly?
No--When you use .Include, AutoMapper expects that the destination classes are in a similar hierarchy as the source classes (This is discussed further here). In other words, if you were mapping to different subclasses of Ticket to Detail, Update and Edit, Include would be appropriate.
This doesn't seem helpful in your case. I would recommend using the overload of .Map that takes an existing object and modifies it. That way, you only have to define a mapping for the base type:
Ticket ticket = new Ticket();
Edit edit = new Edit();
Mapper.Map<Ticket, Detail>(ticket, edit);
// Edit has now been automapped using the base mapping.
Mapper.Map<Ticket, Edit>(ticket, edit);
// The properties unique to Edit have now been mapped.

Use automapper to update an Entity Framework Entity

I am trying to use Entity Framework and have Automapper update my entity from my contract.
My code looks like this:
var temp = OrderContract;
Order order = dataAccess.FindOne<Order>(x => x.OrderId == temp.OrderId)
?? new Order();
Mapper.Map(OrderContract, order);
if (order.OrderId <= 0)
dataAccess.Add(order);
(Note: I am using the Repository Pattern. dataAccess.FindOne calls CreateQuery to return one entity.)
The problem I am having is with the relationships. I get this error when I do an update (inserts work just fine):
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I am guessing that automapper is not updating the way I want it to. From the error message and googling around I have surmised that my relationships that are collections (and maybe even the ones that are not collections) are being recreated by Automapper.
How can I tell Automapper to just update and not remake any objects or collections?
Guesses:
I read that maybe I need to use the UseDestinationValue option for automapper. I went back and put that on all my collections But when I do that then my inserts fail with a foreign key violation.
Code Mappings:
Using UseDestinationValue only on one collection (this one inserts but will not update):
//+ Source
Mapper.CreateMap<SourceContract, Source>()
.IgnoreAllNonExisting();
//+ SelectedRequirement
Mapper.CreateMap<SelectedRequirementContract, SelectedRequirement>()
.ForMember(x => x.SelectedRequirementId, opt => opt.MapFrom(src => src.RequirementId))
.IgnoreAllNonExisting();
//+ Comment Contract
Mapper.CreateMap<CommentContract, Comment>()
.ForMember(x => x.CommentText, opt => opt.MapFrom(src => src.Comment))
.IgnoreAllNonExisting();
//+ Order Automapper setup
Mapper.CreateMap<OrderContract, Order>()
.ForMember(x => x.Source, opt => opt.MapFrom(src => src.Source))
.ForMember(x => x.Comment, opt => opt.MapFrom(src => src.Comment))
//Although a mapping was created for Comment entity,
//we still need to map the CommentId of the Order entity otherwise it will remain null during an update.
//Another way to handle this would be to Delete CommentId from the Order entity.
//However, if anyone updates (Update from model) OrderDataModel.edmx that property would show up again thus causing
//a null value to be inserted during an update.
.ForMember(x => x.CommentId, opt => opt.MapFrom(src => src.Comment.CommentId))
.ForMember(x => x.SelectedRequirements, opt => {opt.UseDestinationValue(); opt.MapFrom(src => src.Requirements);})
.ForMember(x => x.OrderStateId, opt => opt.MapFrom(src => src.StateId))
.ForMember(x => x.OrderStateId, opt => opt.MapFrom(src => src.StateId))
.IgnoreAllNonExisting();
Using UseDestinationValue everywhere (this one does not insert):
//+ Source
Mapper.CreateMap<SourceContract, Source>()
.IgnoreAllNonExisting();
//+ SelectedRequirement
Mapper.CreateMap<SelectedRequirementContract, SelectedRequirement>()
.ForMember(x => x.SelectedRequirementId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.RequirementId); })
.IgnoreAllNonExisting();
//+ Comment Contract
Mapper.CreateMap<CommentContract, Comment>()
.ForMember(x => x.CommentText, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment); })
.IgnoreAllNonExisting();
//+ Order Automapper setup
Mapper.CreateMap<OrderContract, Order>()
.ForMember(x => x.Source, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Source); })
.ForMember(x => x.Comment, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment); })
//Although a mapping was created for Comment entity,
//we still need to map the CommentId of the Order entity otherwise it will remain null during an update.
//Another way to handle this would be to Delete CommentId from the Order entity.
//However, if anyone updates (Update from model) OrderDataModel.edmx that property would show up again thus causing
//a null value to be inserted during an update.
.ForMember(x => x.CommentId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment.CommentId); })
.ForMember(x => x.SelectedRequirements, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Requirements); })
.ForMember(x => x.OrderStateId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.StateId); })
.ForMember(x => x.OrderStateId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.StateId); })
.IgnoreAllNonExisting();
What do I need to so I can insert and update?
I think what you want is for AutoMapper to not create your EF entity and take the one you sent it. In version2.0 of auto mapper there is an overload to map method where you can basically pass your datacontext delegate and let EF create your object.
Please look at this article.

Categories

Resources