Is it possible to tell automapper to ignore mapping at runtime? - c#

I'm using Entity Framework 6 and Automapper to map entities to dtos.
I have this models
public class PersonDto
{
public int Id { get; set; }
public string Name { get; set; }
public AddressDto Address { get; set; }
}
public class AddressDto
{
public int Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
I use automapper Queryable Extension to map dto from entity.
var personDto = dbContext.People.Project().To<PersonDto>();
The problem with above method is that it will make EF to always load the address entity. I want address to be loaded only if i explicitly tell them to with include(x => x.Address). If i specify ignore() in automapper map, the address will not be loaded. Is it possible to tell automapper to ignore the address property at runtime? Automapper queryable extensions i'm using doesn't support all functionalities like "Condition or after map". Is there any workaround for this?

You need to enable explicit expansion for your DTOs. First in your configuration:
Mapper.CreateMap<Person, PersonDto>()
.ForMember(d => d.Address, opt => opt.ExplicitExpansion());
Then at runtime:
dbContext.People.Project.To<PersonDto>(membersToExpand: d => d.Address);
The "membersToExpand" can be a list of expressions to destination members, or a dictionary of string values representing the property names to expand.

Related

Getting related data via an AutoMapper mapping?

I want to create a mapping between this entity model:
public class ProductType
{
public int Id { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public ICollection<ProductIdentifierInType> Identifiers { get; set; }
public ICollection<ProductPropertyInType> Properties { get; set; }
public ICollection<Product> Products { get; set; }
}
... and this viewmodel:
public class ViewModelProductType
{
public int Id { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public IList<ViewModelProductIdentifier> Identifiers { get; set; }
public IList<ViewModelProductProperty> Properties { get; set; }
public ICollection<ViewModelProduct> Products { get; set; }
}
... but since the Identifiers and Properties are not of the same type in the viewmodel as in the entity model, it won't work directly, like this:
CreateMap<ProductType, ViewModelProductType>();
I don't want to change my models too much. In the entity model, I need the Identifiers and Properties to be respectively ProductIdentifierInType and ProductPropertyInType, because there are many-to-many relationships there, which requires linking tables.
But in the viewmodel, I need Identifiers and Properties to be the full objects in order to display their properties in the view.
Is there a way to accomplish this with mapping? Maybe using .ForPath() to get the two objects' properties?
Assuming you have defined the direct entity to view model mappings:
CreateMap<ProductIdentifier, ViewModelProductIdentifier>();
CreateMap<ProductProperty, ViewModelProductProperty>();
Now it would be enough to extract the corresponding member using LINQ Select inside MapFrom expression. The important thing to know is that AutoMapper does not require the type of the returned expression to match the type of the destination. If they don't match, AutoMapper will use the explicit or implicit mappings for that types.
CreateMap<ProductType, ViewModelProductType>()
.ForMember(dst => dst.Identifiers, opt => opt.MapFrom(src =>
src.Identifiers.Select(link => link.Identifier)))
.ForMember(dst => dst.Properties, opt => opt.MapFrom(src =>
src.Properties.Select(link => link.Property)))
;
I think what you are looking for is a Custom Value Resolver.
There you can explicitly specify how Auto Mapper should map one object to another.
In your case it could look something like this:
public class CustomResolver : IValueResolver<ProductType, ViewModelProductType, IList<ViewModelProductIdentifier>>
{
public int Resolve(ProductType source, ViewModelProductType destination, IList<ViewModelProductIdentifier> destMember, ResolutionContext context)
{
// Map you source collection to the destination list here and return it
}
}
You can then pass/inject the resolver when calling CreateMap, i.e.:
CreateMap<ProductType, ViewModelProductType>()
.ForMember(dest => dest.Identifiers, opt => opt.ResolveUsing<CustomResolver>());
Analogously, do the same for your 'Properties' property.
Note that I did not debug this but merely adapted the examples provided in the link above.

Automapper source prefix property

We are designing a temporal system where the definition of an entity can change. I am trying to setup Automapper but can't quite work out how the prefix should work.
As an example, I would have the following entity:
public class ReferenceDataDefinition
{
public string Name { get; set; }
}
public class ReferenceData
{
public int Id { get; set; }
public ReferenceDataDefinition Current { get; set; }
}
With the following DTO:
public class ReferenceDataDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I know I can use
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Id, o => o.MapFrom(s => s.Id)
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
But I feel there must be something smarter I can do?
I've tried adding RecognizePrefixes("Current") but that had no effect.
I've tried adding RecognizePrefixes("Current")
This isn't how prefix are used. They are for a scenario where your properties start with a prefix (often because of a database naming schema).
For example, If you had the following classes:
public class ReferenceData
{
public int Ref_Id { get; set; }
public string Ref_Name { get; set; }
}
public class ReferenceDto
{
public int Id { get; set; }
public string Name { get; set; }
}
You could recognize the following prefix:
cfg.RecognizePrefixes("Ref_");
AutoMapper would then be able to map those two objects without you having to define specific mappings with .ForMember.
Regarding you own mapping, since both Id properties on ReferenceData and ReferenceDataDTO have the same name, you should be able to remove the Id member mapping as AutoMapper can infer it automatically:
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
This should suffice.
As for .Current using Flattening you could remove it if you would change your DTO class to rename it to CurrentName.
Please check this documentation:
Recognizing pre/postfixes
Also the RecognizePrefixes works for source object prefixes
Use RecognizeDestinationPrefixes method
Check these previous posts:
AutoMapper with prefix
https://github.com/AutoMapper/AutoMapper/issues/421

Codefirst Migrations not working with EntityFramework, MVC5, ComplexType and fluent

I like to use composition to group up like properties in a class so that they are easier to use when the number of fields increase. My question is, can entity framework handle modeling when we use composition and ComplexTypes in a class? When I use the two together, I get the following error during migrations:
The expression 'x => x.AllQuantities.OnHandUom' is not a valid
property expression. The expression should represent a property: C#:
't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'.
Here is the code. This is a simple model.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public AllQuantities AllQuantities { get; set; }
}
Note, AllQuantities is a Complex Type added through composition (I want the fields to appear in the same table as the products, but I want to group them under AllQuanties (Actually I have many more fields, but I have simplified the problem for here.))
[ComplexType]
public class AllQuantities
{
public double OnHand { get; set; }
public Uom OnHandUom { get; set; }
public int OnHandUomID { get; set; }
}
Please note in my Uom class, I am trying to set up a navigation property back to products through the complex type.
public class Uom
{
public int ID { get; set; }
public string Name { get; set; }
public double QtyInUom { get; set; }
public virtual ICollection<Product> OnHandUoms { get; set; }
}
Then, I have used the following fluent code to set up the model in my context file so that the navigation property works.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasRequired(x => x.AllQuantities.OnHandUom)
.WithMany(y => y.OnHandUoms)
.HasForeignKey(z => z.AllQuantities.OnHandUomID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Note the
"HasRequired(x => x.AllQuantities.OnHandUom)."
I believe the dot notation is causing some kind of a problem. To my mind, it shouldn't.
I have tried the model creation by adding all the fields from my ComplexType AllQuantities to the product directly, as per normal, then everything works, however, when I separate the two as listed above, I get the error mentioned earlier. I would like to use the ComplexType so that I can group like fields.
My question is, is there any way to make the above work with a ComplexType and compostion?

Automapper and NHibernate: lazy loading

I have the following scenario.
public class DictionaryEntity
{
public virtual string DictionaryName { get; set; }
public virtual IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
public class DictionaryDto
{
public string DictionaryName { get; set; }
public IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
I'm using Automapper and NHibernate. In NHibernate the DictionaryRecord property is marked as lazy loaded.
When I make the mapping from DictionaryEntity -> DictionaryDto, Automapper loads all my DictionaryRecords.
But I don't want this behavior, is there a way to configure the Automapper in order to don't resolve this property until I really access this property.
My workaround for this situation consists of splitting the DictionaryEntity in 2 classes and create a second Automapper mapping.
public class DictionaryDto
{
public string DictionaryName { get; set; }
}
public class DictionaryDtoFull : DictionaryDto
{
public IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
and then in the code, depending on the need, call AutoMapper.Map appropriately.
return Mapper.Map<DictionaryDto>(dict);
return Mapper.Map<DictionaryDtoFull>(dict);
Does somebody have a better solution for my problem?
You must add a condition to validate if the collection is initialized to be mapped. You can read here more details: Automapper: Ignore on condition of.
AutoMapper.Mapper.CreateMap<DictionaryEntity, DictionaryDto>()
.ForMember(dest => dest.DictionaryRecord, opt => opt.PreCondition(source =>
NHibernateUtil.IsInitialized(source.DictionaryRecord)));
You could ignore the property, if this is of use?
AutoMapper.Mapper.CreateMap<DictionaryEntity, DictionaryDto>()
.ForMember(dest => dest.DictionaryRecord,
opts => opts.Ignore());
http://cpratt.co/using-automapper-mapping-instances/

Automapper map Viewmodel to Model with same name

Viewmodel
public string Personal_Data_Surname { get; set; }
public string FamilyMember_SurName { get; set; }
Entity class Applicant
public string SurName { get; set; }
Entity class FamilyMember
public string SurName { get; set; }
Automapper Config
Mapper.Configuration.RecognizePrefixes("Personal_Data_");
Mapper.CreateMap<ApplicationViewModel, Applicant>();
Mapper.Configuration.RecognizePrefixes("FamilyMember_");
Mapper.CreateMap<ApplicationViewModel, FamilyMember>();
Controller code mapping entities
Applicant applicant = Mapper.Map<ApplicationViewModel, Applicant>(vaModel);
FamilyMember familyMember = Mapper.Map<ApplicationViewModel, FamilyMember>(vaModel);
The problem is that it maps Personal_Data_Surname from the viewmodel to the Surname in entity class Applicant and FamilyMember. Is it possible to specify a prefix for a specific type
You have to customize your mapping using ForMemeber method, with the MapFrom option method, for sample:
Mapper.CreateMap<ApplicationViewModel, Applicant>()
.ForMember(viewModel => viewModel.Personal_Data_Surname,
opt => opt.MapFrom(entity => entity.SurName));
Then, AutoMapper will map the Personal_Data_Surname viewModel's property to SurName entity's property. Do it to another entities.

Categories