How to use ForMember properly inside a collection? - c#

I have my source class as
public class SourceEmployee
{
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<ResidentialAddress> EmployeeResidences { get; set; }
}
and the ResidentialAddress is as under
public class ResidentialAddress
{
public string State { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
}
The Destination class is as under
public class DestinationEmployee
{
public int EmployeeID { get; set; }
public string FullName { get; set; }
public List<ResidentialAddress1> Address { get; set; }
}
public class ResidentialAddress1
{
public string FullAddress { get; set; }
}
How to perform the ForMember for the FullAddress which will be State + City + ZipCode ?
I am lost after
Mapper.CreateMap<SourceEmployee, DestinationEmployee>();
Mapper.CreateMap<SourceEmployee, DestinationEmployee>().
ForMember(f => f.FullName, f => f.MapFrom(a => string.Concat(a.FirstName, " ", a.LastName)))
.ForMember(x => x.EmployeeResidences1, x => x.MapFrom(y => string.Concat(y.EmployeeResidences.m, " ", y.LastName)));

Well, you can use LINQ to let AutoMapper know how to map 3 properties into one property and you shouldn't really use Mapper.CreateMap() as it is deprecated and will not be supported from version 5.0 - use Mapper.Initialize() instead.
Let's have a look at this example :
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceEmployee, DestinationEmployee>();
cfg.CreateMap<SourceEmployee, DestinationEmployee>()
.ForMember(f => f.FullName, f => f.MapFrom(a => string.Concat(a.FirstName, " ", a.LastName)))
.ForMember(
x => x.Address,
x => x.MapFrom(
y => y.EmployeeResidences.Select(
r => new ResidentialAddress1()
{
FullAddress = String.Concat(
r.State, " ", r.City, " ", r.ZipCode)
}).ToList()));
});
SourceEmployee emp = new SourceEmployee()
{
EmployeeID = 1,
FirstName = "Alex",
LastName = "Green",
EmployeeResidences = new List<ResidentialAddress>()
{
new ResidentialAddress() { State = "abc", City = "def", ZipCode = 110 },
new ResidentialAddress() { State = "foo", City = "qwe", ZipCode = 220 },
new ResidentialAddress() { State = "bar", City = "ert", ZipCode = 330 },
}
};
var sourceEmp = Mapper.Map<SourceEmployee, DestinationEmployee>(emp);
Console.WriteLine(sourceEmp.Address.Count);
Console.WriteLine(sourceEmp.Address[1].FullAddress);
Output :
3
foo qwe 220

You should have a separate map profile for your ResidentialAddress -> ResidentialAddress1 transformation. When your parent object will be transformed using automapper, all child objects will get transformed using defined map profiles:
Mapper.CreateMap<SourceEmployee, DestinationEmployee>()
.ForMember(f => f.FullName, f => f.MapFrom(a => string.Concat(a.FirstName, " ", a.LastName)))
.ForMember(x => x.Address, x => x.MapFrom(y => y.EmployeeResidences)));
Mapper.CreateMap<ResidentialAddress, ResidentialAddress1>
.ForMember(x => x.FullAddress, map=>map.From(from => string.Format("{0} {1} {2}", from.State, from.City, from.ZipCode);
This way if in code you will have to transform ResidentialAddress to ResidentialAddress1 in multiple places, you won't have to add any code, just use Mapper.Map<>.
I also suggest you to switch to profiles, instead of defining your mapper configurations inline: https://github.com/AutoMapper/AutoMapper/wiki/Configuration

Related

Unflattening data list into a nested list using AutoMapper

I have a flatten data list (counties and cities) and I want to map into a unflatten (counties with cities) nested property list (State.County). Please check the following .NET 6.0 console application code:
using AutoMapper;
public class CountyAndCity
{
public int CountyId { get; set; }
public string CountyName { get; set; }
public int CityId { get; set; }
public string CityName { get; set; }
public int CityPopulation { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int Population { get; set; }
}
public class County
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class State
{
public List<County> Counties { get; set; }
}
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<CountyAndCity, City>()
.ForMember(destination => destination.Id, options => options.MapFrom(source => source.CountyId))
.ForMember(destination => destination.Name, options => options.MapFrom(source => source.CountyName))
.ForMember(destination => destination.Population, options => options.MapFrom(source => source.CityPopulation));
CreateMap<IEnumerable<CountyAndCity>, County>()
.ForMember(destination => destination.Id, options => options.MapFrom(source => source.FirstOrDefault().CountyId))
.ForMember(destination => destination.Name, options => options.MapFrom(source => source.FirstOrDefault().CountyName))
.ForMember(destination => destination.Cities, options => options.MapFrom(source => source));
CreateMap<IEnumerable<CountyAndCity>, State>()
.ForMember(destination => destination.Counties, options => options.MapFrom(source => source));
}
}
class HelloWorld
{
static void Main()
{
var flattenData = new List<CountyAndCity>
{
new CountyAndCity { CountyId = 1, CountyName = "Albany", CityId = 10, CityName = "Cohoes", CityPopulation = 1000 },
new CountyAndCity { CountyId = 1, CountyName = "Albany", CityId = 20, CityName = "Watervliet", CityPopulation = 1000 },
new CountyAndCity { CountyId = 2, CountyName = "Bronx", CityId = 30, CityName = "Wakefield", CityPopulation = 2000 },
new CountyAndCity { CountyId = 2, CountyName = "Bronx", CityId = 40, CityName = "Eastchester", CityPopulation = 2000 },
};
var unflattenData = new List<County>
{
new County { Id = 1, Name = "Albany", Cities = new List<City>
{
new City { Id = 10, Name = "Cohoes", Population = 1000 },
new City { Id = 20, Name = "Watervliet", Population = 1000 },
}},
new County { Id = 2, Name = "Bronx", Cities = new List<City>
{
new City { Id = 30, Name = "Wakefield", Population = 2000 },
new City { Id = 40, Name = "Eastchester", Population = 2000 },
}},
};
var mapperConfiguration = new MapperConfiguration(configuration => configuration.AddProfile(new AutoMapperProfile()));
var mapper = mapperConfiguration.CreateMapper();
State mappedData = mapper.Map<State>(flattenData);
//Should be TRUE after some kind of custom comparison but it is goof enough for the example.
bool result = mappedData.Counties == unflattenData;
}
}
I'm receiving the following exception:
/*
Error mapping types.
Mapping types:
IEnumerable`1 -> State
System.Collections.Generic.IEnumerable`1[[CountyAndCity, ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> State
Type Map configuration:
IEnumerable`1 -> State
System.Collections.Generic.IEnumerable`1[[CountyAndCity, ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> State
Destination Member:
Counties
*/
What I'm doing wrong? What mappers should I create to transform the unflatten list into the flatten nested property: "Counties"?
You may look for the Custom Value Resolver to group data for County.
public class StateCountiesResolver : IValueResolver<List<CountyAndCity>, State, List<County>>
{
public List<County> Resolve(List<CountyAndCity> src, State dest, List<County> destMember, ResolutionContext ctx)
{
destMember = src.GroupBy(x => x.CountyId)
.Select(x => new County
{
Id = x.Key,
Name = x.FirstOrDefault()?.CountyName,
Cities = ctx.Mapper.Map<List<City>>(x.ToList())
})
.ToList();
return destMember;
}
}
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<CountyAndCity, City>()
.ForMember(destination => destination.Id, options => options.MapFrom(source => source.CityId))
.ForMember(destination => destination.Name, options => options.MapFrom(source => source.CityName))
.ForMember(destination => destination.Population, options => options.MapFrom(source => source.CityPopulation));
CreateMap<List<CountyAndCity>, State>()
.ForMember(destination => destination.Counties, options => options.MapFrom<StateCountiesResolver>());
}
}
Demo # .NET Fiddle
While with the below statement:
bool result = mappedData.Counties == unflattenData;
you will still get false when comparing both lists (even the contents of both lists are the same) as the references are used to compare instead of the content of both lists:
Instead, you need to override the Equals() and GetHashCode() methods.
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int Population { get; set; }
public override bool Equals (Object obj)
{
if (obj is not City)
return false;
City b = (City)obj;
return Id == b.Id
&& Name == b.Name
&& Population == b.Population;
}
public override int GetHashCode()
{
return this.Id.GetHashCode() ^ this.Name.GetHashCode() ^ this.Population.GetHashCode();
}
}
public class County
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<City> Cities { get; set; }
public override bool Equals (Object obj)
{
if (obj is not County)
return false;
County b = (County)obj;
return Id == b.Id
&& Name == b.Name
&& Enumerable.SequenceEqual(Cities, b.Cities);
}
public override int GetHashCode()
{
return this.Id.GetHashCode() ^ this.Name.GetHashCode();
}
}
And use Enumerable.SequenceEqual() method to compare the contents of the lists.
bool result = Enumerable.SequenceEqual(mappedData.Counties, unflattenData);

How to map two classes using Auto Mapper in c# when the destination class has object arrays

Below is the mapper snippet with classes :
Mapper.Initialize(cfg =>
{
cfg.CreateMap<STest, ETest>().ForMember(d => d.customFields[0].stringValue, o => o.MapFrom(s => s.val));
}
);
var result = Mapper.Map<ETest>(JsonDeseriazedSource);
var serialized = JsonConvert.SerializeObject(result, Formatting.Indented);
And these are the source and destination classes to be mapped:
//source class
public class STest
{
public string Id { get; set; }
public string val { get; set; }
}
// destination class
public class ETest
{
public string Id { get; set; }
public Customfield[] customFields { get; set; }
}
public class Customfield
{
public string id { get; set; }
public string fieldName { get; set; }
public string stringValue { get; set; }
}
I need to map stringValue in Customfield object(destination) from val in Stest(source).
Thanks in advance!!
If I understand your question correctly, you can try this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<STest, Customfield>()
.ForMember(d => d.stringValue, o => o.MapFrom(s => s.val));
cfg.CreateMap<STest[], ETest>()
.ForMember(d => d.customFields, o => o.MapFrom(s => s.Select(Mapper.Map<Customfield>)));
});
var sTests= new[] { new STest { Id = "1", val = "val1" }, new STest { Id = "2", val = "val2" } };
var result = Mapper.Map<ETest>(sTests);

Nest ignoring my percolate query property

I'm indexing a type that has percolate query but Nest/elasticsearch choose to ignore the query property.
public class MyQueryModel
{
public string Id { get; set; }
public string UserId { get; set;}
public string Email { get ; set;}
public string Name { get; set; }
public string State { get; set; }
public QueryContainer PercolatedQuery { get; set; }
}
public class DocModel
{
public string Id { get; set; }
public string Title { get; set; }
public string State { get; set; }
public string Category { get; set;}
public string Email { get; set; }
}
EDIT: some of property names between the 2 are same by coincidence. They totally mean different things on either of the 2 models and maybe mapped differently.
my mappings:
on queries index:
client.CreateIndex("on_my_queries", c => c
.Mappings(m => m
.Map<MyQueryModel>(mq => mq
.AutoMap()
.Properties(props => props
.Percolator(perc => perc
.Name(m => m.PercolatedQuery)
)
)
)
)
)
on doc index
client.CreateIndex("on_my_docs", c => c
.Mappings(m => m
.Map<MyDocModel>(md => md
.AutoMap()
)
)
)
Indexing my query model:
var queryModel = new MyQueryModel
{
Id = "some-id",
UserId = "some-user-id",
Email = "some-valid-email",
State = "some-valid-state",
PercolatedQuery = new TermQuery
{
Field = "category",
Value = "some-valid-cat-on-my-doc-models"
}
}
var request = new IndexRequest<QueryModel>(DocumentPath<MyQueryModel>.Id(queryModel));
var result = client.Index(request);
Everything gets indexed except the PercolatedQuery field. After scratching a lot of my head, I find out that client is not even serializing it. I ran the following only to see that PercolatedQuery was not serialized:
var jsonString = client.Serializer.SerializeToString(request);
jsonString:
{
"id" : "some-id",
"userId" : "some-user-id",
"email: : "some-valid-email",
"state" : "some-valid-state"
}
What client see as percolated query:
var queryString = client.Serializer.SerializeToString(queryModel.PercolatedQuery);
queryString:
{
"term": {
"category": {
"value": "some-valid-cat-on-my-doc-models"
}
}
}

Automapper behavior differs when mapping lists compared to singe objects

I have two objects:
public class Person1
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Person2
{
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public int? Foo { get; set; }
}
The difference between the two is that the destination object has the additional property named Foo. I would like Automapper to use the destination value of Foo when mapping between a list of Person1 to Person2.
When I map individual objects I can achieve this behaviour. However when I map a collection of Person1 to Person2 the value of Foo is set to null, instead of being equal to 1. Please see my unit test below.
[TestMethod]
public void TestUseDestValWhenNotFoundIgnore()
{
Mapper.CreateMap<Person1, Person2>()
.ForMember(dest => dest.FIRST_NAME, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LAST_NAME, opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.Foo, opt =>
{
opt.UseDestinationValue();
});
var personList1 = new List<Person1>();
var personList2 = new List<Person2>();
for (int i = 0; i < 50; i++)
{
personList1.Add(new Person1
{
FirstName = "FirtName",
LastName = "LastName"
});
personList2.Add(new Person2
{
FIRST_NAME = "",
LAST_NAME = "",
Foo = 1
});
}
var sourcePerson = new Person1
{
FirstName = "FirstName",
LastName = "LastName"
};
var destinationPerson = new Person2
{
FIRST_NAME = "",
LAST_NAME = "",
Foo = 1
};
// This works as expected, the assert is successful, Foo = 1
Mapper.Map(sourcePerson, destinationPerson);
Assert.IsTrue(destinationPerson.Foo == 1);
// This assert fails, Foo is null for every property
Mapper.Map(personList1, personList2);
Assert.IsTrue(personList2.All(p => p.Foo == 1));
}

Mapping A Property Of List<T> Using Automapper

Using Automapper, I want to map a property that is a List of type Employee using string.Join() to product a comma-delimited string of the names of an employee's rights. Here are the classes I'm using:
public class MappedEmployee
{
public string Name { get; set; }
public string RightNames { get; set; }
}
public class Employee
{
public string Name { get; set; }
public List<Right> Rights { get; set; }
}
public class Right
{
public string Name { get; set; }
}
And here is the code I have:
Mapper.CreateMap<Employee, MappedEmployee>()
.ForMember(d => d.RightNames, o => o.MapFrom(s => s.Rights.SelectMany(r => string.Join(", ", r.Name))));
var employee = new Employee
{
Name = "Joe Schmoe",
Rights = new List<Right>
{
new Right { Name = "Admin" },
new Right { Name = "User" },
}
};
var mappedEmployee = Mapper.Map<Employee, MappedEmployee>(employee);
However, this it's producing the folowing:
System.Linq.Enumerable+<SelectManyIterator>d__14`2[Employee.Right,System.Char]
What can I do do get a comma-delimited string of the Employee's rights?
Try using ResolveUsing instead and putting string.Join before the selection:
Mapper.CreateMap<Employee, MappedEmployee>()
.ForMember(d => d.RightNames, o => o.ResolveUsing(s => string.Join(", ",s.Rights.Select(r => r.Name))));

Categories

Resources