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());
Related
So I created a new simple project just to help a friend.
So I made a class Customer which has a list of Stuff
So far so good, now with the mapping and storing the relationsship. I went to map in accordance with fluent nhibernate class maps and ended up with the following
public class CustomerMap : ClassMap<Customer> {
Id(p => p.Id).GenerateBy.Guid();
HasMany(p => p.Stuff).Access.CamelCaseField().KeyColumn("Id").Inverse().Cascade.AllDeleteOrphan();
}
public class StuffMap : ClassMap<Stuff> {
Id(p => p.Id).GeneratedBy.Guid();
Reference(p => p.Customer).Column("CustomerId).Not.Nullable();
}
and my classes
public class Customer {
private ISet<Stuff> stuff = new HashSet<Stuff>()
public virtual IEnumerable<Stuff> Stuff => stuff;
public void AddStuff(Stuff newstuff) {
newstuff.Customer = this;
stuff.Add(stuff);
}
}
public class Stuff {
public virtual Customer Customer { get; set; }
}
All this works good and when I create a new Customer and add one of more Stuff elements into the collection using the method AddStuff and commits the transaction it gets correctly written to the database.
However now the strange begins, when I make a test like the following
[TestMethod]
public void TestStuffAndCustomer() {
var customer = session.Add(new Customer());
customer.AddStuff(new Stuff());
session.Flush();
var customer = session.Query<Customer>().Single();
customer.Stuff.Should().HaveCount(1);
}
The assertion of the collection fails with reason that the count of the collection is 0. However if I debug the test and check the collection it contains one element. The assertion fails regardless however.
So what is wrong with this setup?
I think you add new Customer and Stuff to Customer on session, but without saving them you flush the session.
We have an application with a fairly complex entity model where high-performance and low-latency are essential but we have no need for horizontal scalability. The application has a number of event sources in addition to a self-hosted ASP.NET Web API 2. We use Entity Framework 6 to map from POCO classes to the database (we use the excellent Reverse POCO Generator to generate our classes).
Whenever an event arrives the application has to make some adjustment to the entity model and persist this delta adjustment to the database via EF. At the same time read or update requests may arrive via the Web API.
Because the model involves many tables and FK relationships, and reacting to an event usually requires all relationships under the subject entity to be loaded, we have elected to maintain the entire set of data in an in-memory cache rather than load the entire object graph for each event. The image below shows a simplified version of our model:-
At program start-up we load all the interesting ClassA instances (and their associated dependency graph) via a temporary DbContext and insert into a Dictionary (ie our cache). When an event arrives we find the ClassA instance in our cache and attach it to a per-event DbContext via DbSet.Attach(). The program is written using the await-async pattern throughout and multiple events can be processed at the same time. We protect the cached objects from being accessed concurrently by the use of locks so we guarantee that a cached ClassA can be loaded into a DbContext only one-at-a-time. So far so good, the performance is excellent and we are happy with the mechanism. But there is a problem. Although the entity graph is fairly self-contained under ClassA, there are some POCO classes representing what we consider to be read-only static-data (shaded in orange in the image). We have found that EF sometimes complains
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
when we attempt to Attach() two different instances of ClassA at the same time (even though we are attaching to different Dbcontexts) because they share a reference to the same ClassAType. This is demonstrated by the code snippet below:-
ConcurrentDictionary<int,ClassA> theCache = null;
using(var ctx = new MyDbContext())
{
var classAs = ctx.ClassAs
.Include(a => a.ClassAType)
.ToList();
theCache = new ConcurrentDictionary<int,ClassA>(classAs.ToDictionary(a => a.ID));
}
// take 2 different instances of ClassA that refer to the same ClassAType
// and load them into separate DbContexts
var ctx1 = new MyDbContext();
ctx1.ClassAs.Attach(theCache[1]);
var ctx2 = new MyDbContext();
ctx2.ClassAs.Attach(theCache[2]); // exception thrown here
Is there any way to inform EF that ClassAType is read-only/static and we don't want it to ensure that each instance can be loaded into only one DbContext? So far the only way around the problem I've found is to modify the POCO generator to ignore these FK relationships, so they are not part of the entity model. But this complicates the programming because there are processing methods in ClassA which need access to the static data.
I think the key to this question is what exactly the exception means:-
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
It occurred to me that perhaps this exception is Entity Framework complaining that an instance of an object has been changed in multiple DbContexts rather than simply being referenced by objects in multiple DbContexts. My theory was based on the fact that the generated POCO classes have reverse FK navigation properties, and that Entity Framework would naturally attempt to fix-up these reverse navigation properties as part of the process of attaching the entity graph to the DbContext (see a description of the fix-up process)
To test out this theory I created a simple test project where I could enable and disable the reverse navigation properties. To my great joy I discovered that the theory was correct, and that EF is quite happy for the objects to be referenced multiple times so long as the objects themselves don't change - and this includes navigation properties being changed by the fix-up process.
So the answer to the question is simply follow 2 rules:-
Ensure the static data objects are never changed (ideally they should have no public setter properties) and
Do not include any FK reverse navigation properties pointing back to the referring classes. For users of the Reverse POCO Generator I have made a suggestion to Simon Hughes (the author) to add an enhancement making this a configuration option.
I've included the test classes below:-
class Program
{
static void Main(string[] args)
{
ConcurrentDictionary<int,ClassA> theCache = null;
try
{
using(var ctx = new MyDbContext())
{
var classAs = ctx.ClassAs
.Include(a => a.ClassAType)
.ToList();
theCache = new ConcurrentDictionary<int,ClassA>(classAs.ToDictionary(a => a.ID));
}
// take 2 instances of ClassA that refer to the same ClassAType
// and load them into separate DbContexts
var classA1 = theCache[1];
var classA2 = theCache[2];
var ctx1 = new MyDbContext();
ctx1.ClassAs.Attach(classA1);
var ctx2 = new MyDbContext();
ctx2.ClassAs.Attach(classA2);
// When ClassAType has a reverse FK navigation property to
// ClassA we will not reach this line!
WriteDetails(classA1);
WriteDetails(classA2);
classA1.Name = "Updated";
classA2.Name = "Updated";
WriteDetails(classA1);
WriteDetails(classA2);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
System.Console.WriteLine("End of test");
}
static void WriteDetails(ClassA classA)
{
Console.WriteLine(String.Format("ID={0} Name={1} TypeName={2}",
classA.ID, classA.Name, classA.ClassAType.Name));
}
}
public class ClassA
{
public int ID { get; set; }
public string ClassATypeCode { get; set; }
public string Name { get; set; }
//Navigation properties
public virtual ClassAType ClassAType { get; set; }
}
public class ClassAConfiguration : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<ClassA>
{
public ClassAConfiguration()
: this("dbo")
{
}
public ClassAConfiguration(string schema)
{
ToTable("TEST_ClassA", schema);
HasKey(x => x.ID);
Property(x => x.ID).HasColumnName(#"ID").IsRequired().HasColumnType("int").HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
Property(x => x.Name).HasColumnName(#"Name").IsRequired().HasColumnType("varchar").HasMaxLength(50);
Property(x => x.ClassATypeCode).HasColumnName(#"ClassATypeCode").IsRequired().HasColumnType("varchar").HasMaxLength(50);
//HasRequired(a => a.ClassAType).WithMany(b => b.ClassAs).HasForeignKey(c => c.ClassATypeCode);
HasRequired(a => a.ClassAType).WithMany().HasForeignKey(b=>b.ClassATypeCode);
}
}
public class ClassAType
{
public string Code { get; private set; }
public string Name { get; private set; }
public int Flags { get; private set; }
// Reverse navigation
//public virtual System.Collections.Generic.ICollection<ClassA> ClassAs { get; set; }
}
public class ClassATypeConfiguration : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<ClassAType>
{
public ClassATypeConfiguration()
: this("dbo")
{
}
public ClassATypeConfiguration(string schema)
{
ToTable("TEST_ClassAType", schema);
HasKey(x => x.Code);
Property(x => x.Code).HasColumnName(#"Code").IsRequired().HasColumnType("varchar").HasMaxLength(12);
Property(x => x.Name).HasColumnName(#"Name").IsRequired().HasColumnType("varchar").HasMaxLength(50);
Property(x => x.Flags).HasColumnName(#"Flags").IsRequired().HasColumnType("int");
}
}
public class MyDbContext : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<ClassA> ClassAs { get; set; }
public System.Data.Entity.DbSet<ClassAType> ClassATypes { get; set; }
static MyDbContext()
{
System.Data.Entity.Database.SetInitializer<MyDbContext>(null);
}
const string connectionString = #"Server=TESTDB; Database=TEST; Integrated Security=True;";
public MyDbContext()
: base(connectionString)
{
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ClassAConfiguration());
modelBuilder.Configurations.Add(new ClassATypeConfiguration());
}
}
I think this might work: try using AsNoTracking in those entities DbSets when selecting them at program start-up:
dbContext.ClassEType.AsNoTracking();
This will disable the change tracking for them, so EF will not try to persist them.
Also, the POCO class for those entities should only have read-only properties (with no set method).
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.
I've run into an annoying problem when trying to update an entity that has a relationship which I don't care about in the frontend.
The problem is that when automapper creates my Item the default constructor sets the relationship to an empty list (should be null) which causes problems when EF is trying to save it, because now it thinks that it should delete the relationship the existing entity has.
I was thinking that this is a problem many have had, but google didn't seem to think so. Am I approaching this in a wrong way? How do you maintain your entity relationships when mapping from DTO to entity model?
Classes
// normal code first POCO class
public class Item
{
public Item()
{
Others = new List<Other>();
}
public int Id {get; set;}
public virtual ICollection<Other> Others {get; set;}
}
// my DTO
public class ItemDTO
{
public int Id {get; set;}
}
Controller action
[HttpPost]
public void PostAction(ItemDTO dto)
{
var item = Mapper.Map<Item>(dto);
// The problem here is that item.Others.Count is 0, should be null
// so EF thinks it needs to delete the relationships
_repo.Update(item);
}
There's two ways you can go about this:
Either you could just set the relation to null after AutoMapper has mapped it:
[HttpPost]
public void PostAction(ItemDTO dto)
{
var poco = Mapper.Map<Item>(dto);
poco.Others = null;
_repo.Update(poco);
}
Or you could create a custom automapper profile that always sets it to null:
public class ItemMap : Profile
{
protected override void Configure()
{
CreateMap<Item, Listing>().ForMember(d => d.Others, o => o.UseValue(null));
}
}
You'd use the first method if that's the only place you want it. Use the second one if it's always the case
I have entity with nested collection and according model. Let's say:
class Entity
{
public IList<NestedEntity> Nested { get; set; }
}
and
class Model
{
public IList<NestedModel> Nested { get; set; }
}
and I need something like the following:
var existingEntity = service.GetEntity(id);
Mapper.Map<Model, Entity>(model, existingEntity);
// now existingEntity is an updated entity and we can save it
service.SaveEntity(existingEntity);
So while mapping nested collection mapper should remove the items which are not exist in existing entity, add which are newly created and just update the others.
How should I configure AutoMapper to reach such behavior?
I found that custom ValueResolvers ResolveCore method has no target class parameter so it can only create but not update collection.
Here is the solution for Model -> Entity mapping.
Mapper.CreateMap<NestedModel, NestedEntity>();
Mapper.CreateMap<Model, Entity>()
.ForMember(x => x.Nested, opt => opt.ResolveUsing<Resolver>());
public class Resolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
var targetCollection = ((Entity) source.Context.DestinationValue).Nested;
// TODO: Custom mapping here.
return source.New(targetCollection, typeof(NestedEntity[]));
}
}