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.
Related
I have 3 classes:
public class CountryModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class CountryDTO
{
public int Id { get; set; }
public string Title { get; set; }
}
public class BaseCountryDTO
{
public CountryDTO Country {get; set};
}
I need to map CountryDTO to CountryModel, but through BaseCountryDTO class.
I know that I can do it like this:
CreateMap<BaseCountryDTO, CountryModel>()
.ForMember(model => model.Id, o => o.MapFrom(dto => dto.Country.Id))
.ForMember(model => model.Title, o => o.MapFrom(dto => dto.Country.Title));
But I want to do it clear, something like this:
// This is not working code, just my imagination :)
CreateMap<BaseCountryDTO, CountryModel>()
.ForMember(model => model, dto => dto.Country));
Because in model can be more than 2 properties. Is there way to do it?
#LucianBargaoanu Helped me with the link https://docs.automapper.org/en/latest/Flattening.html#includemembers
It solved my problem.
The solution looks like:
CreateMap<BaseCountryDTO, CountryModel>().IncludeMembers(s => s.Country);
CreateMap<CountryDTO, CountryModel>();
So the thing is we have to create a map of base class to our model with include of what we are really want to map. Then we should create a map of class which we are really need to map.
If the properties in CountryModel and CountryDTO have same names/types, then you can configure the mapping simply as -
CreateMap<CountryDTO, CountryModel>();
You can test the mapping as -
CountryDTO dto = new CountryDTO { Id = 4, Title = "Something" };
CountryModel model = Mapper.Map<CountryModel>(dto);
It will automatically map the properties from CountryDTO to CountryModel, no matter how many they are. You don't have to manually configure mapping for any property, or go through another class like BaseCountryDTO.
We are mapping domain hierarchy to the Dto hierarchy. Some of the properties of the domain base class get flattened. We are using ReverseMap to simplify the mapping back to domain from dto.
We've mapped Contained.Id do ContainedId of Dto and ignored the path for the reverse map.
What we were expecting, that mapping back to domain object shouldn't create new instances for Contained-property. But that was not the case.
After some investigations we've found out, that it was due to not-inheriting the .ForPath(, opt => opt.Ignore()) for the ReverseMap.
We are using 9.0.0 version of Automapper.
Here is the code:
public class Contained {
public Guid Id { get; set; }
}
public class Base {
public Contained Contained { get; set;}
}
public class Derived: Base { }
public abstract class BaseDto {
public Guid? ContainedId { get; set; }
}
public class DerivedDto: BaseDto { }
[Test]
public void ForPathInheritanceWorks()
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Base, BaseDto>()
.IncludeAllDerived()
.ReverseMap()
.ForPath(d => d.Contained.Id, opt => opt.Ignore());
cfg.CreateMap<Derived, DerivedDto>()
.ReverseMap();
});
var mapper = configuration.CreateMapper();
var derived = mapper.Map<Derived>(new DerivedDto());
Assert.That(derived.Contained, Is.Null); // fails, because derived.Contained is not null.
}
To workaround an issue we had to add
.ForPath(d => d.Contained.Id, opt => opt.Ignore())
after ReverseMap of the derived class.
For me it looks like a bug in Automapper.
TB_R_LEAVE_Configuration
public class TB_R_LEAVE_REQ_ID_Configuration : DataUseLogConfiguration<Entities.TB_R_LEAVE_REQ_ID>
{
public TB_R_LEAVE_REQ_ID_Configuration()
{
ToTable("TB_R_LEAVE_REQ_ID");
HasKey(e => e.LEAVE_REQ_ID);
HasRequired<TB_R_LEAVE_REQ>(e => e.TB_R_LEAVE_REQ);
Property(e => e.LEAVE_REQ_ID).HasColumnName("LEAVE_REQ_ID").HasMaxLength(10);
Property(e => e.LEAVE_ID).HasColumnName("LEAVE_ID").HasMaxLength(5);
}
}
DataUseLogConfiguration (abstract base class)
public abstract class DataUseLogConfiguration<T> : EntityTypeConfiguration<T> where T : DataUseLog
{
public DataUseLogConfiguration()
{
Property(e => e.CREATED_BY).HasColumnName("CREATED_BY").HasMaxLength(5).IsRequired();
Property(e => e.CREATED_DT).HasColumnName("CREATED_DT").IsRequired();
Property(e => e.UPDATED_BY).HasColumnName("UPDATED_BY").HasMaxLength(5).IsOptional();
Property(e => e.UPDATED_DT).HasColumnName("UPDATED_DT").IsOptional();
}
}
I want to ignore all DataUseLogConfiguration properties
I tried use Ignore on TB_R_LEAVE_Configuration:
Ignore(e => e.UPDATED_BY);
and this happen:
"The property 'UPDATED_BY' is not a declared property on type
'TB_R_LEAVE_REQ_ID'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property."
Without Ignore this happen:
"ORA-00904: \"Extent2\".\"UPDATED_BY\": invalid identifier"
The Data Model also use inherited base class
public class TB_R_LEAVE_REQ_ID : DataUseLog
{
public string LEAVE_REQ_ID { get; set; }
public string LEAVE_ID { get; set; }
public virtual TB_M_LEAVE TB_M_LEAVE { get; set; }
public virtual TB_R_LEAVE_REQ TB_R_LEAVE_REQ { get; set; }
}
How i properly ignore properties on base class just using fluent API on TB_R_LEAVE_Configuration (im try to avoid changing the data model)
thanks for everyone who try to help me, btw this is how i solve it with just edit the TB_R_LEAVE_REQ_ID_Configuration
public class TB_R_LEAVE_REQ_ID_Configuration : EntityTypeConfiguration<Entities.TB_R_LEAVE_REQ_ID>
{
public TB_R_LEAVE_REQ_ID_Configuration()
{
ToTable("TB_R_LEAVE_REQ_ID");
HasKey(e => e.LEAVE_REQ_ID);
HasRequired<TB_R_LEAVE_REQ>(e => e.TB_R_LEAVE_REQ);
Ignore(e => e.UPDATED_BY);
Ignore(e => e.UPDATED_DT);
Ignore(e => e.CREATED_BY);
Ignore(e => e.CREATED_DT);
Property(e => e.LEAVE_REQ_ID).HasColumnName("LEAVE_REQ_ID").HasMaxLength(10);
Property(e => e.LEAVE_ID).HasColumnName("LEAVE_ID").HasMaxLength(5);
}
}
notice that i changed the inheritance to EntityTypeConfiguration then just add the ignores as usual
Hope this help everyone in the future
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)));
In the application I am busy writing, all my mapping destination objects derive from a base class like this:
public class CatalogObject<TObject>
{
TObject InnerObject { get; set; }
}
public class CatalogTable : CatalogObject<table>
{
public string Name { get; set; }
public int ObjectId { get; set; }
}
Now, after mapping a table object to a CatalogTable object, I want the InnerObject property of that destination to be a reference to the source table object.
You could do it with a Custom Resolver:
Mapper.CreateMap<Table, CatalogTable>()
.ForMember(dest => dest.InnerObject,
opt => opt.ResolveUsing<InnerObjectResolver>());
Where the resolver would look something like:
public class InnerObjectResolver : ValueResolver<Table, Table>
{
protected override Table ResolveCore(Table source)
{
return source;
}
}
Full details can be found in the custom resolver documentation.
You might also be able to do it directly, but I haven't tried that. Something like this maybe:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.InnerObject, opt => opt.MapFrom(src => src));