So I have "entities" and "dtos".
Department has N Employees Employee has 1 (parent) Department.
Right now I have no deviation with property names.
My EFCore "query" is getting All Departments with an Include (child) Employees.
I prefer to isolate my "mapping" code to an interface and concrete. My concrete will inject Mapster dependency.
// Entities
using System;
[Serializable]
public partial class DepartmentEntity
{
public int DepartmentKey { get; set; } /* PK */
public string DepartmentName { get; set; }
}
public partial class DepartmentEntity
{
public DepartmentEntity()
{
this.Employees = new List<EmployeeEntity>();
}
public ICollection<EmployeeEntity> Employees { get; set; }
}
using System;
[Serializable]
public partial class EmployeeEntity
{
public int EmployeeKey { get; set; } /* PK */
public string LastName { get; set; }
public string FirstName { get; set; }
}
public partial class EmployeeEntity
{
public DepartmentEntity ParentDepartment { get; set; }
}
Dtos:
// Different CsProject
// Dtos
using System;
[Serializable]
public partial class DepartmentDto
{
public int DepartmentKey { get; set; } /* PK */
public string DepartmentName { get; set; }
}
public partial class DepartmentDto
{
public DepartmentDto()
{
this.Employees = new List<EmployeeDto>();
}
public ICollection<EmployeeDto> Employees { get; set; }
}
using System;
[Serializable]
public partial class EmployeeDto
{
public int EmployeeKey { get; set; } /* PK */
public string LastName { get; set; }
public string FirstName { get; set; }
}
public partial class EmployeeDto
{
public DepartmentDto ParentDepartment { get; set; }
}
and CustomerMapper interface and concrete
using System.Collections.Generic;
public interface IDepartmentConverter
{
DepartmentDto ConvertToDto(DepartmentEntity entity);
ICollection<DepartmentDto> ConvertToDtos(ICollection<DepartmentEntity> entities);
DepartmentEntity ConvertToEntity(DepartmentDto dto);
ICollection<DepartmentEntity> ConvertToEntities(ICollection<DepartmentDto> dtos);
}
using System;
using System.Collections.Generic;
using Mapster;
using MapsterMapper;
public class DepartmentConverter : IDepartmentConverter
{
public const string ErrorMessageIMapperNull = "IMapper is null";
private readonly IMapper mapper;
public DepartmentConverter(IMapper mapper)
{
this.mapper = mapper ?? throw new ArgumentNullException(ErrorMessageIMapperNull, (Exception)null);
}
public DepartmentDto ConvertToDto(DepartmentEntity entity)
{
return this.mapper.Map<Department>(entity);
}
public ICollection<DepartmentDto> ConvertToDtos(ICollection<DepartmentEntity> entities)
{
return this.mapper.Map<ICollection<Department>>(entities);
}
public ICollection<DepartmentEntity> ConvertToEntities(ICollection<DepartmentDto> dtos)
{
return this.mapper.Map<ICollection<DepartmentEntity>>(dtos);
}
public DepartmentEntity ConvertToEntity(DepartmentDto dto)
{
return this.mapper.Map<DepartmentEntity>(dto);
}
}
So after my EF Get All Departments, Include Employees call, I have a fully hydrated
ICollection<DepartmentEntity> departmentsWithEmps
at my disposal.
But when it goes through the convert/map code....I get a "Stack Overflow" exception.
I'm pretty sure I know why. It is the Employee property of "ParentDepartment".... aka a "reciprocal" property on the child.
With Newtonsoft, one of the "fixes" is usually this
var json = JsonConvert.SerializeObject(harry,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
Does Mapster have a configuration to deal with this scenario?
Below is my IoC registration attempt(s).......I have tried, but am getting no-where fast.
namespace MyStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mapster;
using MapsterMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public class Startup
{
private string tempDebuggingConnectionString = string.Empty;
public Startup(IConfiguration configuration, IWebHostEnvironment iwhe)
{
this.Configuration = configuration;
this.WebHostEnvironment = iwhe;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
Type iregisterType = typeof(IRegister);
IEnumerable<Assembly> iregisterTypeAssemblies = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from assemblyType in assembly.GetTypes()
where assemblyType.GetInterfaces().Contains(iregisterType)
select assembly;
//TypeAdapterConfig.GlobalSettings.Scan(iregisterTypeAssemblies.Distinct().ToArray());
// TypeAdapterConfig config = new TypeAdapterConfig();
// Or
//TypeAdapterConfig.GlobalSettings.Default.ShallowCopyForSameType(true);
TypeAdapterConfig config = TypeAdapterConfig.GlobalSettings;
// config.NewConfig<DepartmentDto, DepartmentEntity>()
//.ShallowCopyForSameType(true);
// config.NewConfig<DepartmentEntity, DepartmentDto>()
// .ShallowCopyForSameType(true);
// config.NewConfig<DepartmentEntity, DepartmentDto>()
// .Map(dest => dest.Employees, src => src.Employees);
// TypeAdapterSetter<DepartmentEntity, Department> orgSetter = TypeAdapterConfig<DepartmentEntity, Department>
//.NewConfig()
//.ShallowCopyForSameType(true);
// orgSetter.Config = config;
services.AddSingleton(config);
//services.AddSingleton(orgSetter);
services.AddScoped<IMapper, ServiceMapper>();
}
public void Configure(ILogger<Startup> logger, IApplicationBuilder app, IWebHostEnvironment env)
{
/* not shown */
}
}
}
And while you can guess, my EFCore code looks like this:
public async Task<IEnumerable<DepartmentDto>> GetAllAsync(CancellationToken token)
{
List<DepartmentEntity> entities = await this.entityDbContext.Departments.Include(ent => ent.ApplicationDetails).AsNoTracking().ToListAsync(token);
try
{
/* below is injected, but new'ing it up here for SOF question */
IDepartmentConverter localNonInjectedConverter = new DepartmentConverter(/* again, not my real code....my IoC has the Mapster object */);
ICollection<DepartmentDto> returnItems = localNonInjectedConverter.ConvertToDtos(entities);
return returnItems;
}
catch (Exception ex)
{
throw ex;
}
}