Map flattened strings to list - c#

Ok totally noob question about AutoMapper.
Employee model object.
public class Employee {
public string FirstName { get; set; }
public string LastName { get; set; }
public decimal HourlyRate { get; set; }
public List<string> EmailAddresses { get; set; }
}
Dto object:
public class UpdateEmployeeDetailsDto {
public decimal HourlyRate { get; set; }
public string EmailAddress1 { get; set; }
public string EmailAddress2 { get; set; }
}
I want to map the DTO to the Employee object and turn EmailAddress1 and EmailAddress2 into a List.
I've searched for this type of thing on google and SO but the expamples I've found I've not been able to translate them into working code.
Any help?

Use AfterMap to provide custom mapping (creation list from properties in your case):
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.AfterMap((dto, e) => e.EmailAddresses = new List<string>() {
dto.EmailAddress1,
dto.EmailAddress2
});

Apart from what #lazyberezovsky says, you can also create a custom resolver:
public class CustomResolver : ValueResolver<UpdateEmployeeDetailsDto, List<string>>
{
protected override List<string> ResolveCore(UpdateEmployeeDetailsDto source)
{
return new List<string> { source.EmailAddress1, source.EmailAddress2 }
}
}
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.ForMember(dest => dest.EmailAddresses, opt => opt.ResolveUsing<CustomResolver>());
What's the point? You can make it more generic and use this to resolve cases Value1, Value2, Value3...ValueN get mapped to List<ValueType> Values

Related

Automapper: Trying to map to a property with a private setter

I am trying to map to a class that contains a property with a private setter. I read in a different answer that this was possible (Automapper apparently used Reflection to do so) just as long as I configured the property in question with the ForMember method (see below)
Mapper config
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<IDbCoverage, ICoverage>()
.ForMember(dest => dest.CoverageCodeDesc, conf => conf.MapFrom(src => src.CoverageCodeDesc));
});
Interfaces
public interface IDbCoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
public interface ICoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
Instantiation of the destination class:
public class Coverage
{
public string ExternalMemberId { get; set; }
public string CoverageCode { get; set; }
public string CoverageCodeDesc { get; private set; }
}
However when I try this only the properties with public setters are mapped. The source value of the other property is not mapped and the destination class ends up with a null value.
What am I doing wrong?

Load navigation properties in generic repository

I have a problem when using AutoMapper and EF Core together to map navigation properties from the model to the DTO. My EF classes are:
public class Meal
{
public DateTime Day { get; set; }
public MealType MealType { get; set; }
public int MealId { get; set; }
}
public class MealType
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
And the corresponding DTO classes:
public class ExistingMealDto
{
public DateTime Day { get; set; }
public ExistingMealTypeDto MealType { get; set; }
public int MealId { get; set; }
public string MealTypeName { get; set; }
}
public class ExistingMealTypeDto
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
This is my AutoMapper mapping:
config.CreateMap<DataLayer.EfClasses.MealType, ExistingMealTypeDto>();
config.CreateMap<DataLayer.EfClasses.Meal, ExistingMealDto>()
.ForMember(x => x.MealType, x => x.MapFrom(x=>x.MealType))
.ForMember(x => x.MealTypeName, x => x.MapFrom(y => y.MealType.Name));
I'm loading the data within a generic method that looks like this:
public IEnumerable<TDtoOut> GetAllAsDto<TIn, TDtoOut>()
where TIn : class
{
var allEntities = DbContext.Set<TIn>();
return Mapper.Map<IEnumerable<TDtoOut>>(allEntities);
}
When calling this code, all Meal instances are loaded from the database and MealId and Day are filled correctly. However, MealType is null and therefore ExistingMealDto.MealType is null as well. I can work around this problem by explicitly calling DbContext.MealTypes.ToList(), but since the method should be generic for TIn, this is not a production solution.
How can I solve this issue? Thanks!
For getting the related data in generic method , you can judge the Type of the passed type. The following is a test demo , you could refer to:
public IEnumerable<TIn> GetAllAsDto<TIn>()
where TIn : class
{
Type typeParameterType = typeof(TIn);
if (typeParameterType == typeof(User))
{
var Entities = _context.Set<User>().Include(u=>u.Orders);
return (IEnumerable<TIn>)Entities;
}
else
{
var allEntities = _context.Set<TIn>();
return allEntities;
}
}
public void Test()
{
var data = GetAllAsDto<User>();
var data1 = GetAllAsDto<Status>();
}
Result

Automapper says Missing map from System.String to System.Char? But I see no Char property

I've got three classes:
public class UserReport : Entity
{
public string Name { get; set; }
public string Email { get; set; }
public List<string> Departments { get; set; }
public List<string> Titles { get; set; }
}
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateLastModified { get; set; }
public string CreatedBy { get; set; }
public string LastModifiedBy { get; set; }
public bool Active { get; set; }
protected Entity()
{
DateCreated = DateTime.Now;
DateLastModified = DateTime.Now;
Id = Guid.NewGuid();
}
}
public class UserModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Departments { get; set; }
public string Titles { get; set; }
}
With my automapper configs set as:
CreateMap<List<string>, string>().ConvertUsing(strings => {
if (strings != null)
return string.Join("\n", strings);
return "";
});
CreateMap<UserReport, UserModel>();
When trying to call from a generic method using the Automapper Ef Extensions:
IQueryable<TModel> modelQueryable = _reportService.GetReportBaseQuery().ProjectToQueryable<TModel>();
I get this error
Missing map from System.String to System.Char. Create using Mapper.CreateMap.
GetReportBaseQuery() returns an IQueryable<TReport>, so the UserReport in this instance. I don't see any char properties, why is this coming up?
Just for testing I tried to make one:
CreateMap<String, Char>().ConvertUsing(x => x.FirstOrDefault());
And then it says:
Argument types do not match
Further research shows that this works:
Mapper.Map<List<TReport>, List<TModel>>(_reportService.GetReportBaseQuery().ToList());
But I can't use that since I need it to be a queryable returned. So something is different when I try to do an EF projection, not sure what that is though. Writing a select statement from one to the other is easy enough, but that's not generic and re-usable.
The solution is to explicitly set mapping for the Departments and Titles fields:
CreateMap<UserReport, UserModel>()
.ForMember(x => x.Departments, o => o.MapFrom(s => string.Join("\n", s.Departments)))
.ForMember(x => x.Titles, o => o.MapFrom(s => string.Join("\n", s.Titles)));
But, this does not explain, why this situation occurs.
As DOTang pointed out:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable1[System.String])' method, and this method cannot be translated into a store expression.
The AutoMapper extension seems to try to map the following thing:
IEnumerable<string> => IEnumerable<char>

AutoMapper throwing StackOverflowException when calling ProjectTo<T>() on IQueryable

I have created classes using EF Code First that have collections of each other.
Entities:
public class Field
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<AppUser> Teachers { get; set; }
public Field()
{
Teachers = new List<AppUser>();
}
}
public class AppUser
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public virtual List<Field> Fields { get; set; }
public AppUser()
{
Fields = new List<FieldDTO>();
}
}
DTOs:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<AppUserDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<AppUserDTO>();
}
}
public class AppUserDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
Mappings:
Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();
And I am getting StackOverflowException when calling this code (Context is my dbContext):
protected override IQueryable<FieldDTO> GetQueryable()
{
IQueryable<Field> query = Context.Fields;
return query.ProjectTo<FieldDTO>();//exception thrown here
}
I guess this happens because it loops in Lists calling each other endlessly. But I do not understand why this happens. Are my mappings wrong?
You have self-referencing entities AND self-referencing DTOs. Generally speaking self-referencing DTOs are a bad idea. Especially when doing a projection - EF does not know how to join together and join together and join together a hierarchy of items.
You have two choices.
First, you can force a specific depth of hierarchy by explicitly modeling your DTOs with a hierarchy in mind:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeacherDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<TeacherDTO>();
}
}
public class TeacherDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
}
public class AppUserDTO : TeacherDTO
{
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
This is the preferred way, as it's the most obvious and explicit.
The less obvious, less explicit way is to configure AutoMapper to have a maximum depth it will go to traverse hierarchical relationships:
CreateMap<AppUser, AppUserDTO>().MaxDepth(3);
I prefer to go #1 because it's the most easily understood, but #2 works as well.
Other option is using PreserveReferences() method.
CreateMap<AppUser, AppUserDTO>().PreserveReferences();
I use this generic method:
public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
{
if (null == sourceItem)
{
return default(TTarget);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);
return JsonConvert.DeserializeObject<TTarget>(serializedObject);
}
...
MapperConfiguration(cfg =>
{
cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...
When you giving 1 navigation_property to 2nd entity and visa-versa it go in an infinite loop state. So, the compiler automatically throws a Stackoverflow exception.
So, to avoid that, you just need to remove one navigation_property from any of the entities.

Automapping based on condition

I'm trying to use Automapper v3 to map from my Post struct to my Postmodel class. I need to map Term Name to my Categories array but only if the Type equals "Category".
Here's my code
public class NewsModel
{
public NewsModel(int id)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Post, PostModel>();
});
Posts = new List<PostModel> {Mapper.Map<PostModel>(_newsGetter.GetItem(id))};
}
public List<PostModel> Posts { get; set; }
}
Map to this class
public class PostModel
{
public String[] Categories { get; set; }
}
Map from this Struct
public struct Post
{
public Term[] Categories { get; set; }
}
public Struct Term
{
public string Name{ get; set; }
public string Type{ get; set; }
}
Any help is greatly appreciated.
The simplest solution would be to map the filtered categories from the Post to the PostModel.
cfg.CreateMap<Post, PostModel>()
.ForMember(pm => pm.Categories
, o => o.MapFrom(p => p.Categories.Where(t => t.Name != "Category")));
Then simply convert the Term to a string using ConvertUsing
cfg.CreateMap<Term, string>().ConvertUsing(t => t.Name);

Categories

Resources