AutoMapper to map a child list object - c#

I'm using an generic method to map two classes using Automapper
My generic methods
public class AutoMapperConfiguration
{
public MapperConfiguration Configure<TSource, TDestination>() where TSource:class where TDestination:class
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ClientMappingProfile<TSource,TDestination>>();
});
return config;
}
}
ClientMappingProfile.cs
public class ClientMappingProfile<TSource,TDestination>: Profile where TSource : class where TDestination:class
{
public ClientMappingProfile()
{
CreateMap<TSource, TDestination>().ReverseMap();
}
}
StudentDetailsViewModel.cs
public class StudentDetailsViewModel
{
public long ID { get; set; }
public string FirstName { get; set; }
public List<QualificationViewModel> listQualificationViewModel { get; set; }
}
QualificationViewModel.cs
public class QualificationViewModel
{
public long ID { get; set; }
public long StudentID { get; set; }
public string ExaminationPassed { get; set; }
}
StudentValueObject.cs
public class StudentValueObject
{
public long ID { get; set; }
public string FirstName { get; set; }
public List<StudentQualificationValueObject> listStudentQualificationValueObject { get; set; }
}
StudentQualificationValueObject.cs
public class StudentQualificationValueObject
{
public long ID { get; set; }
public long StudentID { get; set; }
public string ExaminationPassed { get; set; }
}
Usage
StudentValueObject studentValueObject = new StudentValueObject();
var config = new AutoMapperConfiguration().Configure<StudentValueObject, StudentDetailsViewModel>();
var iMapper = config.CreateMapper();
studentValueObject = iMapper.Map<StudentDetailsViewModel, StudentValueObject>(objStudentModel);
So, this works fine with Mapping StudentDetailsViewModel.cs with StudentValueObject.cs. But it silently fails to copy my child list objects which is List<QualificationViewModel> to List<StudentQualificationValueObject>. The child list object always seems to be null. I'm pretty newbie to AutoMapper. I need some help as to know where am I going wrong or what need to be added/fixed to my generic method, so that the child list object gets copied to with Parent object.
Update -
Currently I'm doing it using below code and its working properly but I'm confused is this the proper way of doing this.
StudentValueObject studentValueObject = new StudentValueObject();
var config = new AutoMapperConfiguration().Configure<StudentValueObject, StudentDetailsViewModel>();
var iMapper = config.CreateMapper();
studentValueObject = iMapper.Map<StudentDetailsViewModel, StudentValueObject>(objStudentModel);
config = new AutoMapperConfiguration().Configure<StudentQualificationValueObject, QualificationViewModel>();
iMapper = config.CreateMapper();
studentValueObject.listStudentQualificationValueObject = iMapper.Map<List<QualificationViewModel>, List<StudentQualificationValueObject>>(objStudentModel.listQualificationViewModel);

You have to map the list properties, cause they have different names in the given parent types and you have to add a mapping for the types used within both lists. Here is an working example for your code:
public class StudentsMappingProfile : Profile
{
public StudentsMappingProfile()
{
CreateMap<StudentValueObject, StudentDetailsViewModel>()
.ForMember(viewModel => viewModel.listQualificationViewModel, conf => conf.MapFrom(value => value.listStudentQualificationValueObject));
CreateMap<StudentQualificationValueObject, QualificationViewModel>();
}
}
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<StudentsMappingProfile>());
var mapper = config.CreateMapper();
var source = new StudentValueObject { ID = 73, FirstName = "Hello", listStudentQualificationValueObject = new List<StudentQualificationValueObject> { new StudentQualificationValueObject { ID = 42, StudentID = 17, ExaminationPassed = "World" } } };
var destination = mapper.Map<StudentDetailsViewModel>(source);
Console.ReadKey();
}
}

Related

Create Object Using Constructor and Argument with AutoMapper

I have a scenario where I want to create an object (Calc) which takes some options as a constructor argument. It happens I have a serialized version of Calc, its properties and the options properties as another single object. Here's my code:
void Main()
{
var mockMapper = new MapperConfiguration(c =>
{
c.AddProfile<MapperProf>();
})
.CreateMapper();
}
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Calc>()
.ConstructUsing(c => new Calc()) // I AM STUCK HERE
CreateMap<Scen, Opt>()
.ForMember(o => o.OptProp, o => o.MapFrom(o => o.OptProp));
}
}
public class Calc
{
public Calc(Opt opt)
{
OptProp = opt.OptProp;
}
public string CalcProp { get; set; }
private string OptProp { get; set; }
}
public class Opt
{
public string OptProp { get; set; }
}
public class Scen
{
public string CalcProp { get; set; }
public string OptProp { get; set; }
}
For various reasons I cannot access Calc.OptProp, I have to pass it in via the constructor argument.
In equivalent terms what I want to do in one shot is:
Calc c = mockMapper.Map<Calc>().ConstructUsing(c => new Calc(mockMapper.Map<Opt>(scen)));
That is, construct both the Calc and Opt from the same Scen.
In the ConstructUsing you can use ResulotionContext and then use mapper for create constructor like this:
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Opt>().ForMember(o => o.OptProp, o => o.MapFrom(scen => scen.OptProp));
CreateMap<Scen, Calc>().ConstructUsing((scen, context) => new Calc(context.Mapper.Map<Scen, Opt>(scen)));
}
};
And for mapping scen:
var scen = new Scen() { CalcProp = "Calc", OptProp = "Opt" };
var calc = mockMapper.Map<Scen, Calc>(scen);

How to recursively map one nested object type to another

I would like to map the data from one list of objects and another. I am looping through CompanyAEmployee list and able to map FullName and Title. But not able to map Children property.
public class CompanyAEmployee
{
public string FullName { get; set; }
public string Title { get; set; }
public List<CompanyAEmployee> Children { get; set; }
}
public class CompanyBEmployee
{
public string Name { get; set; }
public string PositionName { get; set; }
public List<CompanyBEmployee> Children { get; set; }
}
companyAEmployeeList; // stores all employees of companyA
var companyBEmployeeList = new List<CompanyBEmployee>();
foreach(var employee in companyAEmployeeList)
{
var companyBEmployee = new CompanyBEmployee();
companyBEmployee.Name = employee.FullName;
companyBEmployee.PositionName = employee.Title;
//how to map the children??
}
Can someone suggest a way to map Children?
You can create a recusive method, like below:
public CompanyBEmployee ComAToComB(CompanyAEmployee a){
CompanyBEmployee b = new(){
Name = a.FullName,
PositionName = a.Title,
Children = new()
};
foreach(var child in a.Children){
b.Children.Add(ComAToComB(child));
}
return b;
}
And then call it like
var comB = ComAToComB(comA);
So, in my case it was best solution to use extension methods.
You can see example here
https://dotnetfiddle.net/SwhGMY
Main idea is to use such extension method that was called recursively.
public static class ClassConverterExtensions
{
public static CompanyBEmployee ToCompanyBEmployee(this CompanyAEmployee that)
{
var result = new CompanyBEmployee();
result.Name = that.FullName;
result.PositionName = that.Title;
if(that.Children == null)
{
return result;
}
result.Children = new List<CompanyBEmployee>();
foreach(var item in that.Children)
{
result.Children.Add(item.ToCompanyBEmployee());
}
return result;
}
}
Good point is that you can write this without changing source code of classes CompanyBEmployee and CompanyAEmployee
So, this classes is not referenced one to another, but you can write converters From A to B and from B to A without cyclic references.
Here is the solution:
public class CompanyAEmployee
{
public string FullName { get; set; }
public string Title { get; set; }
public List<CompanyAEmployee> Children { get; set; }
public static explicit operator CompanyBEmployee(CompanyAEmployee employee)
{
CompanyBEmployee employee1 = new CompanyBEmployee();
employee1.Name = employee.FullName;
employee1.PositionName = employee.Title;
employee1.Children = new List<CompanyBEmployee>(employee.Children.Count);
int count = employee.Children.Count;
for (int i = 0; i < count; i++)
employee1.Children[i] = (CompanyBEmployee)employee.Children[i];
return employee1;
}
}
public class CompanyBEmployee
{
public string Name { get; set; }
public string PositionName { get; set; }
public List<CompanyBEmployee> Children { get; set; }
}
Now you can just use an assignment operator with explicit casting from CompanyAEmployee to CompanyBEmployee.
Like this:
CompanyAEmployee employee = new CompanyAEmployee();
//... assign all the fields
CompanyBEmployee employee2 = (CompanyBEmployee)employee.
Now you are done!

ASP.NET C# OData Service + Navigation Property + $expand = null. What am I missing?

I will try to explain my problem as thoroughly as possible with a simplified example. Please note that I am NOT using Entity Framework.
I have this model:
public class Person
{
[Key]
public Guid Id { get; set; }
public string GivenName { get; set; }
public string FamilyName { get; set; }
public List<Employment> Employments { get; set; }
}
public class Employment
{
public string Role { get; set; }
public Guid? ManagerId { get; set; }
public virtual Person Manager { get; set; }
}
I create an in-memory data source:
public class MyDataSource
{
private static MyDataSource instance = null;
public static MyDataSource Instance
{
get
{
if (instance == null)
{
instance = new MyDataSource();
}
return instance;
}
}
public List<Person> Persons { get; set; }
private MyDataSource()
{
this.Persons = new List<Person>();
this.Persons.AddRange(new List<Person>
{
new Person()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"), //Just for simplicity
GivenName = "John",
FamilyName = "Doe",
Employments = new List<Employment>()
{
new Employment()
{
Role = "Boss"
}
}
},
new Person()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000002"), //Just for simplicity
GivenName = "Clark",
FamilyName = "Kent",
Employments = new List<Employment>()
{
new Employment()
{
Role = "Worker",
ManagerId = Guid.Parse("00000000-0000-0000-0000-000000000001"), //Just for simplicity
}
}
}
});
}
}
I have this controller:
[EnableQuery]
public class PersonsController : ODataController
{
public IHttpActionResult Get()
{
return Ok(MyDataSource.Instance.Persons)
}
}
I configure the EndPoint:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata", CreateEdmModel());
config.Select().Expand().Filter().OrderBy().MaxTop(null).Count()
}
public static IEdmModel CreateEdmModel()
{
var builder = new ODataConventionModelBuilder();
var persons = builder.EntitySet<Person>("Persons");
builder.ComplexType<Employment>().HasOptional(e => e.Manager, (e, p) => e.ManagerId == p.Id);
return builder.GetEdmModel();
}
}
Checking the $metadata I see this:
<NavigationProperty Name="Manager" Type = "MyNamespace.Person">
<ReferentialConstraint Property="ManagerId" ReferenceProperty="Id" />
</NavigationProperty
Everything looks fine from what I can tell but:
https://example.com/odata/persons?$expand=Employments/Manager
receives everything fine but:
Manager is null for both persons. I was expecting to see John Doe on Clark Kents employment.
What am I missing?
I have solved it myself.
I realised that it doesn't work like I thought and that I have to add a reference to the manager directly in MyDataSource. After that it works to $expand the manager.

Error CS0118 'TestBuilder' is a namespace but is used like a type

I'm working on Unit test. I create TestBuilder class, where I create method SetupTown method.
When I tried call this method in my main test class - i have error( Error CS0118'TestBuilder' is a namespace but is used like a type). I read about it and recommend to call a class with method. I tried do it, but It doesn't help.
public partial class TownServiceTests
public partial class TownServiceTests
{
private class TestBuilder
{
public Mock<ITownRepository> MockTownRepository { get; set; }
//public Mock<IClientViewModelBuilder> MockClientPropertyModelBuilder { get; set; }
public Mock<IMapper> MockMapper { get; set; }
public TestDataGenerator TestDataGenerator;
private readonly string _jsonDataPath = #"../../../TestData/Town/TownTestData.json";
private string _jsonDataKey;
private TownViewModel Towns { get; set; }
public TestBuilder(string jsonDataKey)
{
MockTownRepository = new Mock<ITownRepository>();
//MockClientPropertyModelBuilder = new Mock<IClientViewModelBuilder>();
MockMapper = new Mock<IMapper>();
TestDataGenerator = new TestDataGenerator(_jsonDataPath);
_jsonDataKey = jsonDataKey;
TestDataGenerator.LoadData(_jsonDataKey);
}
public ITownService Build()
{
return new TownService(MockTownRepository.Object,
MockMapper.Object);
}
public TestBuilder SetupTowns()
{
var towns = TestDataGenerator.GetTestData<Town>(_jsonDataKey, "Towns");
MockTownRepository.Setup(r => r.InsertTown(It.IsAny<string>()))
.ReturnsAsync(towns.FirstOrDefault().Id);
return this;
}
}
}
}
Please check method public TestBuilder SetupTowns
Here my TestClass
[TestClass]
public partial class TownServiceTests
{
[TestMethod]
public async Task ValidInsertTown()
{
var builder = new TestBuilder("Data").SetupTowns; //Problem
var service = builder.Build();
var expectedTowns = builder.TestDataGenerator.GetTestData<Town>("Data", "Towns");
var result = await service.InsertTown(expectedTowns);
Assert.IsNotNull(result);
Assert.IsNull(result);
}
}
Could toy tell me what I do wrong?
Example
public partial class ClientServiceTests
{
private class TestBuilder
{
public Mock<IClientRepository> MockClientRepository { get; set; }
public Mock<IClientViewModelBuilder> MockClientPropertyModelBuilder { get; set; }
public Mock<IMapper> MockMapper { get; set; }
public TestDataGenerator TestDataGenerator;
private readonly string _jsonDataPath = #"../../../TestData/Client/ClientTestData.json";
private string _jsonDataKey;
public TestBuilder(string jsonDataKey)
{
MockClientRepository = new Mock<IClientRepository>();
MockClientPropertyModelBuilder = new Mock<IClientViewModelBuilder>();
MockMapper = new Mock<IMapper>();
TestDataGenerator = new TestDataGenerator(_jsonDataPath);
_jsonDataKey = jsonDataKey;
TestDataGenerator.LoadData(_jsonDataKey);
}
public IClientService Build()
{
return new ClientService(MockClientRepository.Object
, MockClientPropertyModelBuilder.Object
, MockMapper.Object);
}
public TestBuilder SetupClients()
{
var clients = TestDataGenerator.GetTestData<ClientSummary>(_jsonDataKey, "Clients");
MockClientRepository.Setup(r => r.GetClientBySearchCriteria(It.IsAny<string>()))
.ReturnsAsync(clients);
var clientViewModels = TestDataGenerator.GetTestData<ClientViewModel>(_jsonDataKey, "ClientViewModel");
MockClientPropertyModelBuilder.Setup(r => r.GetClientViewModel(clients))
.Returns(clientViewModels);
return this;
}
public TestBuilder SetupInvalidInputClients()
{
MockClientRepository.Setup(r => r.GetClientBySearchCriteria(It.IsAny<string>()))
.ReturnsAsync(new List<ClientSummary>());
MockClientPropertyModelBuilder.Setup(r => r.GetClientViewModel(new List<ClientSummary>()))
.Returns(new List<ClientViewModel>());
return this;
}
}
}
TestClass (here works good)
[TestMethod]
public async Task GetClientBySearchCriteria_ValidInput_ReturnClients()
{
var searchParameter = "1";
var builder = new TestBuilder("Data").SetupClients();
var service = builder.Build();
var expectedClients = builder.TestDataGenerator.GetTestData<ClientSummary>("Data", "Clients");
var result = await service.GetClientBySearchCriteria(searchParameter);
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
Assert.AreEqual(expectedClients.FirstOrDefault().Name, result.FirstOrDefault().Name);
}
namespace of the file
I think, the issue is happened because you have Something.TestBuilder.Something namespace and compiler is trying to use it instead of class.
You have the TestBuilder folder and a few classes inside it. It may be that classes inside TestBuilder folder contains TestBuilder in their namespaces and compiler trying to access this namespace instead of class.

Get list of properties in LINQ projection

I have a following LINQ expression:
var query = entities
.Select(e => new MyObject()
{
Property1 = e.Item1,
Property2 = e.Item2
});
MyObject might have also Property3, Property4 defined. I need to realize which properties are part of LINQ projection via expression visitor.
So I call something like:
var listOfProperties = query.GetSelectedPropertyNames();
and the content of listOfProperties will be string array which contains Property1, Property2 or something by which I can check:
var isPropertyInProjection = query.HasPropertyInProjection(nameof(MyObject.Property3));
and the result will be false.
You can easily do that using an ExpressionVisitor. Just create a new class and override the visiting methods. If you know that the projection was done using member bindings, you can simply override the method VisitMemberBinding and add the bound member to a list that you store as an instance variable. Then all you need to do is to make that instance variable public.
class ProjectionAnalyzer : ExpressionVisitor
{
private HashSet<MemberInfo> boundMembers = new HashSet<MemberInfo>();
protected override MemberBinding VisitMemberBinding(MemberBinding node)
{
boundMembers.Add(node.Member);
return base.VisitMemberBinding(node);
}
public IEnumerable<MemberInfo> BoundMembers => boundMembers;
}
Then, use this class as follows:
var analyzer = new ProjectionAnalyzer();
analyzer.Visit(selectorPredicate);
var boundMembers = analyzer.BoundMembers;
How you obtain the selector predicate depends on your LINQ provider.
I did something similar using VisitMemberAssignment:
namespace BoundPropertiesinQuery
{
static class IEnumerableExtensions
{
class ProjectedVisitor : ExpressionVisitor
{
public IList<string> ProjectedPropertyNames { get; set; } = new List<string>();
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
ProjectedPropertyNames.Add(node.Member.Name);
return base.VisitMemberAssignment(node);
}
}
public static IEnumerable<string> ProjectedProperties<T>(this IQueryable<T> #this)
{
var pv = new ProjectedVisitor();
pv.Visit(#this.Expression);
return pv.ProjectedPropertyNames.Distinct();
}
}
internal class MyObject
{
public int Property1 { get; set; }
public int Property2 { get; set; }
public int Property3 { get; set; }
public int Property4 { get; set; }
}
internal class MyOtherObject
{
public int other1 { get; set; }
public int other2 { get; set; }
public int other3 { get; set; }
public int other4 { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
var listOfItems = new List<MyOtherObject>()
{
new MyOtherObject
{
other1 = 1,
other2 = 2,
other3 = 3,
other4 = 4
},
new MyOtherObject
{
other1 = 5,
other2 = 6,
other3 = 7,
other4 = 8
}
};
var result = listOfItems.AsQueryable().Select(m => new MyObject
{
Property1 = m.other1,
Property2 = m.other2
}).ProjectedProperties();
foreach (var item in result)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
}

Categories