Automapper mapping generic property from ISource => IDest, then specifying remaining mappings - c#

My situation is I must merge the name of a related object to a name property on many different models. Those models have implementations which need their own configurations.
What I've tried is:
ProjectFrom.cs
public interface IProjectFrom {
int Id { get; set; }
}
public class ProjectFromConcreteA : IProjectFrom {
public int Id { get; set; }
public string SomeProp { get; set; }
}
ProjectTo.cs
public interface IProjectTo {
string Name { get; set; }
}
public class ProjectToConcreteA : IProjectTo {
public string Name { get; set; }
public string SomeProp { get; set; }
}
Profile.cs
CreateMap<IProjectFrom, IProjectTo>()
.ForMember(projection => projection.Name, expression => expression.ResolveUsing<ProjectionResolver>());
CreateMap<ProjectFromConcreteA, ProjectToConcreteA>()
.ForMember(concreteTo => concreteTo.SomeProp, expression => expression.MapFrom(concreteFrom => concreteFrom.SomeProp));
ProjectionResolver.cs
public class ProjectionResolver : IValueResolver<IProjectFrom, IProjectTo, string>
{
private readonly IDependency _dependency;
public ProjectionResolver(IDependency dependency)
{
_dependency = dependency;
}
public string Resolve(IProjectFrom source, IProjectTo destination,
string destMember, ResolutionContext context)
{
return _dependency.GetStringFor(source.Id);
}
}
However this does not seem to work as my Resolver is never used. Am I missing a step?

Related

Automapper: Trying to map to a property with a private setter

I am trying to map to a class that contains a property with a private setter. I read in a different answer that this was possible (Automapper apparently used Reflection to do so) just as long as I configured the property in question with the ForMember method (see below)
Mapper config
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<IDbCoverage, ICoverage>()
.ForMember(dest => dest.CoverageCodeDesc, conf => conf.MapFrom(src => src.CoverageCodeDesc));
});
Interfaces
public interface IDbCoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
public interface ICoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
Instantiation of the destination class:
public class Coverage
{
public string ExternalMemberId { get; set; }
public string CoverageCode { get; set; }
public string CoverageCodeDesc { get; private set; }
}
However when I try this only the properties with public setters are mapped. The source value of the other property is not mapped and the destination class ends up with a null value.
What am I doing wrong?

AutoMapper ForAllPropertyMaps with IValueConverter

I'm trying to replace an existing localization mechanism by using AutoMapper => ForAllPropertyMaps.
Database entities have properties marked with an Localize attribute, so the idea is that when mapping from Entity => Dto to intercept the entity property that is marked with the Localize attribute and translate it to the corresponding language before setting the value to the Dto property.
Below is my prof of concept, but in the case of a custom mapping like the one below:
CreateMap<Entities.Product, Dto.Product>().ForMember(a => a.OfferName, opt =>
{
opt.PreCondition(m => m.ProductOffer != null);
opt.MapFrom(a => a.ProductOffer.Name);
})
I receive the ProductOffer as SourceMember instead of the ProductOffer.Name, and I didn't find any solution to identify the Name property value, so my question is, it possible to handle this types of scenarios in a generic way?
public class ProfileBase : Profile
{
public ProfileBase()
{
ForAllPropertyMaps(Condition, Configuration);
}
private static bool Condition(PropertyMap pm)
{
return pm.SourceMember != null &&
pm.SourceType == typeof(string) &&
pm.DestinationType == typeof(string) &&
pm.SourceMember.CustomAttributes.Any(a => a.AttributeType == typeof(LocalizedAttribute));
}
private static void Configuration(PropertyMap pm, IMemberConfigurationExpression opt)
{
opt.ConvertUsing(new LocalizedValueConverter());
}
}
public class LocalizedValueConverter : IValueConverter<string, string>
{
internal const string Language = "LANGUAGE";
public string Convert(string sourceMember, ResolutionContext context)
{
var language = context.Options.Items[Language]?.ToString();
if (language == null)
{
return sourceMember;
}
return GetLocalizedValue(sourceMember, language); // Custom logic for translating the sourceMember value
}
}
CreateMap<Entities.Product, Dto.Product>().ForMember(a => a.OfferName, opt =>
{
opt.PreCondition(m => m.ProductOffer != null);
opt.MapFrom(a => a.ProductOffer.Name);
})
namespace Dto
{
public class Product
{
public int Id { get; set; }
public int ProductOfferId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string OfferName { get; set; }
}
}
namespace Entities
{
public class Product
{
public int Id { get; set; }
public int ProductOfferId { get; set; }
[Localized]
public string Name { get; set; }
[Localized]
public string Description { get; set; }
public virtual ProductOffer ProductOffer { get; set; }
}
public class ProductOffer
{
public int Id { get; set; }
[Localized]
public string Name { get; set; }
[Localized]
public string Description { get; set; }
}
}

Project a name-value list into an object

I want to be able to save an arbitrary flat object into the name-value list.
public class NameValueListEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[InverseProperty(nameof(NameValueListContentEntity.Entity))]
public ICollection<NameValueListContentEntity> Content { get; set; }
}
public class NameValueListContent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("entity_fk")]
public NameValueListEntity Entity { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class ObjectToSave
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
I could use reflection to manually assemble/parse the list, but it will create a lot of overhead. Lots of NameValueListContent objects will be needlessly created both during the saving and the reading. Could it somehow be omitted? Especially during the reading, which is very performance-sensitive in my case.
Assume you have a AppDbContext class that holds your NameValueListContent class objects named as NVListContents. You can read and write the name-value list of objects by doing the following:
public class AppDbContext : DbContext
{
public DbSet<NameValueListContent> NVListContents { get; set; }
public AppDbContext()
: base()
{ }
}
public class SomeClass
{
private AppDbContext context { get; set; }
public SomeClass(AppDbContext _context)
{
context = _context;
}
public List<ObjectToSave> ReadObjects()
{
return context.NVListContents
.Select(nvlc => new ObjectToSave { Prop1 = nvlc.Name, Prop2 = nvlc.Value
}).ToList();
}
public bool WriteObjects(int id, string name, string value)
{
var query = context.NVListContents
.FirstOrDefault(nvlc => nvlc.Id == id);
if(query != null)
{
query.Name = name;
query.Value = value;
context.Update(query);
context.SaveChanges();
return true;
}
else
{
return false;
}
}
}
Hope, this answers to your question.

Automapper configure 2 classes to one

I have the following DB (Infrastructure) classes:
[Table("ApplicationDriverEquipments")]
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public int TypeId { get; set; }
public virtual ApplicationDriverEquipmentType Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
[Table("ApplicationDriverEquipmentTypes")]
public partial class ApplicationDriverEquipmentType
{
public ApplicationDriverEquipmentType()
{
Equipments = new HashSet<ApplicationDriverEquipment>();
}
public int Id { get; set; }
[Required]
[StringLength(256)]
public string Name { get; set; }
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
and the following DTO (Domain) classes:
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public static class ApplicationDriverEquipmentTypeStaticStringsDomain
{
public const string Tractor = "Tractor";
public const string Trailer = "Trailer";
public const string StraightTruck = "Straight Truck";
public const string CargoVan = "Cargo Van";
}
I wrote the following Automapper rules to resolve it:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan));
I got an error:
Expression 'c => c.Type.Name' 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.
UPDATE
I rewrote maps:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest)=> dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan);
but now I got an error:
Type Map configuration: ApplicationDriverEquipmentTractorDomain ->
ApplicationDriverEquipment
Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain ->
Infrastructure.Asset.ApplicationDriverEquipment
Property: Type ---> AutoMapper.AutoMapperMappingException: Missing
type map configuration or unsupported mapping.
Mapping types:
String -> ApplicationDriverEquipmentType
System.String -> Infrastructure.Asset.ApplicationDriverEquipmentType
Seems, I don't understand how to map it correctly
You are trying to map from
ApplicationDriverEquipmentTractorDomain.Type is a string
to
ApplicationDriverEquipment.Type is a ApplicationDriverEquipmentType
Where is your mapping configuration for that?
Is it even possible to map a string to a ApplicationDriverEquipmentType?
Sure, you can have a string Name, but where do you get the Id and Equipments?
I suspect you don't want to create a new instance of that type each time you map, but rather you need to look up an instance from some dictionary, sort of a registry pattern
To implement this idea, you simply need to
Load all of the ApplicationDriverEquipmentType from DB
Put them in a dictionary (assuming name is unique)
Register a custom type converter or custom value resolver as below
One way to implement this would be to use a custom type converter
You could use something like
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
And put in your own function that would resolve your ApplicationDriverEquipmentType by name, assuming name is unique like this:
var applicationEquipments = new ApplicationDriverEquipmentTypeRepository().FindAll(); // get all the values somehow from db
var dictionary = applicationEquipments.ToDictionary(x=>x.Name);
Func<string, ApplicationDriverEquipmentType> resolver = x=>dictionary[x];
Yet another way to do this would be to use a custom value resolver
Essentially, the idea would be the same - map of pre-loaded objects, only the way you "plug it in" would be different
Try to use MapFrom method instead:
.ForMember(c => c.Type.Name, p => p.MapFrom(s => Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));

Mapping foreign keys of subclasses

I have following abstract class:
public abstract class ClauseComponent
{
public int ClauseComponentId { get; set; }
public abstract string[] Determinate(ClimateChart chart);
public abstract List<ClauseComponent> GiveCorrectPath(ClimateChart chart);
public abstract String GetHtmlCode(Boolean isYes);
public virtual void Add(Boolean soort, ClauseComponent component)
{
throw new ApplicationException();
}
public ClauseComponent()
{
}
}
The Clause class inherits from the abstract class:
public class Clause : ClauseComponent
{
public virtual ClauseComponent YesClause { get; set; }
public virtual ClauseComponent NoClause { get; set; }
public String Name { get; private set; }
public virtual Parameter Par1 { get; set; }
public virtual Parameter Par2 { get; set; }
public int Waarde { get; set; }
public String Operator { get; set; }
public Clause()
{
}
public Clause(String name, Parameter par1, String op, int waarde)
{
this.Name = name;
this.Par1 = par1;
this.Operator = op;
this.Waarde = waarde;
}
public Clause(String name, Parameter par1, Parameter par2)
{
this.Name = name;
this.Par1 = par1;
this.Par2 = par2;
}
}
This is the mapper of the abstract class (I dont have a mapper for the subclass):
public ClauseComponentsMapper()
{
ToTable("ClauseComponents");
// Primary key
HasKey(c => c.ClauseComponentId);
// Properties
Property(c => c.ClauseComponentId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
I have this in my DB:
Now I want to give a proper name to the mapping, how can I accomplish this?
I have never done the mapping on abstract classes and subclasses so I'm a little bit in the blue here.
One way is to create properties for the mapping columns, and in the mapping class, map the virtual property using the mapping column property.
E.g.
public class Clause : ClauseComponent
{
public int MyCustomPar1Id{ get; set; }
[ForeignKey("MyCustomPar1Id")]
public virtual Parameter Par1 { get; set; }
}
Or Fluent Api:
modelBuilder.Entity<Clause >().HasRequired(p => p.Par1 ) // Or Optional
.HasForeignKey(p => p.MyCustomPar1Id);

Categories

Resources