Prevent AutoMapper ProjectTo from adding unwanted column - c#

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); })

Related

EFCore Many/One to Many relationship of the same entity

I am trying to set up a realationship between an object and versions of the same object. I am using EFCore and its saving the ID from one of the objects into both fields.
My objects are as follows:
public class Workflow
{
public int WorkflowId { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public virtual Workflow Parent { get; set; }
public virtual List<Workflow> Workflows { get; set; } = new List<Workflow>();
public int InternalSequence { get; set; }
public decimal Quantity { get; set; }
public virtual List<WorkflowVersionLink> VersionLinks { get; set; } = new List<WorkflowVersionLink>();
}
public class WorkflowVersionLink
{
public int WorkflowVersionLinkId { get; set; }
public int? IsVersionOfId { get; set; }
public virtual Workflow IsVersionOf { get; set; }
public int? VersionId { get; set; }
public virtual Workflow Version { get; set; }
public bool IsLive { get; set; }
}
And I am setting up the OnModelCreating as follows:
//All of my attempts have used this:
modelBuilder.Entity<WorkflowVersionLink>()
.HasKey(t => new { t.IsVersionOfId, t.VersionId });
//Attempt one: No manual configuration
//Attempt two:
modelBuilder.Entity<WorkflowVersionLink>()
.HasOne(lk => lk.Version)
.WithMany(wf => wf.VersionLinks)
.HasForeignKey(lk => lk.VersionId);
//Attempt three (following info from SO):
modelBuilder.Entity<WorkflowVersionLink>()
.HasOne(lk => lk.IsVersionOf)
.WithMany()
.HasForeignKey(lk => lk.IsVersionOfId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<WorkflowVersionLink>()
.HasOne(lk => lk.Version)
.WithMany(wf => wf.VersionLinks)
.HasForeignKey(lk => lk.VersionId);
I have already tried the approach here:
Many-to-many self referencing relationship & the other approaches mentioned above.
I am open to changing to structure, before all of this I didn't use the WorkflowVersionLink Object, and just had a List Versions, however this failed also because I already have a list of child workflows (which I need to keep)
As you can see, I have attempted to fix this issue myself, but I am now stuck and decided to turn to StackOverflow.
You can see the database result here: (I am 100% sure I added two brand new objects that didnt have IDs each time I ran the code)
database result
What am I doing wrong here?
Thanks
I would be extra sure you are creating new objects for Version and isVersionOf in your WorkflowVersionLink object. If you are trying to make a copy of one, you may be referencing the same object by mistake.
modelBuilder.Entity<WorkflowVersionLink>()
.HasKey(t => t.WorkflowVersionLinkId );
modelBuilder.Entity<WorkflowVersionLink>()
.HasOne(lk => lk.Version)
.WithMany(wf => wf.VersionLinks)
.HasForeignKey(lk => lk.VersionId);
modelBuilder.Entity<WorkflowVersionLink>()
.HasOne(lk => lk.IsVersionOf)
.HasForeignKey(lk => lk.IsVersionOfId)
You may still need to add constraints to keep your database clean

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

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");

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/

Categories