Automapper - Flatten with list at the same time - c#

I have a view model that I've received from the client which looks something like this
public class OrderViewModel
{
public string Name{get;set;}
public string ContactDetails {get;set;}
public List<FunkyThing_ViewModel> {get;set;}
}
public class FunkyThing_ViewModel
{
public string ThingName{get;set;}
public string Colour{get;set;}
public string Size{get;set;}
}
I wish to map this to a list of domain models where each which looks something more like this:
public class Order
{
public string Name{get;set;}
public string ContactDetails {get;set;}
public string ThingName{get;set;}
public string Colour{get;set;}
public string Size{get;set;}
}
So I'm wanting do end up with something that looks like this:
List<Order> orders = new Orders();
Mapper.CreateMap<OrderViewModel, List<Order>>();
//Something in here to ensure each funky thing creates an additional order....
Mapper.Map(viewModel, orders);

using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using NUnit.Framework;
using SharpTestsEx;
namespace StackOverflowExample.Automapper
{
public class OrderViewModel
{
public string Name { get; set; }
public string ContactDetails { get; set; }
public List<FunkyThingViewModel> FunkyThingViewModels { get; set; }
}
public class FunkyThingViewModel
{
public string ThingName { get; set; }
public string Colour { get; set; }
public string Size { get; set; }
}
public class Order
{
public string Name { get; set; }
public string ContactDetails { get; set; }
public string ThingName { get; set; }
public string Colour { get; set; }
public string Size { get; set; }
}
[TestFixture]
public class FlattenWithListTests
{
[Test]
public void FlattenListTest()
{
//arrange
var source = new OrderViewModel
{
Name = "name",
ContactDetails = "contact",
FunkyThingViewModels = new List<FunkyThingViewModel>
{
new FunkyThingViewModel {Colour = "red"},
new FunkyThingViewModel {Colour = "blue"}
}
};
Mapper.CreateMap<FunkyThingViewModel, Order>();
Mapper.CreateMap<OrderViewModel, Order>();
Mapper.CreateMap<OrderViewModel, List<Order>>()
.ConvertUsing(om => om.FunkyThingViewModels.Select(
ftvm =>
{
var order = Mapper.Map<Order>(om);
Mapper.Map(ftvm, order);
return order;
}).ToList());
//act
var mapped = Mapper.Map<List<Order>>(source);
//assert
mapped[0].Satisfy(m =>
m.Name == source.Name &&
m.ContactDetails == source.ContactDetails &&
m.Colour == "red");
mapped[1].Satisfy(m =>
m.Name == source.Name &&
m.ContactDetails == source.ContactDetails &&
m.Colour == "blue");
}
}
}

Related

Automapper based on dynamic conditions

I have multiple classes
I am using automapper to map the classes
class Country
{
public int Countryid {get;set}
public string CountryEnglishName {get;set;}
public string CountryArabicName {get;set;}
public Location Location{get;set;}
}
class Location
{
public int Locationid {get;set}
public string LocationEnglishName {get;set;}
public string LocationArabicName {get;set;}
}
class Customer
{
public int Customerid {get;set}
public string CustomerName {get;set;}
public int LocationId
public Location Location{get;set;}
public string PropertyArabicName {get;set;}
public string PropertyEnglishName{get;set;}
}
And my DTO looks like below
class CustomerDetailsReadDTO
{
public int Customerid {get;set}
public string PropertyName {get;set;}
public string CustomerName {get;set;}
public string CountryName {get;set;}
public string LocationName {get;set;}
}
If user pass API parameter as "en-US" then CountryDTO class field Country should contain with CountryEnglishName like that Location field also.
My mapping looks like below
class AutomapperProfile :Profile
{
CreateMap<Customer,CustomerDetailsReadDTO>
.ForMember(dest=>dest.CountryName,opt.MapFrom<CustomResolver,string>(src=>src.Location.Country.CountryEnglishName))
.ForMember(dest=>dest.LocationName,opt.MapFrom<CustomResolver,string>(src=>src.Location.LocationEnglishName))
.ForMember(dest=>dest.PropertyName,opt.MapFrom<CustomResolver,string>(src=>src.Location.PropertyNameEnglishName));
}
my interface and customresolver looks like below
This interface has been scoped in startup class
public interface ILanguage
{
string Language{get;set;}
}
public class CustomResolver:IMemberResolver<object,object,string,string>
{
private string _Lang;
public CustomResolver(ILanguage ilanguage)
{
_Lang = ilanguage;
}
public string Resolve(object source,object destination,string sourcemember,string destmember,ResoutionContext context)
{
var type = source.GetType();
switch(Type.Name)
{
case "Country":
var country =(Country)source;
return _Lang == "en-US" ? country.CountryEnglishName :country.CountryArabicName;
case "Location":
var location =(Location)source;
return _Lang == "en-US" ? location.locationEnglishName :location.locationArabicName;
}
}
}
Iam getting correctvalues in PropertyName after mapping.While passing ar-SA iam getting arabicpropertyname else englishpropertyname.
Here the propblem is after mapping iam getting CountryName and LocationName value as empty string .
Can we get correct values for CountryName and LocationName based on language?
Can we solve this ?
Here is a fully working sample console project, that does what you want:
using System;
using System.Diagnostics;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
namespace IssueConsoleTemplate
{
class Country
{
public int Countryid { get; set; }
public string CountryEnglishName { get; set; }
public string CountryArabicName { get; set; }
}
class Location
{
public int Locationid { get; set; }
public string LocationEnglishName { get; set; }
public string LocationArabicName { get; set; }
public Country Country { get; set; }
}
class Customer
{
public int Customerid { get; set; }
public string CustomerName { get; set; }
public int LocationId { get; set; }
public Location Location { get; set; }
public string PropertyArabicName { get; set; }
public string PropertyEnglishName { get; set; }
}
class CustomerDetailsReadDTO
{
public int Customerid { get; set; }
public string PropertyName { get; set; }
public string CustomerName { get; set; }
public string CountryName { get; set; }
public string LocationName { get; set; }
}
class AutomapperProfile : Profile
{
public AutomapperProfile(IServiceProvider serviceProvider)
{
CreateMap<Customer, CustomerDetailsReadDTO>()
.ForMember(
dest => dest.CountryName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.Location.Country.CountryEnglishName
: s.Location.Country.CountryArabicName))
.ForMember(
dest => dest.LocationName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.Location.LocationEnglishName
: s.Location.LocationArabicName))
.ForMember(
dest => dest.PropertyName,
opt => opt.MapFrom(
s => serviceProvider.GetService<ILanguage>().Language == "en-US"
? s.PropertyEnglishName
: s.PropertyArabicName));
}
}
interface ILanguage
{
string Language { get; set; }
}
class CurrentLanguage : ILanguage
{
public string Language { get; set; }
}
internal static class Program
{
private static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddScoped<ILanguage>(p => new CurrentLanguage())
.BuildServiceProvider();
var config = new MapperConfiguration(
cfg =>
{
cfg.ConstructServicesUsing(t => serviceProvider.GetService(t));
cfg.AddProfile(new AutomapperProfile(serviceProvider));
});
var mapper = config.CreateMapper();
var customer = new Customer
{
Customerid = 1,
CustomerName = "John",
LocationId = 1,
Location = new Location
{
Locationid = 1,
LocationEnglishName = "New York",
LocationArabicName = "نِيويورْك",
Country = new Country
{
Countryid = 1,
CountryEnglishName = "USA",
CountryArabicName = "الوِلايات المُتَّحِدة الأَمْريكيّة",
}
},
PropertyArabicName = "مبني المقاطعة الملكية",
PropertyEnglishName = "Empire State Building",
};
serviceProvider.GetService<ILanguage>().Language = "en-US";
var englishDto = mapper.Map<CustomerDetailsReadDTO>(customer);
serviceProvider.GetService<ILanguage>().Language = "ar-SA";
var arabicDto = mapper.Map<CustomerDetailsReadDTO>(customer);
Debug.Assert(englishDto.CountryName == "USA");
Debug.Assert(englishDto.LocationName == "New York");
Debug.Assert(englishDto.PropertyName == "Empire State Building");
Debug.Assert(arabicDto.CountryName == "الوِلايات المُتَّحِدة الأَمْريكيّة");
Debug.Assert(arabicDto.LocationName == "نِيويورْك");
Debug.Assert(arabicDto.PropertyName == "مبني المقاطعة الملكية");
}
}
}
I added a Country property to the Location class, to make sense of the model.
Though you could use IMemberResolver, you don't really need to (as shown in the sample code).

AutoMapper not mapping sub entity list

I mapping a model across, which has a child list sub map as well. However, after calling the map, the sub list is not being mapped? I am using AutoMapper 9.0.0 with AutoMapper.Extensions.Microsoft.DependencyInjection 7.0.0 (note this is the parent node in the package list.
I have as follows (reduced for brevity):
public class Agreement
{
//...
public List<Document> Documents { get; set; }
}
public class Document : Entity
{
public string Url { get; set; }
public string Location { get; set; }
public string MimeType { get; set; }
public string FileHash { get; set; }
public float FileSize { get; set; }
public string Notes { get; set; }
public string Type { get; set; }
public byte[] Data { get; set; }
}
public class AgreementDataGridOutputModel : BaseModel
{
//...
public List<DocumentOutputModel> Documents { get; set; }
}
public class DocumentOutputModel
{
public int Id { get; set; }
public string Url { get; set; }
public string MimeType { get; set; }
public string Notes { get; set; }
}
My Mappings are as follows;
CreateMap<Document, DocumentOutputModel>();
CreateMap<List<Document>, List<DocumentOutputModel>>();
CreateMap<Agreement, AgreementDataGridOutputModel>()
.ForMember(dest => dest.AgreementType, opt => opt.MapFrom(src => src.Type.Name))
.ForMember(dest => dest.CompanyName, opt => opt.MapFrom(src => src.Company.Name))
.ForMember(dest => dest.Documents, opt => opt.MapFrom(src => src.Documents));
CreateMap<List<Agreement>, List<AgreementDataGridOutputModel>>();
I then map in my controller as follows;
var response = await _agreementService.FindAsync(criteria);
var output = _mapper.Map<IList<Agreement>,IList<AgreementDataGridOutputModel>>(response.Result);
Can anyone see what I am doing wrong here please?
Mapping collections prevent the collection properties maps from working. See a working test below:
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var list = new List<ParentSource> {
new ParentSource {
Id =1,
Name = "My name",
MyList = new List<ChildSource> {
new ChildSource { Id = 1, Name = "Child name" }
}
}
};
var conf = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ParentSource, ParentTarget>();
cfg.CreateMap<ChildSource, ChildTarget>();
/*
* This line prevents the list mappings from working...
*/
// cfg.CreateMap<List<ChildSource>, List<ChildTarget>>();
});
var mapper = new Mapper(conf);
var targets = mapper.Map<List<ParentSource>, IList<ParentTarget>>(list);
Console.WriteLine(JsonSerializer.Serialize(targets));
// Output: [{"Id":1,"Name":"My name","MyList":[{"Id":1,"Name":"Child name"}]}]
Console.ReadLine();
}
}
public class ParentSource
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildSource> MyList { get; set; }
}
public class ChildSource
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ParentTarget
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildTarget> MyList { get; set; }
}
public class ChildTarget
{
public int Id { get; set; }
public string Name { get; set; }
}
}

Only get filtered elements from class properties

Below is how the code looks like. Note that all of the EmployeeSomethingA, EmployeeSomethingB, EmployeeSomethingC class have a common EmployeeID property.
class EmployeesData
{
public List<EmployeeSomethingA> EmployeeSomethingAs { get; set; }
public List<EmployeeSomethingB> EmployeeSomethingBs { get; set; }
public List<EmployeeSomethingC> EmployeeSomethingCs { get; set; }
}
class EmployeeSomethingA
{
public int EmployeeID { get; set; }
public string SomethingA { get; set; }
}
class EmployeeSomethingB
{
public int EmployeeID { get; set; }
public string SomethingB { get; set; }
public float SomethingBA { get; set; }
}
class EmployeeSomethingC
{
public int EmployeeID { get; set; }
public string SomethingC { get; set; }
public Guid SomethingCA { get; set; }
public double SomethingCB { get; set; }
}
I want add ability on EmployeesData class so that I can retrieve filtered from EmployeeSomethingAs, EmployeeSomethingBs and EmployeeSomethingCs for a particular EmployeeID. I am not sure I can use linq where in this case but I would like is something as follows.
EmployeeDataInstance.Where ( x => x.EmployeeID > 2);
This should return only an EmployeeData instance where EmployeeSomethingAs, EmployeeSomethingBs and EmployeeSomethingCs will contains data with EmployeeID > 2. I don't know if implementing IEnumerable can get me this ability. Any idea how this could be accomplished?
You can try something like that;
Create Employee class that contains shared property EmployeeID and other classes are derived from it.
class Employee
{
public int EmployeeID { get; set; }
}
class EmployeeSomethingA : Employee
{
public string SomethingA { get; set; }
}
class EmployeeSomethingB : Employee
{
public string SomethingB { get; set; }
}
class EmployeeSomethingC : Employee
{
public string SomethingC { get; set; }
}
And create Where method to perform where clause for EmployeesData class;
class EmployeesData
{
public List<EmployeeSomethingA> EmployeeSomethingAs { get; set; }
public List<EmployeeSomethingB> EmployeeSomethingBs { get; set; }
public List<EmployeeSomethingC> EmployeeSomethingCs { get; set; }
public void Where(Func<Employee, bool> predicate)
{
EmployeeSomethingAs = EmployeeSomethingAs.Where((Func<EmployeeSomethingA, bool>)predicate).ToList();
EmployeeSomethingBs = EmployeeSomethingBs.Where((Func<EmployeeSomethingB, bool>)predicate).ToList();
EmployeeSomethingCs = EmployeeSomethingCs.Where((Func<EmployeeSomethingC, bool>)predicate).ToList();
}
}
Usage;
employeesData.Where(x => x.EmployeeID == 1);
EDIT
If you want to filter the list properties as new EmployeesData instance you can modify Where method like this;
public EmployeesData Where(Func<Employee, bool> predicate)
{
return new EmployeesData
{
EmployeeSomethingAs = EmployeeSomethingAs.Where((Func<EmployeeSomethingA, bool>)predicate).ToList(),
EmployeeSomethingBs = EmployeeSomethingBs.Where((Func<EmployeeSomethingB, bool>)predicate).ToList(),
EmployeeSomethingCs = EmployeeSomethingCs.Where((Func<EmployeeSomethingC, bool>)predicate).ToList()
};
}
var newEmployeesData = employeesData.Where(x => x.EmployeeID == 1);
You can get away with using Linq and a single predicate if you use a base class and don't care about the type when consuming the results. I am providing a solution for that and the case where you do care about the types:
public class EmployeesData
{
public List<EmployeeSomethingA> EmployeeSomethingAs { get; private set; }
public List<EmployeeSomethingB> EmployeeSomethingBs { get; private set; }
public List<EmployeeSomethingC> EmployeeSomethingCs { get; private set; }
//Single predicate alternative.
public IEnumerable<EmployeeBase> Where(Func<EmployeeBase, bool> selector)
{
return EmployeeSomethingAs.Where(selector)
.Union(EmployeeSomethingBs.Where(selector))
.Union(EmployeeSomethingCs.Where(selector));
}
//Typed alternative
public SearchResult FindById(int id)
{
var a = EmployeeSomethingAs.Where(e => e.EmployeeID == id);
var b = EmployeeSomethingBs.Where(e => e.EmployeeID == id);
var c = EmployeeSomethingCs.Where(e => e.EmployeeID == id);
return new SearchResult(a, b, c);
}
}
public class SearchResult
{
public SearchResult(IEnumerable<EmployeeSomethingA> a, IEnumerable<EmployeeSomethingB> b, IEnumerable<EmployeeSomethingC> c)
{
As = a;
Bs = b;
Cs = c;
}
public IEnumerable<EmployeeSomethingA> As { get; private set; }
public IEnumerable<EmployeeSomethingB> Bs { get; private set; }
public IEnumerable<EmployeeSomethingC> Cs { get; private set; }
}
public class EmployeeSomethingA : EmployeeBase
{
public string SomethingA { get; set; }
}
public class EmployeeSomethingB : EmployeeBase
{
public string SomethingB { get; set; }
}
public class EmployeeSomethingC : EmployeeBase
{
public string SomethingC { get; set; }
}
public class EmployeeBase
{
public int EmployeeID { get; set; }
}
Difference in usage:
var data = new EmployeesData();
//This will get me an IEnumerable of EmployeeBase
var results = data.Where(e => e.EmployeeID == 5);
//This will get me a SearchResult object with typed results
var results2 = data.FindById(5);

Automapper, CustomMapping not loading fields of a virtual property

I have the following classes.
public class SomeModel
{
[Key]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
public virtual User User { get; set; }
[Required]
public string Name { get; set; }
}
And:
public class SomeModelDetailsResponseModel : IMapFrom<SomeModel>, IHaveCustomMappings
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
public void CreateMappings(IConfiguration configuration)
{
configuration.CreateMap<SomeModel, SomeModelDetailsResponseModel>("name").AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
}
}
For some reason, when I project an IQueryable of SomeModel to an IQueryable of SomeModelDetailsResponseModel the UserName property turns out to be null.
Assuming these are you class definitions:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SomeModel
{
public int Id { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string Name { get; set; }
}
public class SomeModelDetailsResponseModel
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
Solution 1
Do your mapping like this:
var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>().AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
});
var mapper = config.CreateMapper();
var response = mapper.Map<SomeModel, SomeModelDetailsResponseModel>(new SomeModel()
{
User = new User()
{
FirstName = "FN",
LastName = "LN"
}
});
Since you have your input as IQueryable<SomeModel> and you want to project it into IQueryable<SomeModelDetailsResponseModel>, then you can do this:
var result = q.Select(m => mapper.Map<SomeModel, SomeModelDetailsResponseModel>(m));
where q is your IQueryable<SomeModel> instance.
Solution 2
If you want to use ProjectTo<>, then initialize your mapper as the following:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>()
.ForMember(r => r.UserName, c => c.MapFrom(o => o.User.FirstName + o.User.LastName));
});
Then, do your projection as this:
var result = q.ProjectTo<SomeModelDetailsResponseModel>().ToArray();
Where q is your IQueryable<SomeModel>.

how to make group by and orderby linq

This is my model:
public partial class DEGIVREUSE_SITE
{
public int EVENEMENT_ID { get; set; }
public string DEGIVREUSE_ID { get; set; }
public string SITE_COD { get; set; }
public string ETAT_GLOBAL { get; set; }
public string ETAT_CARBURANT { get; set; }
public Nullable<int> KM_CHASSIS { get; set; }
public Nullable<int> HEURE_CHASSIS { get; set; }
public string ETAT_FONCTIONNEMENT { get; set; }
public Nullable<int> HEURE_GROUPE { get; set; }
public string COMMENTAIRE { get; set; }
public virtual DEGIVREUSE DEGIVREUSE { get; set; }
public virtual SITE SITE { get; set; }
public virtual EVENEMENT EVENEMENT { get; set; }
}
[DataContract]
public class InventaireDegivreuse : Evenement
{
public InventaireDegivreuse()
: base(-1, Global.EvenementType.InventaireDegivreuse, DateTime.MinValue)
{
}
public InventaireDegivreuse(int id, DateTime date, string libelle, string societe)
: base(id, (int)Global.EvenementType.InventaireDegivreuse, date, libelle, "", "", societe)
{
ListeDegivreuses = new List<EtatDegivreuse>();
}
[DataMember]
public List<EtatDegivreuse> ListeDegivreuses { get; set; }
public void AddDegivreuse(EtatDegivreuse degivreuse)
{
lock (ListeDegivreuses)
ListeDegivreuses.Add(degivreuse);
}
public int NbDegivreuses
{
get
{
lock (ListeDegivreuses)
return ListeDegivreuses.Count;
}
}
public override void GenereLibelle()
{
Libelle = Properties.Resources.InventaireDegivreuse.Replace("%s", SocieteNom);
}
}
I need to orderby all Events with EVENEMENT_DT_CREA and after for each societe i get the first element of InventaireDegivreuse (the newer one which has the biggest EVENEMENT_DT_CREA) , I try this query but i had a bad result:
var #eventss = GetQuery(unitOfWork).Include(entity => entity.EVENEMENT).OrderByDescending(e => e.EVENEMENT.EVENEMENT_DT_CREA).GroupBy(m => m.EVENEMENT.SOCIETE_NOM).First().ToList();
In my solution for only one societe, i have correct result like this:
public InventaireDegivreuse GetLastBySociete(IReadOnlyUnitOfWork unitOfWork, string societeName)
{
var #event = GetQuery(unitOfWork).Include(entity => entity.EVENEMENT).OrderByDescending(e => e.EVENEMENT.EVENEMENT_DT_CREA).FirstOrDefault(m => m.EVENEMENT.SOCIETE_NOM ==societeName);
return DatabaseMapping.Map<DEGIVREUSE_SITE, InventaireDegivreuse>(#event);
}
Any idea please?
It will be hard to decide what do you want but I suspect that you want something like this:
var #event = GetQuery(unitOfWork)
.Include(entity => entity.EVENEMENT)
.GroupBy(e => e.EVENEMENT.SOCIETE_NOM)
.Select(g => g.OrderByDescending(e => e.EVENEMENT.EVENEMENT_DT_CREA)
.First());

Categories