Why is a custom resolver needed here (AutoMapper)? - automapper

Given the following entity model:
public class Location
{
public int Id { get; set; }
public Coordinates Center { get; set; }
}
public class Coordinates
{
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
... and the following view model:
public class LocationModel
{
public int Id { get; set; }
public double? CenterLatitude { get; set; }
public double? CenterLongitude { get; set; }
}
The LocationModel properties are named such that mapping from the entity to the model does not require a custom resolver.
However when mapping from a model to an entity, the following custom resolver is needed:
CreateMap<LocationModel, Location>()
.ForMember(target => target.Center, opt => opt
.ResolveUsing(source => new Coordinates
{
Latitude = source.CenterLatitude,
Longitude = source.CenterLongitude
}))
Why is this? Is there a simpler way to make AutoMapper to construct a new Coordinates value object based on the naming conventions in the viewmodel?
Update
To answer the first comment, there is nothing special about the entity to viewmodel mapping:
CreateMap<Location, LocationModel>();

Edit
Please see comment thread below. This answer is actually for the opposite mapping.
You're doing something else wrong. You are following the convention correctly, so the mapping should work without any need for a resolver.
I just tried this test, and it passed:
public class Location
{
public int Id { get; set; }
public Coordinates Center { get; set; }
}
public class Coordinates
{
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
public class LocationModel
{
public int Id { get; set; }
public double? CenterLatitude { get; set; }
public double? CenterLongitude { get; set; }
}
[Test]
public void LocationMapsToLocationModel()
{
Mapper.CreateMap<Location, LocationModel>();
var location = new Location
{
Id = 1,
Center = new Coordinates { Latitude = 1.11, Longitude = 2.22 }
};
var locationModel = Mapper.Map<LocationModel>(location);
Assert.AreEqual(2.22, locationModel.CenterLongitude);
}

Related

Autquery not including nested result when using a response DTO

Let's say you have these models:
public class Blog
{
[PrimaryKey]
[AutoIncrement]
public int Id { get; set; }
public string Url { get; set; }
public string PrivateField { get; set; }
[Reference]
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}
public class BlogResponse
{
public int Id { get; set; }
public string Url { get; set; }
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}
And this request:
public class BlogsLookUpRequest : QueryDb<Blog, BlogResponse>
{
}
The return value will have BlogToBlogCategories as null, but this request:
public class BlogsLookUpRequest : QueryDb<Blog>
{
}
Will have BlogToBlogCategories populated. I can manually create the query response like so with custom implementation:
var q = _autoQuery.CreateQuery(request, Request.GetRequestParams(), base.Request);
var results = _autoQuery.Execute(request,q, base.Request);
return new QueryResponse<ResponseBlog>()
{
Results = results.Results.ConvertTo<List<ResponseBlog>>(),
Offset = request.Skip,
Total = results.Total
};
Then it will have the nested results. If I decorate the collection with [Reference] then it is trying to find foreign key on non-existant BlogResponse table.
Why are referenced results removed when specifying a return model with AutoQuery? Is there a way to mark it up so it works?
The POCO Reference Types is used to populate Data Models not adhoc Response DTOs.
In this case it's trying to resolve references on a non-existent table, you can specify which table the DTO maps to with [Alias] attribute, e.g:
[Alias(nameof(Blog))]
public class BlogResponse
{
public int Id { get; set; }
public string Url { get; set; }
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}

Autmapper nested mapping

I have the following main class:
public class ResearchOutcome
{
public ResearchOutcomeCategory ResearchOutcomeCategory { get; set; }
public string? UniqueIdentifier { get; set; }
}
And the category class is:
public class ResearchOutcomeCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
}
The View models for above classes are:
public class ResearchOutcomeDetailVm : IMapFrom<ResearchOutcome>
{
public int Id { get; set; }
public virtual ResearchOutcomeCategoryDetailVm ResearchOutcomeCategory { get; set; }
}
public class ResearchOutcomeCategoryDetailVm : IMapFrom<ResearchOutcomeCategory>
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Now, I have used the following mapping profile:
// First this one
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>();
//Then I tried this one
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>()
.ForMember(o => o.ResearchOutcomeCategory,
cat => cat.MapFrom( o => o.ResearchOutcomeCategory));
But the ResearchOutcomeCategory is always null. Any help would be appreciated.
After digging more, I identified that I was not "Including" the relevant item in the query, hence, the view model was always empty. Pretty dumb on my part :D
Regarding the mapping, if the properties (even complex ones) have the same names, then the mapper will map them automatically. So simply this line worked
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
Hope it helps someone

how to choose aggregate root

I model three entities in the auto industry as following:
public class Manufacturer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Model> Models { get; set; }
public ACManufacturer()
{
AutoCareModels = new List<ACModel>();
}
}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int NumberOfSeats { get; set; }
public Manufacturer Manufacturer { get; set; }
public ICollection<ManufacturedYear> ManufacturedYears { get; set; }
public Model()
{
ManufacturedYears = new List<ManufacturedYear>();
}
}
public class ManufacturedYear
{
public int Id { get; set; }
public int ProductionYear { get; set; }
public Model Model { get; set; }
}
Please tell me how to choose aggregate root or the differente way to model three entities
Thank you every much
Answer depends on what you do with these models. What is your app doing? If 10 users are updating data in this app - how can they divide their work? What are transaction boundaries?
If those 10 users are usually working with 10 different models, your screens are organized around models, then Model is your aggregate root.

SchemaBuilder with complex types

I want to store complex content part record but couldn't create columns with SchemaBuilder in Migrations file.
Here are my classes:
public enum BoxInheritance
{
Empty, Inherit, Enter
}
public class BoxSize
{
public string Width { get; set; }
public string Height { get; set; }
}
public class BoxSpace
{
public string Left { get; set; }
public string Right { get; set; }
public string Top { get; set; }
public string Bottom { get; set; }
}
public class BoxPartRecord : ContentPartRecord
{
public virtual BoxSize Size { get; set; }
public virtual BoxSpace Space { get; set; }
public virtual Dictionary<string, BoxInheritance> Inheritances { get; set; }
public BoxPartRecord()
{
Size = new BoxSize();
Space = new BoxSpace();
Inheritances = new Dictionary<string, BoxInheritance>();
}
}
Is it ok to use a content part record like this?
How to create a table for this content part record?
I think this won't work. My suggestion is to use simple types in the record class and complex types in the content part itself (you can do the mapping there).
public class BoxPartRecord
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
...
}
public class BoxPart : ContentPart
{
public BoxSize Size { get { return new BoxSize {record.Width, record.Height} ...
}

how do i use AutoMapper in ICollation<> Fields

when i use AutoMapper for mapping my ViewModels and get All News, thrown error for me.
Errors...
The following property on Mosque.Core.ViewModels.CategoryViewModel cannot be mapped:
Categories
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type Mosque.Core.ViewModels.CategoryViewModel.
please help me, thank you
//Models
public class News
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual User User { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<News> News { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<News> News { get; set; }
}
//ViewModels
public class NewsViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<CategoryViewModel> Categories { get; set; }
public virtual UserViewModel User { get; set; }
}
public class CategoryViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<NewsViewModel> News { get; set; }
}
public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<NewsViewModel> News { get; set; }
}
how do i use for select All News?
--Update1--
I used onion architecture in the project and i installed AutoMapper in the Service layer and i want get all news from repository and fill into ViewModels and pass to the UI.
my code in service layer is...
public List<NewsViewModel> GetAll()
{
Mapper.CreateMap<News, NewsViewModel>()
.ForMember(dest => dest.Categories, src => src.MapFrom(p => p.Categories))
.ForMember(dest => dest.User, src => src.MapFrom(p => p.User));
Mapper.AssertConfigurationIsValid();
var viewModels = new List<NewsViewModel>();
foreach (var item in _newsRepository.GetAll())
{
var viewModel = Mapper.Map<News, NewsViewModel>(item);
viewModels.Add(viewModel);
}
return viewModels;
}
You don't seem to have created maps for Catagory and User.
Add the following maps:
Mapper.CreateMap<User, UserViewModel>();
Mapper.CreateMap<Category, CategoryViewModel>();
By the way, why are you creating the maps inside the GetAll method? You can create the maps once, usually at application startup.

Resources