I'm using AutoMapper in my ASP.NET MVC4 project. I have a problem when mapping 2 class Question and QuestionViewModel. Here my two model classes:
public class Question
{
public int Id { get; set; }
public string Content { get; set; }
public Tuple<int, int> GetVoteTuple()
{
"some code here"
}
}
public class QuestionViewModel
{
public int Id { get; set; }
public string Content { get; set; }
public Tuple<int, int> VoteTuple { get; set; }
}
Here is my controller code :
public class QuestionController: Controller
{
public ActionResult Index(int id)
{
Question question = Dal.getQuestion(id);
Mapper.CreateMap<Question, QuestionViewModel>()
.ForMember(p => p.VoteTuple,
m => m.MapFrom(
s => s.GetVoteTuple()
));
QuestionViewModel questionViewModel =
Mapper.Map<Question, QuestionViewModel>(question);
return View(questionViewModel);
}
}
When I run this code the VoteTuple property in QuestionViewModel has null value. How can I map 2 class with Tuple property ?
Thanks.
Mapping from Tuple to Tuple is not possible by default through Automapper, because Tuple doesn't have setter properties (they can only be initialized through the constructor).
You have 2 options:
1) Create a custom resolver for Automapper and then use the .ResolveUsing method in the mapping config: .ForMember(p => p.VoteTuple, m => m.ResolveUsing<CustomTupleResolver>())
2) Map to a properties / a class instead, like this:
public class QuestionViewModel
{
public int Id { get; set; }
public string Content { get; set; }
public int VoteItem1 { get; set; }
public int VoteItem2 { get; set; }
}
And then:
.ForMember(p => p.VoteItem1, m => m.MapFrom(g => g.Item1))
.ForMember(p => p.VoteItem2, m => m.MapFrom(g => g.Item2))
You don't really need to use Tuple in your view model, so I'd recommend the 2nd option.
Edit:
I see that you've updated your code so that GetVoteTuple() is a function, not a property. In that case, you could easily adapt the code like this:
.ForMember(p => p.VoteItem1, m => m.MapFrom(g => g.GetVoteTuple().Item1))
.ForMember(p => p.VoteItem2, m => m.MapFrom(g => g.GetVoteTuple().Item2))
Your CreateMap call is incorrect:
Mapper.CreateMap<Question, QuestionViewModel>()
.ForMember(p => p.VoteTuple,
m => m.MapFrom(
s => s.GetVoteTuple()
//-----------^
));
Try using ResolveUsing instead of MapFrom (and use the generic s argument in your lambda instead of the local variable reference:
Mapper.CreateMap<Question, QuestionViewModel>()
.ForMember(p => p.VoteTuple,
m => m.ResolveUsing(
s => s.GetVoteTuple()
));
MapFrom is used to map properties directly. Since you're wanting to "map" from the result of a function call, ResolveFrom is more appropriate.
Also, you should only call CreateMap once in your application, typically that's done in Application_Start in global.asax
try this :
Mapper.CreateMap<Question, QuestionViewModel>()
.ForMember(p => p.VoteTuple,op=>op.MapFrom(v=>new Tuple<int,int>(v.GetVoteTuple.Item1,v.GetVoteTuple.Item2)));
Related
Here I am stucked with the conversion of dictionary to Icollection in EF Core. I have Dictionary in FlatEmployee class in which I am storing list of key, value pair in database. I have declared like this:
public class FlatEmployee
{
public int EmployeeId { get; set; }
public Dictionary<string, long> PayAndAllowances { get; set; }
}
//====================Configuration
public void Configure(EntityTypeBuilder<FlatEmployee> builder)
{
builder.HasKey(f => new { f.EmployeeId });
builder.Property(sb => sb.PayAndAllowances)
.HasConversion(
pa => JsonConvert.SerializeObject(pa),
pa => JsonConvert.DeserializeObject<Dictionary<string, long>>(pa));
}
This works absolutely fine when I am seeding or inserting. But the problem I am facing when I am trying to get the FlatEmployee class. This is because I want to get the dictionary in Collection. For that purpose I have declared another class like this:
public class LastPayCertificateViewModel: IHaveCustomMapping
{
public int EmployeeCode { get; set; }
public EmployeeEarningDTO PayAndAllowances { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<FlatEmployee, LastPayCertificateViewModel>()
.ForMember(dto => dto.EmployeeCode , opt => opt.MapFrom(entity => entity.EmployeeId ));
}
}
public class EmployeeEarningDTO : IHaveCustomMapping
{
public ICollection<BaseEmployeeDictionary> PayAndAllowances { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>()
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity.PayAndAllowances));
}
}
public class BaseEmployeeDictionary
{
public string Name { get; set; }
public long Amount { get; set; }
}
When I am trying to use above classes for getting the data like this:
public class LastPayCertificateQuery : IRequest<LastPayCertificateViewModel>
{
public string EmployeeCode { get; set; }
}
public async Task<LastPayCertificateViewModel> Handle(LastPayCertificateQuery request, CancellationToken cancellationToken)
{
var predicate = PredicateBuilder.False<FlatEmployee>();
predicate = predicate.Or(emp => emp.EmployeeId == request.EmployeeCode);
var employee = await _context.FlatEmployee
.Where(predicate)
.FirstOrDefaultAsync(cancellationToken);
if (employee == null)
return null;
var empVM = _mapper.Map<LastPayCertificateViewModel>(employee);
}
Then I am getting null in PayAndAllowances in empVM. This is what my problem is. Where is my problem? I thought it was because that Dictionary has key value pair and which is not been able to convert to BaseEmployeeDictionary. I have tried this way as well to add List Item to PayAndAllowances in empVM
foreach (KeyValuePair<string, long> data in employee.DeductionsByAdjustment)
{
BaseEmployeeDictionary listItem = new BaseEmployeeDictionary
{
Name = data.Key,
Amount = data.Value
};
empVM.EarningDetails.PayAndAllowances.Add(listItem);
return empVM;
}
Which of course wont work because the empVM.EarningDetails.PayAndAllowances is null and throws NullReferenceException. My queries is how to map between the Dictionary to ICollection while Creating Map in EmployeeEarningDTO. OR It would be really appretiated for your valuable suggestion and solution please.
It turns out to be AutoMapper mapping issue.
First, EmployeeEarningDTO inside LastPayCertificateViewModel creates additional level compared to FlatEmployee:
LastPayCertificateViewModel.PayPayAndAllowances.PayAndAllowances
vs
FlatEmployee.PayAndAllowances
AutoMapper maps by default properties with the same name. So inside FlatEmployee to LastPayCertificateViewModel map it would try to map Dictionary<string, long> PayAndAllowances to EmployeeEarningDTO PayAndAllowances. But there is no mapping from Dictionary<string, long> to EmployeeEarningDTO. Instead, there is a mapping from FlatEmployee to EmployeeEarningDTO, so you have to tell AM to use it:
configuration.CreateMap<FlatEmployee, LastPayCertificateViewModel>()
.ForMember(dto => dto.EmployeeCode, opt => opt.MapFrom(entity => entity.EmployeeId))
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity)); // <--
Second, mapping from FlatEmployee to EmployeeEarningDTO - AM will automatically try to map PayAndAllowances properties, but there is no mapping from KeyValuePair<string, long> to BaseEmployeeDictionary. You could define such mapping
configuration.CreateMap<KeyValuePair<string, long>, BaseEmployeeDictionary>()
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dst => dst.Amount, opt => opt.MapFrom(src => src.Value));
which will allow you to use simply
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>();
however you probably won't do that because you don't want every KeyValuePair<string, long> to be mapped to BaseEmployeeDictionary, so you could do that mapping inplace:
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>()
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity.PayAndAllowances
.Select(src => new BaseEmployeeDictionary { Name = src.Key, Amount = src.Value })));
I have a source class like this:
public class Basket {}
a target class like this:
public class BasketModel
{
public string Property { get; set; }
}
and a mapping like this:
Mapper.CreateMap<Basket, BasketModel>()
.ForMember(x => x.Property, o => o.ResolveUsing(x => "anything"));
Now I've made the "Property" property in the original model virtual and created a new class that inherits from the model:
public class BasketModel
{
public virtual string Property { get; set; }
}
public class BasketModel2 : BasketModel
{
public override string Property
{
get
{
return "some value";
}
}
}
I've updated the mapping as such:
Mapper.CreateMap<Basket, BasketModel>()
.ForMember(x => x.Property, o => o.ResolveUsing(x => "anything"))
.Include<Basket, BasketModel2>();
And created the mapping
Mapper.CreateMap<Basket, BasketModel2>()
.ForMember(x => x.Property, o => o.Ignore());
Now when I try to map into BasketModel2, instead of null, the value of Property is "anything".
What am I missing here?
Ok I think I had a brainfart when I wrote this code. model2.Property is never going to be null because it's a getter that always returns the same string. I have some refactoring to do, but AutoMapper was doing what it needs to do, I was just using it wrong.
Is it possible to create a default destination mapping in AutoMapper ?
Source classes:
class SourceA {
public string X { get; set; }
}
class SourceB {
public string Y { get; set; }
}
Destination classes:
class DestBase {
public List<string> Z { get; set; }
}
class DestA : DestBase {
public string X { get; set; }
}
class DestB : DestBase {
public string Y { get; set; }
}
And the mapping configuration contains the following:
cfg.CreateMap<SourceA, DestA>()
.ForMember(dest => dest.Z, src => src.MapFrom(s => null));
cfg.CreateMap<SourceB, DestB>()
.ForMember(dest => dest.Z, src => src.MapFrom(s => null));
Is it possible to create a default mapping for all destination classes inheriting the DestBase to avoid the repeated .ForMember(...) lines ?
eg. something like:
cfg.CreateMap<object, DestBase>
.ForMember(dest => dest.Z, src => src.MapFrom(s => new List<string>()));
In principle yes, with the Include method, but there is a caveat.
If you define a map from source type object, this map would match all types. Maybe you can introduce an interface ISource for the source types that should be affected by this mapping.
So it could look like this:
class SourceA : ISource {
public string X { get; set; }
}
class SourceB : ISource {
public string Y { get; set; }
}
cfg.CreateMap<ISource, DestBase>
.Include<SourceA, DestA>
.Include<SourceB, DestB>
.Include<SourceC, DestC>
.ForMember(dest => dest.Z, , o => o.MapFrom(src => new List<string>()));
cfg.CreateMap<SourceA, DestA>()
.ForMember(dest => dest.X, o => o.MapFrom(src => src.X));
cfg.CreateMap<SourceB, DestB>()
.ForMember(dest => dest.Y, o => o.MapFrom(src => src.Y));
// still need to create a map even if no additional properties are to be mapped
cfg.CreateMap<SourceC, DestC>();
Note that you still need to create maps for all included types, even if there are no additional properties to map.
I want to create a map for a somewhat complex entity model to a flattened view Model
My entity model is like so
cbItems
has many cbItemsContent
has many cbRegulators
so my viewmodels are like so
for cbItems:
public class ItemViewModel
{
public ItemViewModel()
{
this.CbItemsContents = new HashSet<ItemContentViewModel>();
}
public int ItemID { get; set; }
......
public virtual ICollection<ItemContentViewModel> CbItemsContents { get; set; }
}
}
for cbItemsContent:
public class ItemContentViewModel
{
public int ItemContentID { get; set; }
public int ItemID { get; set; }
....
public ItemContentRegulatorsViewModel RegulatedBy { get; set; }
}
}
for cbRegulators:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IEnumerable<int> RegulatorIds { get; set; }
}
}
I had hoped it would be as easy as this:
config.CreateMap<CbItem, ItemViewModel>();
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy.ItemContentId,
m => m.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatedBy.RegulatorIds,
n => n.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
from teh following query:
ItemViewModel item =
_context.cbItems.Where(u => u.ItemId = id)
.ProjectTo<ItemViewModel>()
.first();
But this results in an error:
Expression 'dest => dest.RegulatedBy.ItemContentId' must resolve to
top-level member and not any child object's properties. Use a custom
resolver on the child type or the AfterMap option instead. Parameter
name: lambdaExpression
HOw can I achieve my desired model layout?
You have to map ItemContentRegulatorsViewModel, then you don't need to set it from the ViewModel above.
#Rabban probably means something like this:
config.CreateMap<CbItemsContent, ItemContentViewModel>()
.ForMember(dest => dest.RegulatedBy, o => o.MapFrom(src => src));
config.CreateMap<CbItemsContent, ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.ItemContentId, o => o.MapFrom(src => src.GenericID))
.ForMember(dest => dest.RegulatorIds, o => o.MapFrom(src => src.cbItemsContentRegulators.Select(q => q.cbRegulator.RegulatorId)));
I have something like this
public class ProductViewModel
{
public int SelectedProductId { get; set; }
public string ProductName {get; set;}
public int Qty {get; set;}
public List<SelectListItem> Products { get; set};
}
I have a domain like this
public class Product
{
public int ProductId {get; set;}
public string ProductName {get; set;}
public int Qty {get; set;}
}
public class Store
{
public Product() {get; set;}
}
Now I need to do the mapping.
// in my controller
var result = Mapper.Map<ProductViewModel, Store>(Product);
this won't bind anything since it can't figure out how to put the ProductId in since it
is
Store.Product.ProductId;
My map is like this
Mapper.CreateMap<ProductViewModel, Store>().ForMember(dest => dest.Product.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
I get this error
Expression 'dest =>
Convert(dest.Product.SelectedProductId' must
resolve to top-level member. Parameter
name: lambdaExpression
I am unsure how to do this.
To Map nested structures, you just need to create a new object in the MapFrom argument.
Example
Mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.MyNestedType, o => o.MapFrom(t => new NestedType { Id = t.Id }));
Mapper.AssertConfigurationIsValid();
Test Code:
var source = new Source { Id = 5 };
var destination = Mapper.Map<Source, Destination>(source);
Classes:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public NestedType MyNestedType { get; set; }
}
public class NestedType
{
public int Id { get; set; }
}
You can use Resolver.
Create a resolver class like that :
class StoreResolver : ValueResolver<Store, int>
{
protected override int ResolveCore(Store store)
{
return store.Product.ProductId;
}
}
And use it like that :
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.SelectedProductId, opt => opt.ResolveUsing<StoreResolver >());
Hope it will help ...
The error your getting is because you cannot declare mapping declarations more than one level deep in your object graph.
Because you've only posted one property its hard for me to give you the codes that will make this work. One option is to change your viewmodel property to MyTestTestId and the conventions will automatically pick up on that.
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src));
Mapper.CreateMap<ProductviewModel, Product>()
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.