Map nested elements to related lists using AutoMapper - c#

I have two sets of objects
Objects that I use in C# client application:
public class EmployeeClient
{
public int Id { get; set; }
public int DepartmentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentClient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationClient
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentClient> Departments { get; set; }
public List<EmployeeClient> Employees { get; set; }
}
And DTOs:
public class EmployeeDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class OrganizationDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentDto> Departments { get; set; }
}
I use AutoMapper and I need to configure mapping Client -> DTOs and DTOs -> Client.
I implemented mapping DTOs->Client like this:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.ResolveUsing(src => src.Departments.SelectMany(d => d.Employees)))
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client)
{
foreach (var department in dto.Departments)
{
foreach (var employee in department.Employees)
{
var clientEmployee = client.Employees.First(e => e.Id == employee.Id);
clientEmployee.DepartmentId = department.Id;
}
}
}
}
It is not universal solution, but is works for me.
I've found only one option how mapping Client->DTOs could be implemented:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto)
{
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var configuration = (IConfigurationProvider)new MapperConfiguration(cfg =>
{
cfg.AddProfiles(typeof(ClientToDtosMappingProfile));
});
var mapper = (IMapper)new Mapper(configuration);
var employeeDto = mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}
It works, but I do not like this solution because I should create instance of new Mapper every time I map objects. In my real code Employee has a lot of nested elements and mapping is configured in multiple profiles.
Any ideas how it could be implemented better?

I made my code a bit better using ResolutionContext. It allows not to create mappers in AfterMap function.
DtoToClientMappingProfile:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.Ignore())
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client, ResolutionContext resolutionContext)
{
if (dto.Departments == null)
{
return;
}
client.Departments = new List<DepartmentClient>();
foreach (var department in dto.Departments)
{
var departmentClient = resolutionContext.Mapper.Map<DepartmentClient>(department);
client.Departments.Add(departmentClient);
if (department.Employees == null)
{
continue;
}
if (client.Employees == null)
{
client.Employees = new List<EmployeeClient>();
}
foreach (var employee in department.Employees)
{
var employeeClient = resolutionContext.Mapper.Map<EmployeeClient>(employee);
employeeClient.DepartmentId = department.Id;
client.Employees.Add(employeeClient);
}
}
}
ClientToDtosMappingProfile:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto, ResolutionContext resolutionContext)
{
if (client.Employees == null)
{
return;
}
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var employeeDto = resolutionContext.Mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}

If you call AssertConfigurationIsValid, AM will complain about what it doesn't know how to map.
The problem seems to be that you don't have the information needed to fill the destination object in the source object.
You will need to add a resolver for each property AM complains about, like the ResolveUsing you already have, for example.
You also need to pass the extra information that's needed.
The result may not look good eventually because AM cannot rely on uniform objects to do its job, you have to tell it what to do.
Another way to go about it is to do the high level mapping in your own code and rely on AM only when the mapping is simple enough so AM can do it by itself. The more you customize AM, the less value you get from it.

Related

Child Table Properties are not fetching in ef core inheritance

i have following entities..
public class Paper
{
public int Id { get; set; }
public string PaperCode { get; set; }
...
}
public class MCQPaper : Paper
{
public ICollection<MCQQuestion> Questions { get; set; }
}
public class MCQQuestion : Question
{
public int MCQPaperId { get; set; }
public MCQPaper MCQPaper { get; set; }
public int? MCQOptionId { get; set; }
[ForeignKey("MCQOptionId")]
public MCQOption TrueAnswer { get; set; }
public ICollection<MCQOption> MCQOptions { get; set; }
}
public class MCQOption
{
public int Id { get; set; }
public string OptionText { get; set; }
}
and i am trying to fetch MCQPaper based on unique papercode but it always gives me empty collection of Questions
here is my Query inside Repository..
public MCQPaper GetByPaperCode(string paperCode)
{
var ans = AppDbContext.MCQPapers
.Where(paper => paper.PaperCode.Equals(paperCode))
//.Include(paper => paper.Questions)
// .ThenInclude(que => que.MCQOptions)
.Include(paper => paper.Questions)
.ThenInclude(que => que.MCQPaper)
//.Include(paper => paper.Questions)
// .ThenInclude(que => que.TrueAnswer)
.FirstOrDefault();
return ans;
}
here i have tried various combinations of include() and theninclude() but none of them work for me
and lastly ignore grammer mistakes if any
thankyou in advance
after going threw comments and searching on google i have found solution
using two queries this problem can be solved because here i have circular dependency between MCQQuestion and MCQOption
so solution is ....
public MCQPaper GetByPaperCode(string paperCode)
{
using var transaction = AppDbContext.Database.BeginTransaction();
MCQPaper ans = new MCQPaper();
try
{
ans = AppDbContext.MCQPapers
.FirstOrDefault(paper => paper.PaperCode.Equals(paperCode));
var questions = AppDbContext.MCQQuestions
.Include(que => que.MCQOptions)
.Where(que => que.MCQPaperId == ans.Id);
ans.Questions = questions.ToList();
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
return ans;
}

AutoMapper does not update nested collection

I am trying to simplify a process of mapping classes. For this purpose I am using AutoMapper. If no one of classes does not have a nested collection then all is great. But if someone have, I have a trouble with mapping. Collection into a class is overwriting. I found an extension AutoMapper.Collection. But it does not work for me.
I am working on a web api project, so some parts of codes here:
// Classes for mapping
public class A_Dto
{
public int Id { get; set; }
public List<A_SubItem_Dto> SubItems { get; set; }
}
public class A_SubItem_Dto
{
public int Id { get; set; }
public string Value { get; set; }
public A_Dto A_MainObject { get; set; }
}
public class A_Resource
{
public int Id { get; set; }
public List<A_SubItem_Resource> SubItems { get; set; }
}
public class A_SubItem_Resource
{
public int Id { get; set; }
public string Value { get; set; }
}
// Configure AutoMapper
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(cfg => cfg.AddCollectionMappers(), typeof(Startup));
}
// Configure profile class for mapping
public class AutoMapping : Profile
{
CreateMap<A_Dto, A_Resource>().ReverseMap()
.ForMember(m => m.Id, opt => opt.Ignore());
CreateMap<A_SubItem_Dto, A_SubItem_Resource>().ReverseMap()
.EqualityComparison((sir, si) => sir.Id == si.Id);
}
// Controller action with simple test
public async Task<IActionResult> Put(/*...*/)
{
// Test
var aDto = new A_Dto()
{
Id = 7,
SubItems = new List<A_SubItem_Dto>()
{
new A_SubItem_Dto() { Id = 2, Value = "_2_" },
new A_SubItem_Dto() { Id = 9, Value = "_9_" }
}
};
var aResource = new A_Resource()
{
Id = 7,
SubItems = new List<A_SubItem_Resource>()
{
new A_SubItem_Resource() { Id = 2, Value = "_222222_" }
}
};
_mapper.Map(aResource, aDto);
}
In result aDto object contains new collection with one element of aResource.SubItems
Could you tell me please, what I am doing wrong?

Automapper: how do I iterate through a collection

Right now I have some models that look like this:
Consumer ResourceModel:
public ConsumerResourceModel()
{
consumerData = new List<ConsumerData>();
Memberships = new List<MemberResourceModel>();
}
public List<ConsumerData> consumerData { get; set; }
public List<MemberResourceModel> Memberships { get; set; }
ConsumerResponseModel:
public class ConsumerResponseModel
{
public List<Consumer> consumers { get; set; }
}
public class Membership
{
public string membershipType { get; set; }
}
public class Consumer
{
public decimal consumerId { get; set; }
public Name name { get; set; }
public List<Membership> memberships { get; set; }
}
My current Mapping looks like this:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(config =>
{
config.CreateMap<ConsumerData, Consumer>()
.ForMember(dest=>dest.memberships, opts=>opts.MapFrom(src=>new List<Membership>()))
.ReverseMap();
config.CreateMap<ConsumerData, Name>()
.ReverseMap();
config.CreateMap<MemberResourceModel, Membership>()
.ReverseMap();
});
}
I have everything being mapped correctly for consumer and membership but I'm basically having to do something like this in my method:
public ConsumerResponseModel MapConsumer(IEnumerable<ConsumerResourceModel> res)
{
var responseModel = new ConsumerResponseModel {consumers = new List<Consumer>()};
var consumerResourceModels = res as IList<ConsumerResourceModel> ?? res.ToList();
foreach (var item in consumerResourceModels)
{
foreach(var consumerData in item.consumerData)
{
var consumer = Mapper.Map<Consumer>(consumerData);
foreach(var membership in item.Memberships)
{
consumer.memberships.Add(Mapper.Map<Membership>(membership);
}
responseModel.consumers.Add(consumer);
}
}
return responseModel;
}
While this code is doing the mapping I need it to do and everything works fine, I'm trying to figure out how I can make one call to Mapper.Map and it just automaps everything in this collection for me instead of having this extra method iterating through and just have it all being done in my AutoMapperConfig class.
I'm having a tough time wrapping my head around how to get that piece to work as everything I have tried like doing .AfterMap in the ConsumerData, Consumer mapping just ends up creating empty memberships. I haven't mapped ConsumerResourceModel yet as I figured once I learned how to map the memberships then I could move a level up to map that all correctly.

How to exclude all virtual properties from classes inheritted from ClassMapper<T> by default?

I have a lot of POCO classes that contain several virtual properties each. Something like this:
public class Policy
{
public int Id { get; set; }
public int EntityId { get; set; }
public int ProgramId { get; set; }
public string PolicyNumber { get; set; }
public DateTime EffectiveDate { get; set; }
public DateTime ExpirationDate { get; set; }
public virtual Entity Entity{ get; set; }
public virtual Program Program { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
}
To make Dapper.Extensions work, I need to write a mapping for each of these classes, which is fine. My problem is, if there are any virtual properties inside a class, they need to be explicitly marked as ignored, which I always forget to do.
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
Map(p => p.Entity).Ignore();
Map(p => p.Program).Ignore();
Map(p => p.Transactions).Ignore();
AutoMap();
}
}
What would be great for me, if the Dapper.Extensions library will automatically exclude virtual properties, if any, when mapped to the POCO class. There is an extension for Automapper that does something similar (link). Is there a way to do that for Dapper.Extensions library? Possibly something like this:
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
IgnoreAllVirtual();
AutoMap();
}
}
I found my own solution. Since all my mapping classes derive from BaseMapper class, I decided to override AutoMap() method that will exclude virtual properties:
public class BaseMapper<T> : ClassMapper<T> where T : BaseClass
{
public BaseMapper()
{
}
protected override void AutoMap()
{
CustomAutoMap(null);
}
private void CustomAutoMap(Func<Type, PropertyInfo, bool> canMap)
{
Type type = typeof(T);
bool hasDefinedKey = Properties.Any(p => p.KeyType != KeyType.NotAKey);
PropertyMap keyMap = null;
foreach (var propertyInfo in type.GetProperties())
{
// Exclude virtual properties
if (propertyInfo.GetGetMethod().IsVirtual)
{
continue;
}
if (Properties.Any(p => p.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
if ((canMap != null && !canMap(type, propertyInfo)))
{
continue;
}
PropertyMap map = Map(propertyInfo);
if (!hasDefinedKey)
{
if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
{
keyMap = map;
}
if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
{
keyMap = map;
}
}
}
if (keyMap != null)
{
keyMap.Key(PropertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
? PropertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
: KeyType.Assigned);
}
}
}
}

Is EntityFramework performance affected by specifying information like primary key?

Say I have this simple entity:
public class Person
{
public int PersonID { get; set; }
public string Name { get; set; }
}
The entity framework can infer by convention that the PersonID field is the primary key. However, if I give the model builder this class:
public class PersonCfg : EntityTypeConfiguration<Person>
{
public PersonCfg()
{
ToTable("Person", "Person");
HasKey(p => p.PersonID);
}
}
Would that improve startup performance? My thought was it might allow EF to do less reflecting and startup the app faster but I don't know how it works behind the scenes to know if it has any impact.
To test this, you can use the DbModelBuilder class to build the model yourself and track the speed of the "Compile" step.
Here's my example code (LinqPad script):
void Main()
{
// Initialize the overall system, but don't count the result.
BuildC();
DateTime startDateA = DateTime.Now;
BuildA();
DateTime.Now.Subtract(startDateA).TotalMilliseconds.Dump("A");
DateTime startDateB = DateTime.Now;
BuildB();
DateTime.Now.Subtract(startDateB).TotalMilliseconds.Dump("B");
}
public class PersonA
{
public int PersonAId { get; set; }
public string Name { get; set; }
}
private void BuildA()
{
var builder = new DbModelBuilder();
builder.Entity<PersonA>();
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
public class PersonB
{
public int PersonBId { get; set; }
public string Name { get; set; }
}
private void BuildB()
{
var builder = new DbModelBuilder();
builder.Conventions.Remove<IdKeyDiscoveryConvention>();
builder.Entity<PersonB>()
.HasKey(p => p.PersonBId);
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
public class PersonC
{
public int PersonCId { get; set; }
public string Name { get; set; }
}
private void BuildC()
{
var builder = new DbModelBuilder();
builder.Entity<PersonC>()
.HasKey(p => p.PersonCId);
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
I get the result of 2.0004ms to 2.0009ms. Curiously, removing conventions made the operation take longer.

Categories

Resources