TL;DR: I'm having trouble with Polymorphic mapping. I've made a github repo with a test suite that illustrates my issue. Please find it here: LINK TO REPO
I'm working on implementing a save/load feature. To accomplish this, I need to make sure the domain model that I'm serializing is represented in a serialization-friendly way. To accomplish this I've created a set of DTOs that contain the bare-minimum set of information required to do a meaningful save or load.
Something like this for the domain:
public interface IDomainType
{
int Prop0 { get; set; }
}
public class DomainType1 : IDomainType
{
public int Prop1 { get; set; }
public int Prop0 { get; set; }
}
public class DomainType2 : IDomainType
{
public int Prop2 { get; set; }
public int Prop0 { get; set; }
}
public class DomainCollection
{
public IEnumerable<IDomainType> Entries { get; set; }
}
...and for the DTOs
public interface IDto
{
int P0 { get; set; }
}
public class Dto1 : IDto
{
public int P1 { get; set; }
public int P0 { get; set; }
}
public class Dto2 : IDto
{
public int P2 { get; set; }
public int P0 { get; set; }
}
public class DtoCollection
{
private readonly IList<IDto> entries = new List<IDto>();
public IEnumerable<IDto> Entries => this.entries;
public void Add(IDto entry) { this.entries.Add(entry); }
}
The idea is that DomainCollection represents the current state of the application. The goal is that mapping DomainCollection to DtoCollection results in an instance of DtoCollection that contains the appropriate implementations of IDto as they map to the domain. And vice versa.
A little extra trick here is that the different concrete domain types come from different plugin assemblies, so I need to find an elegant way to have AutoMapper (or similar, if you know of a better mapping framework) do the heavy lifting for me.
Using structuremap, I'm already able to locate and load all the profiles from the plugins and configure the applications IMapper with them.
I've tried to create the profiles like this...
public class CollectionMappingProfile : Profile
{
public CollectionMappingProfile()
{
this.CreateMap<IDomainType, IDto>().ForMember(m => m.P0, a => a.MapFrom(x => x.Prop0)).ReverseMap();
this.CreateMap<DtoCollection, DomainCollection>().
ForMember(fc => fc.Entries, opt => opt.Ignore()).
AfterMap((tc, fc, ctx) => fc.Entries = tc.Entries.Select(e => ctx.Mapper.Map<IDomainType>(e)).ToArray());
this.CreateMap<DomainCollection, DtoCollection>().
AfterMap((fc, tc, ctx) =>
{
foreach (var t in fc.Entries.Select(e => ctx.Mapper.Map<IDto>(e))) tc.Add(t);
});
}
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
.IncludeBase<IDomainType, IDto>().ReverseMap();
}
}
public class DomainProfile2 : Profile
{
public DomainProfile2()
{
this.CreateMap<DomainType2, IDto>().ConstructUsing(f => new Dto2()).As<Dto2>();
this.CreateMap<DomainType2, Dto2>().ForMember(m => m.P2, a => a.MapFrom(x => x.Prop2))
.IncludeBase<IDomainType, IDto>().ReverseMap();
}
}
I then wrote a test suite to make sure that the mapping will behave as expected when its time to integrate this feature with the application. I found whenever DTOs were getting mapped to Domain (think Load) that AutoMapper would create proxies of IDomainType instead of resolving them to the domain.
I suspect the problem is with my mapping profiles, but I've run out of talent. Thanks in advance for your input.
Here's another link to the github repo
I stumbled across this question when looking in to a polymorphic mapping issue myself. The answer is good, but just another option if you'd like to approach it from the base mapping perspective and have many derived classes, you can try the following:
CreateMap<VehicleEntity, VehicleDto>()
.IncludeAllDerived();
CreateMap<CarEntity, CarDto>();
CreateMap<TrainEntity, TrainDto>();
CreateMap<BusEntity, BusDto>();
See the automapper docs for more info.
I spent a little time reorganizing the repo. I went as far as to mimic a core project and two plugins. This made sure that I wouldn't end up with a false-positive result when the tests finally started passing.
What I found was that the solution had two(ish) parts to it.
1) I was abusing AutoMapper's .ReverseMap() configuration method. I was assuming that it would perform the reciprocal of whatever custom mapping I was doing. Not so! It only does simple reversals. Fair enough. Some SO questions/answers about it:
1, 2
2) I wasn't fully defining the mapping inheritance properly. I'll break it down.
2.1) My DomainProfiles followed this pattern:
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
.IncludeBase<IDomainType, IDto>().ReverseMap();
this.CreateMap<Dto1, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
}
}
So now knowing that .ReverseMap() is not the thing to use here, it becomes obvious that the map between Dto1 and DomainType1 was poorly defined. Also, The mapping between DomainType1 and IDto didn't link back to the base IDomainType to IDto mapping. Also an issue. The final result:
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, IDto>().IncludeBase<IDomainType, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
this.CreateMap<DomainType1, Dto1>().IncludeBase<DomainType1, IDto>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1));
this.CreateMap<Dto1, IDomainType>().IncludeBase<IDto, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
this.CreateMap<Dto1, DomainType1>().IncludeBase<Dto1, IDomainType>().ForMember(m => m.Prop1, a => a.MapFrom(x => x.P1));
}
}
Now each direction of the mapping is explicitly defined, and the inheritance is respected.
2.2) The most base mapping for IDomainType and IDto was inside of the profile that also defined the mappings for the "collection" types. This meant that once I had split up the project to mimic a plugin architecture, the tests that only tested the simplest inheritances failed in new ways - The base mapping couldn't be found. All I had to do was put these mappings into their own profile and use that profile in the tests as well. That's just good SRP.
I'll apply what I've learned to my actual project before I mark my own answer as the accepted answer. Hopefully I've got it and hopefully this will be helpful to others.
Useful links:
this
this one was a good refactoring exercise. I admittedly used it as a starting place to build up my example. So, thanks #Olivier.
Related
I have a list of DTOs and want to map this list to a list of entites. The entity itself has one property which comes from another source. Can I map this property to all items of the list with one map.
My classes:
Entity:
public class Account
{
public int Id {get;set;}
public string Name {get;set;}
public Guid ExternalId {get;set;}
}
DTO:
public class ExternalAccountDto
{
public int Id {get;set;}
public string Name {get;set;}
}
My Service:
public class AccountService
{
public async Task AddExternalAccounts(Guid externalId, List<ExternalAccountDto> accounts)
{
var entities = _mapper.Map(accounts);
// TODO: Map 'externalId' to all entities
// _mapper.Map(externalId, entities); // DOES NOT WORK!
_context.Create(entities);
}
}
Mapping
public class AccountProfile: Profile
{
public AccountProfile()
{
CreateMap<ExternalAccountDto, Account>();
// TODO: CreateMap for Guid on every Account
}
}
Can anyone give me some advice!
You should use the AfterMap function to do some postprocessing on the mapped items.
There are two ways to go about this. One is using something statically defined in the mapping profile. But in your case, you have something that's dynamic at runtime, like the ExternalId. Doing the aftermap in your AccountService then makes perfect sense.
I've found these kind of constructions very useful, especially when I want to consult other injected services for additional information.
public void AddExternalAccounts(Guid externalId, List<ExternalAccountDto> accounts)
{
var entities = _mapper.Map<List<ExternalAccountDto>, List<Account>>(accounts,
options => options.AfterMap((source, destination) =>
{
destination.ForEach(account => account.ExternalId = externalId);
}));
}
Two more cents regarding the AccountProfile class:
You can check upon creation of the mapping profile if the mapping profile is correct. This will save you a headache running into this problem later at runtime. You'll know immediately that there is a problem with the configuration.
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
cfg.AllowNullDestinationValues = false;
});
// Check that there are no issues with this configuration, which we'll encounter eventually at runtime.
config.AssertConfigurationIsValid();
_mapper = config.CreateMapper();
This also notified me that an .Ignore() on the ExternalId member of the Account class was required:
CreateMap<ExternalAccountDto, Account>().ForMember(d => d.ExternalId, a => a.Ignore());
So, I'm writing a reusable library. And Nhibernate mapping by code is used for ORM operations. There will be multiple services making use of this library so I want the library to behave as dynamically as possible.
There will be multiple services and for every service there will be specific tables found in the database, these are prefixed by their service name. Unity will inject this prefix and that all works nice and dandy when using only one service.
But now I'm at the point where I have to write a service that will read and combine from multiple services. So this libdummy item will have to be mapped multiple times with different table prefixes.
public class LibDummy
{
public virtual int Id { get; set; }
public virtual string Guid { get; set; }
}
public class LibDummyMapping : ClassMapping<LibDummy>
{
public LibDummyMapping(ServiceName service)
{
Table($"{service.Name}_LibDummy");
Id(o => o.Id, m => m.Column("Id"));
Property(o => o.Guid, m => m.Column("Guid"));
}
}
I tried doing it like this:
public class FirstLibDummyMapping : LibDummyMapping
{
public FirstLibDummyMapping (ServiceName service) : base(service)
{
}
}
public class SecondLibDummyMapping : LibDummyMapping
{
public SecondLibDummyMapping (ServiceName service) : base(service)
{
}
}
But this will throw a "Duplicate class/entity mapping" error.
And with 2 different classmaps for the same entity it will throw a Collection already mapped error.
Ideally I would have one dynamic classmap that can just be natively used in the library, but that's not an option i guess??
Any ideas for this, or is this something that is just not going to work?
Any NHibernate guru's with a definitive answer?
I'm developing an application that implements DDD and Repository Pattern as shown in diagram bellow:
I expect to keep my Domain Layer persistent ignorant, so I wouldn't like to install entity framework libraries there. The only problem I'm facing is that my application uses spatial data, but I'm not supposed to use DbGeography as a Property Type of my entities, once it belongs to System.Data.Entity.Spatial namespace, from EntityFramework assembly.
Is there a way to create a class to hold latitude, longitude and elevation values in Domain Layer, like that:
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Elevation { get; set; }
}
and then convert that class to DbGeography in my Repository Layer?
In other words, the domain entities would have only Location class as a property:
public class Place : IEntityBase, ILocalizable
{
public int Id { get; set; }
public string Name { get; set; }
public Location Location { get; set; }
public User Owner { get; set; }
}
and I'd convert it DbGegraphy to persist spatial data and do some calculations only in repository layer. My plans was try something like that to convert:
public class LocationMap : ComplexTypeConfiguration<Location>
{
public LocationMap()
{
Property(l => DbGeography.FromText(string.Format("POINT({0} {1})", l.Longitude, l.Latitude))).HasColumnName("Location");
Ignore(l => l.Elevation);
Ignore(l => l.Latitude);
Ignore(l => l.Longitude);
}
}
But it doesn't work and never will. How I can solve this problem? What are the best practices in this situation?
Thank you
Well, I don't know "right" way, but, i have a tricky idea. I hope, it'll help you or give some more variants:
Ypu have domain entity Place, it's fully persistent ignorant and it's place in Domain assembly. Good.
Lets create one more Place class in Repository assembly:
internal sealed class EFPlace : Place
{
DbGeography EFLocation
{
get
{
return DbGeography.FromText(string.Format("POINT({0} {1})", Location.Longitude, Location.Latitude);
}
set
{
//vice versa convertion, I don't know, how to do it :)
}
}
}
We created special class for Entity Framework, and map it:
public class PlaceMap : ComplexTypeConfiguration<EFPlace>
{
public PlaceMap ()
{
Property(p => p.EFLocation).HasColumnName("Location");
Ignore(p => p.Location);
}
}
But, we have to convert from Place to EFPlace on save in repository. You can create special constructor, or casting method.
Another variant - create partial class Place in Domain and Repository assemblies. And add needed propery in Repository one class and so on.
Well, it looks ugly :( but, I don't know "pure", real-life examples of Persistent Ignorant Domain. We always have limitations of Entity Framework. NHibernate has a little more features.
This is my situation, very much simplified.
My classes;
public class ClassBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class ClassMiddle1 : ClassBase
{
}
public class ClassMiddle2 : ClassBase
{
public Guid Token { get; set; }
}
public class ClassA : ClassMiddle1
{
public string UserId { get; set; }
public string Username { get; set; }
}
public class ClassB : ClassMiddle2
{
public string Username { get; set; }
}
And my OnModelCreating;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassBase>()
.Map(m => {
m.Properties(p => new { p.Id});
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle1>()
.Map<ClassMiddle1>(m =>
{
m.Properties(p => new { });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle2>()
.Map<ClassMiddle2>(m =>
{
m.Properties(p => new { p.Token });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassA>()
.Map<ClassA>(m =>
{
m.Properties(p => new
{
p.UserId,
p.Username
});
m.ToTable("TableA");
});
modelBuilder.Entity<ClassB>()
.Map<ClassB>(m =>
{
m.Properties(p => new
{
p.Username
});
m.ToTable("TableB");
}).Property(p => p.Username).HasColumnName("User");
}
This works fine but the Discriminator column is by default Discriminator, NVARCHAR(128). I read that it is possible to define this column myself using something like below.
m.Requires("ClassType").HasValue(1);
I turned my possibilities inside out but all times running into a dead end. Anyone having a suggestion how to do it?
I will end with another question. As our hierarchy pretty much are as above but even more derivated classes like C, D, E, F and so on to... say P. We found out that EF are making this incredibly big database query (~150K). Anyone else ran into this scenario?
I am hoping with changing Discriminator to at least minimize this. By that I say we have a very neat class hierarchy but an ugly query set.
Late answer how the actual solution went. Only writing it down here because the documentation around this was not that easy to find.
My solution ended up like below...
modelBuilder.Entity<ClassBase>()
.Map(m => {
...
m.Requires("Discriminator").HasValue(1)
});
Regarding your "incredibly big database query": There are indeed performance and query generation issues with TPT inheritance mapping. There still doesn't seem to be a fix for those problems, only this vague announcement (August 2010):
The good news is that we are working
on these issues so that EF no longer
generates unnecessary SQL. The bad
news is that it will take some time
before the fix is delivered in a
future release.
(Quote from the linked article above.)
I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework. I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework.
When I try to run this code using a DbContext with a DbSet<Template>, I get the following error.
A The navigation property 'Flags' is mapped to two different join tables 'page.flags' and 'template.flags'. Only one mapping of the navigation property may exist
What this says to me is that I cannot map inherited properties. This is quite breaking to a lot of object oriented code design. Is there a known remedy? I realize I can make Layout non-abstract, and have a backing for it, but it's very obvious this is not the intention of the domain model. The abstract class is a foundational base, not the stored model.
I would like to add, if I put the IList<Flag> in the Template class, this code runs. The Id field still works, even through inheritance. I do not understand why this is happening. Can someone enlighten me?
public abstract class Layout
{
public virtual int Id
{
get;
set;
}
public virtual IList<Flag> Flags
{
get;
set;
}
}
public class Template : Layout
{
public virtual string Name
{
get;
set;
}
}
public class Page: Layout
{
}
public class LayoutConfiguration : EntityConfiguration<Layout>
{
public LayoutConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).IsIdentity();
MapHierarchy().Case<Page>(c => new
{
c.Id
}).ToTable("Pages");
MapHierarchy().Case<Template>(c => new
{
c.Id,
c.Name
}).ToTable("Templates");
}
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
Property(o => o.Name).HasMaxLength(64).IsUnicode();
HasMany(u => u.Flags).WithOptional()
.Map("template.flags",
(template, flag) => new {
Template = template.Id,
Flag = flag.Id
});
MapSingleType(c => new {
c.Id,
c.Name
}).ToTable("templates");
}
}
public class PageConfiguration : EntityConfiguration<Page>
{
public PageConfiguration()
{
HasMany(c => c.Flags).WithOptional()
.Map("page.flags",
(page, flag) => new
{
Page = page.Id,
Flag = flag.Id
});
}
}
When you use base type for your Template entity, you also have to model this inheritance in mapping. It means that you have to write configuration for Layout which will map Id and Flags and configuration for Template which will map Name. There is several approaches of mapping inheritance in EF. You should probably check Table per Hiearchy.
Edit: Based on your comment you are looking for Table per Class + examples for CTP4.
Edit2: Ok. I tested your scenario with navigation property defined in abstract parent class and it really doesn't work if you are trying to map it to multiple tables.