AutoMapper: Defer property mapping to derived maps for abstract base classes - c#

Is it possible in AutoMapper to defer mapping some properties to derived maps? For instance consider the following classes:
public abstract class Contract
{
public int ContractNumber { get; set; }
}
public class RegularContract : Contract { }
public class SpecialContract : Contract { }
public class ContractModel
{
public int Nr { get; set; }
public bool IsSpecial { get; set; }
}
I would like to create a mapping like the following. Observe that the IsSpecial property is not mapped in the mapping of Contract but in the mappings of the derived classes RegularContract and SpecialContract instead.
public class ContractMappingProfile : Profile
{
public ContractMappingProfile()
{
CreateMap<Contract, ContractModel>()
// .ForMember(t => t.IsSpecial, c => c.Ignore()) // required for valid mapping
.ForMember(t => t.Nr, c => c.MapFrom(s => s.ContractNumber));
CreateMap<RegularContract, ContractModel>()
.IncludeBase<Contract, ContractModel>()
.ForMember(t => t.IsSpecial, c => c.UseValue(false));
CreateMap<SpecialContract, ContractModel>()
.IncludeBase<Contract, ContractModel>()
.ForMember(t => t.IsSpecial, c => c.UseValue(true));
}
}
Such a configuration is however not valid in AutoMapper because IsSpecial is not mapped for Contract. In order to make it valid, I would need to add the commented line to the mapping.
However, in such a case AutoMapper will not throw a validation error if the property is not mapped in some derived class, e.g., when I change the mapping of SpecialContract to:
CreateMap<SpecialContract, ContractModel>()
.IncludeBase<Contract, ContractModel>(); // no IsSpecial mapping
Any ideas how to solve this using AutoMapper mapping inheritance?

Related

How to map a calss that inherits an asbtract class and implements an interface at the same time in nHibernate?

Imagine we have this entities:
public interface ISomeone
{
}
public abstract class Parent
{
}
public class Child : Parent , ISomeone
{
}
public class Orphan : ISomeone
{
}
public class MyHeart
{
public ISomeone Person {get;set;}
}
then how is possible to mapping theses all classes in nhibernate?
I prefer to use separate tables for "Parent", "Orphane" and "MyHeart" class.
"Parent" , "Orphan" and "MyHeart" should be persist as aggrigate root and MyHeart could has a relation to any entity that implemented "ISomeone" interface. so it could expande across diffrent entity types with diffrent id form diffrent tables
Finally I've found the solution
the "Any" method is the answer, briefly with this method we define some meta value for each possible value for IPerson in another world every class that implements this interface should be define in "Any" method then nHibernate persists the Id and the MetaValue.
The mapping by code for my example would be like this:
public class MyHeartMapping : ClassMapping<MyHeart>
{
public MyHeartMapping()
{
Id(x => x.Id, x => x.Generator(Generators.Native));
Any(p => p.Person, typeof(long), m => {
m.MetaValue("Child", typeof(Child));
m.MetaValue("Orphane", typeof(Orphane));
m.Columns(i => i.Name("PersonId"), c => c.Name("ClassName")); });
}
}
public class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Id(x => x.Id, x => x.Generator(Generators.Native));
Discriminator(c => c.Column("Discriminator"));
}
}
public class ChildMapping : SubclassMapping<Child>
{
public ChildMapping()
{
DiscriminatorValue("Child");
}
}
public class OrphaneMapping : ClassMapping<Orphane>
{
public OrphaneMapping()
{
Id(x => x.Id, x => x.Generator(Generators.Native));
}
}

Automapper to work with nested base class with two sub types to flattened Dto

Based on all the examples I've seen, I expected to have this solved by now, but can't quite get the mapping to work for some reason. I keep getting a Missing type map configuration or unsupported mapping. exception. Using Automapper 4.2.1
Here are my classes:
public class Schedule
{
public ScheduleData ScheduleData { get; set; }
}
// Base type
public abstract class ScheduleData {}
// Sub Types
public class ActionItemScheduleData : ScheduleData {}
public class FormScheduleData : ScheduleData {}
Here's the mapping. I went with an AfterMap() approach, and broke out the mappings by type. I didn't think I had to do this, but I got the above error when I just mapped the base type. Even with this approach with the if/else, I still get the error. Is this due to my version of Automapper?
public class ScheduleMapper : Profile
{
protected override void Configure()
{
CreateMap<ActionItemScheduleData, ScheduleDto>(MemberList.Destination)
.ForMember(x => x.Name, c => c.MapFrom(m => m.ActionDescription));
CreateMap<MobileFormScheduleData, ScheduleDto>(MemberList.Destination)
.ForMember(x => x.Name, c => c.MapFrom(m => m.Description));
CreateMap<Schedule, ScheduleDto>(MemberList.Destination)
.AfterMap((s, d) =>
{
if (s.ScheduleData is MobileFormScheduleData)
{
Mapper.Map((MobileFormScheduleData) s.ScheduleData, d);
}
else
{
Mapper.Map((ActionItemScheduleData) s.ScheduleData, d);
}
});
}
}
Here's where I call it:
var scheduleDto = mapper.Map<ScheduleDto>(schedule);
I resolved it by following code:
CreateMap<Schedule, ScheduleDto>().ForMember(t => t.ScheduleData, opt => opt.Ignore()).AfterMap((s, t, c) =>
{
t.ScheduleData = c.Mapper.Map<MobileFormScheduleData, ScheduleDto>((MobileFormScheduleData)s.ScheduleData )
});

Generic entity configuration class in EF Core 2

I'm trying to create a generic configuration class for my entities but i'm stuck.
I have an abstract class called EntityBase:
public abstract class EntityBase
{
public int Id { get; set; }
public int TenantId { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
And many other classes that inherit from EntityBase, in which i have to configure the DateTime properties in each one with the same code. This way:
void EntityTypeConfiguration<MyEntity>.Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.HasIndex(e => e.TenantId);
builder.Property(e => e.CreatedOn)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("GETDATE()");
// Other specific configurations here
}
I would like to be able to call somthing like: builder.ConfigureBase() and avoid the code duplication. Any ideas?
There are several way you can accomplish the goal. For instance, since you seem to be using IEntityTypeConfiguration<TEntity> classes, you could create a base generic configuration class with virtual void Configure method and let your concrete configuration classes inherit from it, override the Configure method and call base.Configure before doing their specific adjustments.
But let say you want to be able to exactly call builder.ConfigureBase(). To allow that syntax, you can simply move the common code to a custom generic extension method like this:
public static class EntityBaseConfiguration
{
public static void ConfigureBase<TEntity>(this EntityTypeBuilder<TEntity> builder)
where TEntity : EntityBase
{
builder.HasIndex(e => e.TenantId);
builder.Property(e => e.CreatedOn)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("GETDATE()");
}
}
with sample usage:
void IEntityTypeConfiguration<MyEntity>.Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ConfigureBase();
// Other specific configurations here
}

Casting of self-referencing entities, detect generic type

Once, I got an interface for all entities:
public interface IEntity
{
int Id { get; set; }
}
For some entities a mutationtable will exist, to log, what was done for which entity (CRUD)
public interface IMutation<TEntity> where TEntity : IEntity
{
ICollection<Mutation<TEntity>> Mutations { get; set; }
}
For each entity that implements IMutation Entity Framework will create a table with name Mutation<EntityName>
So, Mutation<EntityName> is an entity, too.
public class Mutation<TEntity> : Entity where TEntity : IEntity
{
public int EntityId { get; set; }
public TEntity Entity { get; set; }
}
I implemented the interface IEntity on a class, that some entities will inherit.
public class Entity : IEntity
{
public int Id { get; set; }
}
The entity Test inherits from Entity (becuase it's an entity) and implements IMutation with a reference to itself
public class Test : Entity, IMutation<Test>
{
public ICollection<Mutation<Test>> Mutations { get; set; } = new List<Mutation<Test>>();
}
Entity Framework gets it, and creates the two tables:
Test with properties Id and Name
Mutation<Test> with property Id (the PK from IEntity) and EntityId (the FK referencing the Test-entity)
this all works great. DB-schema and so on.
So what I want to do is, always, when one entity taht implements IMutation<EntityName> is changed, a new dataset shall be created.
There is the possibility to override SaveChanges of DbContext. Nice, so I tried it:
public override int SaveChanges()
{
IEnumerable<EntityEntry> entries = ChangeTracker.Entries(); // gets me all entries that were changed
IEnumerable<IEntity> mutationEntries =
entries.Select(s => s.Entity).Where(
w =>
w.GetType()
.GetInterfaces()
.Any(
x =>
x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IMutation<>)))
.Select(s => (IEntity)s);
// so here now I got the entries that implement IMutation<?> <-- call this now ?-type
// what I'd now want to do is:
foreach(var entry in mutationEntries)
{
IMutation<?> mutationEntry = (IMutation<?>)entry;
mutationEntry.Mutations.Add(new Mutation<?>{ /* later on, add here CRUD, Id, user who changed,... */ });
}
return base.SaveChanges();
}
The problem now is, that I never know, what my ?-Type is. I know it has to be from Type IEntity.
But when I try to parse the Entity to IMutation<IEntity> i get an error, saying, he cannot cast from IMutation<Test> to IMutation<IEntity>. (But Test implements IEntity)
Tried it this way:
IEnumerable<IMutation<IEntity>> mutationEntries =
entries.Select(s => s.Entity).Where(
w =>
w.GetType()
.GetInterfaces()
.Any(
x =>
x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IMutation<>)))
.Select(s => (IMutation<IEntity>)s);
But I'm already checking, whether my Entity implements IMutation.
Maybe someone has an idea, how I could solve this issue?
It's hard to work with generic interfaces that are not covariant and have no non generic counterparts (like IEnumerable<T> -> IEnumerable, IQueryable<T> -> IQueryable etc.).
The only remaining choice in such case is reflection or dynamic dispatch.
For instance, you could add a method like this:
private void ProcessMutationEntity<TEntity>(TEntity entity)
where TEntity : IEntity, IMutation<TEntity>
{
entity.Mutations.Add(new Mutation<TEntity> { EntityId = entity.Id, Entity = entity});
}
and then use DLR to call it (using the code from the first example):
// ...
foreach (var entry in mutationEntries)
{
ProcessMutationEntity((dynamic)entry);
}
// ...

Mapping Id attribute by Reflection

I'm trying to map the Id attribute from my entity classes with reflection using FluentNHibernate.
My entities:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Entity : BaseEntity
{
public string Name { get; set; }
}
Ok, my mapping class is like above:
public class BaseMapping<E> : ClassMap<E>
{
public BaseMapping(string schema, string table)
{
Schema(schema);
Table(table);
Id(model => typeof(E).GetProperty("Id", typeof(int)), "Id")
.GeneratedBy.Identity()
.Not.Nullable();
}
}
public class EntityMapping : BaseMapping<Entity>
{
public EntityMapping() : base("dbo", "Entities")
{
Map(model => model.Name, "Name")
.Length(50)
.Insert().Update()
.Not.Nullable();
}
}
I am receiving this exception:
{"Identity type must be integral (int, long, uint, ulong)"}
When I map the Id attribute on the EntityMapping class...
Id(model => model.Id, "Id")
.GeneratedBy.Identity()
.Not.Nullable();
It's works like a charm. But the first attempt is not working.
Firstly your properties should be marked as virtual for your entities. This is so the NHibernate framework can perform its magic lazy loading voodoo.
That being said. Lets assume all your entities derive from BaseEntity as it appears. Because of this assumption you can let the typeparam E understand that it will always be a BaseEntity.
Once you do this you can then rewrite the BaseMapping<E> method as such.
public class BaseMapping<E> : ClassMap<E>
where E: BaseEntity
{
public BaseMapping(string schema, string table)
{
Schema(schema);
Table(table);
Id(model => model.Id, "Id");
}
}
By specifing where E: BaseEntity will expose the properties of E to your method. I have only tested this code up to the point of the mapping methods completing for multiple entity types.
As for why you recieved your message the statement
typeof(E).GetProperty("Id", typeof(int))
returns a type of PropertyInfo where you need to pass the member expression for the memberExpression parameter. By digging through the source of FluentNHibernate they use the Expression to evaluate to a Member through reflection.

Categories

Resources