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
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).
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; }
}
}
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);
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>.
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());