Mapping to base class using automapper - c#

I need to map from a source class to a destination base class with auto-mapper.
My scenario is as below.
class Source
{
string FirstID { get; set; }
string SecondID { get; set; }
}
Also my destination is as below
class DestinationBase
{
string ID { get; set; }
}
class DestinationObject : DestinationBase
{
string Prop { get; set; }
}
When I use automapper with the
Mapper.CreateMap<Source, DestinationObject>()
.ForMember(d => d.ID, s => s.MapFrom(s.FirstID))
.ForMember(d => d.ID, s => s.MapFrom(s.SecondID))
One of the ID after mapping does not work. Please any idea why ? I have tried include but i guess I don't understand its use well enough.

You're specifying the ID field in the destination object twice so the ID will be the value from s.SecondId.
What are you trying to accomplish with FirstID and SecondId in the destination object?

Related

Create Map for IEnumerable to IEnumerable /List in automapper

I am having issue with Automapper when i have a collection.
Below is my situaion
Bussiness Model
public class Member
{
public int MemberId { get; set; }
public int TeamID { get; set; }
public Team Team { get; set; }
}
and i want to return below model from controller
public class Member
{
public int MemberId { get; set; }
public string Team { get; set; }
public int TeamId { get; set; }
}.
I have trying for something like below but not Team string remains null. I beleive i have to specify the mapping. But i am new to automapper i can't figure it out.
IEnumerable<Models.Member> ienumerableDest = _mapper.Map<IEnumerable<Entities.Member>, List<Models.Member>>(members);
Here is the Response
My guess is you haven't specified anywhere how to map your classes.
In your Entities.Member class you have a Team property of type Team, but you are trying to map it to string, so it fails.
In your configuration, add a profile which specifies this mapping, like this:
public class AppProfile : Profile
{
public AppProfile()
{
CreateMap<Entities.Member, Model.Member>(MemberList.Destination)
.ForMember(d => d.Team, opt => opt.MapFrom(src => src.Team.Name));
}
}
In this example i assumed a property "Name" in your Team object.
And in your startup class you should register your profile in your AutoMapper configuration.

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.

Prevent AutoMapper ProjectTo from adding unwanted column

It seems to me that using AutoMapper ProjectTo<> is adding an unwanted (calculated) column to my query. Here's the query:
SELECT TOP(1) CASE
WHEN [dto].[Id] IS NULL
THEN CAST(0 AS BIT) ELSE CAST(1 AS BIT)
END, [dto].[Enabled], [dto].[DurationWarningThresholdSec], [dto].[AverageDurationLabel]
where that first CASE & CAST unnamed column is not really needed, I think. This is the gist of my issue.
This is the EF Core (maybe that matters) POCO entity being queried:
public class CountersConfigData
{
public Guid Id { get; set; }
public bool Enabled { get; set; }
public int DurationWarningThresholdSec { get; set; }
public string AverageDurationLabel { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
public DateTime? DeletedAt { get; set; }
public Guid CompanyId { get; set; }
}
// within DbContext.OnModelCreating()
entityTypeBuilder.HasKey(cfg => cfg.Id);
entityTypeBuilder.HasIndex(cfg => cfg.DeletedAt);
Here is the destination shape of mapping:
public class Result
{
public bool Existing { get; set; }
public CountersMainConfig Main { get; set; }
}
public class CountersMainConfig
{
public bool Enabled { get; set; }
public int DurationWarningThresholdSec { get; set; }
public string AverageDurationLabel { get; set; }
}
This is the mapping initial setup:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CountersConfigData, Result>()
.ForMember(dest => dest.Main, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.Existing, opt => opt.Ignore());
cfg.CreateMap<CountersConfigData, CountersMainConfig>();
});
I understand that I could project to inner type, CountersMainConfig, and then manually create the outer type instance. But I have further cases similar to this one, with more complicated outer types, so I'd like to sort this out for all of them.
If I actually project to the inner type, that first column goes away from generated query. What am I missing? TA
EDIT I created a simplified solution just to confirm the issue before asking here. Afterwards I also tried changing outer destination class by making Existing a float, or by removing it, with no luck.
The issue is similar to Automapper Projection with Linq OrderBy child property error, so is the solution - configure AutoMapper to not generate null check for the Main property:
.ForMember(dest => dest.Main, opt => { opt.AllowNull(); opt.MapFrom(src => src); })

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

Why MapInheritedProperties() maps my entity into two tables?

I'm trying to map my entities following the TPC pattern.
I have an abstract base class Agent :
public abstract class Agent
{
public int Id { get; set; }
public DateTime? ChangeDate { get; set; }
public DateTime CreationDate { get; set; }
public string insuranceCompanyPolicyNumber { get; set; }
public int? KeySys { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
public string Status { get; set; }
public int? Transactionid { get; set; }
}
I have a child entity Suscriber :
public partial class Suscriber : Agent
{
public int? id_federateur { get; set; }
public string Number { get; set; }
public bool? suivi_client { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
}
When i try to configure the Suscriber like the following, i've noticed that the request generated by Entity Framework is wrong :
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.Property(s => s.Number).HasColumnName("numero_souscripteur");
// Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
The request generated by Entity Framework is (The table Agent2 doesn't exist):
SELECT E'0X0X' AS "C1",
"Extent1"."id_personne",
"Extent1"."suivi_client",
"Extent1"."id_federateur",
"Extent2"."date_mod",
"Extent2"."date_cre",
"Extent2"."insuranceCompanyPolicyNumber",
"Extent2"."keySys",
"Extent2"."id_role",
"Extent2"."statut",
"Extent2"."vsc_transactionid",
"Extent2"."numero_souscripteur"
FROM "atlas"."Agent2" AS "Extent1"
INNER JOIN "atlas"."T_ACTEUR_SOUSCRIPTEUR" AS "Extent2" ON
"Extent1"."id_personne" = "Extent2"."id_personne"
I know that if i changed the configuration of suscriber like this, it will work
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
// m.Property(s => s.Number).HasColumnName("numero_souscripteur");
Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
In other words, if i configure the property Number outside the scope of the method Map or i don't apply the method Property to the parameter of the lambda, it works. (Property(s => s.Number).HasColumnName("numero_souscripteur");)
Could somebody explain me how the method MapInheritedProperties() works?
Why Entity Framework generate a non existing table?
Thanks
This is because everything that you put in one mapping fragment...
Map(m => { ... })
...is mapped to the table that the fragment applies to. In fact it's saying: map all properties from Agent and property Number from Subsriber to table "T_ACTEUR_SOUSCRIPTEUR". EF silently assumes that the leftovers are mapped to another table it devices by naming conventions. This happens as soon as you map one property from the derived type. If you only use MapInheritedProperties(), all properties, including the derived ones, are mapped to the table in the mapping fragment.
I don't know if this is a bug or a feature. It's not very clear at least. Anyway, you should map other property names outside the mapping fragment, as you already found out. Of course it's it more clear if you do that in a separate statement:
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
Property(s => s.Number).HasColumnName("numero_souscripteur");
Or if you like:
Map(m => m.MapInheritedProperties());
ToTable("T_ACTEUR_SOUSCRIPTEUR");
Property(s => s.Number).HasColumnName("numero_souscripteur");

Categories