AutoMapper One-To-Many Filter and ProjectTo - automapper

I have the below two Entities (one-to-many)
public class ApplicationCode
{
public Guid ApplicationId { get; set; }
public string? ApplicationAcrynom { get; set; }
public int ApplicationIndex { get; set; }
public IList<ApplicationCodeTranslation> ApplicationCodeTranslations { get; private set; } = new List<ApplicationCodeTranslation>();
}
public class ApplicationCodeTranslation
{
public Guid ApplicationCodeTranslationId { get; set; }
public Guid ApplicationId { get; set; }
public Guid LanguageId { get; set; }
public string? ApplicationDescription { get; set; }
public ApplicationCode ApplicationCode { get; set; } = null!;
}
The goal is to populate the below Dto where I need to filter one language from the child list (IList ApplicationCodeTranslations) in the ApplicationCode entity to get the description value
public class ApplicationCodeDto : IMapFrom<ApplicationCode>
{
public Guid ApplicationId { get; set; }
public string? ApplicationAcrynom { get; set; }
public string? ApplicationDescription { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<ApplicationCode, ApplicationCodeDto>();
profile.CreateMap<ApplicationCodeTranslation, ApplicationCodeDto>();
}
}
I tried below, but it does not give the intended result.
It omits the ApplicationDescription (gives null), the whole .Include line is not translated to SQL at all.
var test1 = await _context.ApplicationCodes
.Include(e => e.ApplicationCodeTranslations.Where(l => l.LanguageId == _languageId))
.ProjectTo<ApplicationCodeDto>(_mapper.ConfigurationProvider)
.ToListAsync();
The other option I tried is
var test2 = await _context.ApplicationCodes
.SelectMany(e => e.ApplicationCodeTranslations)
.Where(l => l.LanguageId == _languageId)
.ProjectTo<ApplicationCodeDto>(_mapper.ConfigurationProvider)
.ToListAsync();
Which omits the properties of the CodeApplication and brings only the child list properties, as SelectMany only brings back the props of the child.
The only way I managed to make it work is below:
var test3 = await _context.ApplicationCodes
.SelectMany(e => e.ApplicationCodeTranslations.Where(l => l.LanguageId == _languageId),
(a,t) => new ApplicationCodeDto{
ApplicationId = a.ApplicationId,
ApplicationAcrynom = a.ApplicationAcrynom,
ApplicationDescription = t.ApplicationDescription,
})
.ToListAsync();
How can AutoMapper (v.11) help in this scenario to avoid mapping the DTO props manually?
Does ProjectTo work here?

Related

EF Core with AutoMapper

I have a DTO containing properties and a Model, eg student can have more than one module and a module can be associated with more than one student. the properties are mapping fine but the Model doesn't map.
public class GetStudentByIdMapping : Profile
{
public GetStudentByIdMapping()
{
CreateMap<Student,StudentDetails>();
CreateMap<Module, StudentDetails>()
.ForPath(dest => dest.StudentModules.ModuleName, opt => opt.MapFrom(m => m.ModuleName))
.ForPath(dest => dest.StudentModules.ModuleCode, opt => opt.MapFrom(m => m.ModuleCode))
.ForPath(dest => dest.StudentModules.Description, opt => opt.MapFrom(m => m.Description))
.ReverseMap();
}
}
public async Task<StudentDetails> GetStudent(int studentId)
{
var student = context.Student
.Where(s => s.StudentId == studentId)
.FirstOrDefault();
var module = await context.Order
.Include(m => m.Module)
.Where(o => o.StudentId == studentId)
.Select(m => m.Module).ToListAsync();
var studMap = Mapper.Map<StudentDetails>(student);
Mapper.Map<StudentDetails>(module);
return studMap;
}
These are the ViewModels I want to map to the Models Model in the StudentDetails ViewModel
public class StudentDetails
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public StudentModule StudentModules { get; set; }
}
public class StudentModule
{
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
}
These are my Entities generated by EF Core
public partial class Student
{
public Student()
{
Order = new HashSet<Order>();
}
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
public partial class Module
{
public Module()
{
Order = new HashSet<Order>();
}
public int ModuleId { get; set; }
public int? LectureId { get; set; }
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
public string ModulePath { get; set; }
public virtual Lecture Lecture { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
You only need to pass the created destination to the second map call:
Just try the following code when mapping:
var studMap = Mapper.Map<Student,StudentDetails>(student);
Mapper.Map<Module,StudentDetails>(module,studMap);
Then the studMap will receive all mapped fields value.

AutoMapper .ReverseMap() .Ignore() not working

Having an issue with version 6.1.1. In the below, the result of the reverse map still has the Company object populated. Per this post, which shows what I am doing below, except they are ignoring a property, and I'm ignoring a complex object.
What am I missing?
CreateMap<Item, ItemViewModel>(MemberList.Destination)
.ReverseMap()
.ForMember(x => x.Company, x => x.Ignore())
;
With AutoMapper 6.1 you could use ForPath instead ForMember to ignore complex objects.
See How to ignore property with ReverseMap for further information.
I see not what is wrong, but here is a running sample:
namespace AutomapperTest2
{
internal class Program
{
#region Methods
private static void Main(string[] args)
{
// Configure the mappings
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ApplicantEducation, ApplicantEducationVM>();
cfg.CreateMap<Applicant, ApplicantVM>().ReverseMap()
.ForMember(x => x.Education, x => x.Ignore());
});
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
Applicant ap = new Applicant
{
Name = "its me",
Education =
new ApplicantEducation
{
SomeInt = 10,
SomeString = "sampleString"
}
};
// Map
ApplicantVM apVm = Mapper.Map<Applicant, ApplicantVM>(ap);
Applicant apBack = Mapper.Map<ApplicantVM, Applicant>(apVm);
}
#endregion
}
/// Your source classes
public class Applicant
{
public ApplicantEducation Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducation
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
// Your VM classes
public class ApplicantVM
{
public string Description { get; set; }
public ApplicantEducationVM Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducationVM
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
}
}

Complex Automapper Configuration

I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}

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.

Deserializing oData to a sane object with ServiceStack

So here's what I'm getting back from the oData service...
{
"odata.metadata":"http://server.ca/Mediasite/Api/v1/$metadata#UserProfiles",
"value":[
{
"odata.id":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')",
"QuotaPolicy#odata.navigationLinkUrl":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/QuotaPolicy",
"#SetQuotaPolicyFromLevel":{
"target":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/SetQuotaPolicyFromLevel"
},
"Id":"111111111111111",
"UserName":"testuser",
"DisplayName":"testuser Large",
"Email":"testuser#testuser.ca",
"Activated":true,
"HomeFolderId":"312dcf4890df4b129e248a0c9a57869714",
"ModeratorEmail":"testuser#testuserlarge.ca",
"ModeratorEmailOptOut":false,
"DisablePresentationContentCompleteEmails":false,
"DisablePresentationContentFailedEmails":false,
"DisablePresentationChangeOwnerEmails":false,
"TimeZone":26,
"PresenterFirstName":null,
"PresenterMiddleName":null,
"PresenterLastName":null,
"PresenterEmail":null,
"PresenterPrefix":null,
"PresenterSuffix":null,
"PresenterAdditionalInfo":null,
"PresenterBio":null,
"TrustDirectoryEntry":null
}
]
}
I want to deserialize this into a simple class, like just the important stuff (Id, Username, etc...to the end).
I have my class create, but for the life of me I can't figureout how to throw away all the wrapper objects oData puts around this thing.
Can anyone shed some light?
You can use JsonObject do dynamically traverse the JSON, e.g:
var users = JsonObject.Parse(json).ArrayObjects("value")
.Map(x => new User
{
Id = x.Get<long>("Id"),
UserName = x["UserName"],
DisplayName = x["DisplayName"],
Email = x["Email"],
Activated = x.Get<bool>("Activated"),
});
users.PrintDump();
Or deserialize it into a model that matches the shape of the JSON, e.g:
public class ODataUser
{
public List<User> Value { get; set; }
}
public class User
{
public long Id { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool Activated { get; set; }
public string HomeFolderId { get; set; }
public string ModeratorEmail { get; set; }
public bool ModeratorEmailOptOut { get; set; }
public bool DisablePresentationContentCompleteEmails { get; set; }
public bool DisablePresentationContentFailedEmails { get; set; }
public bool DisablePresentationChangeOwnerEmails { get; set; }
public int TimeZone { get; set; }
}
var odata = json.FromJson<ODataUser>();
var user = odata.Value[0];

Resources