I am using automapper to import from one DB into a new DB with a slightly different structure. I am not sure how to handle code tables such as TargetType below. Automapper seems to be creating duplicates when importing them(“Unable to determine the principal end of the ‘Models.ShipTarget_TargetType’ relationship. Multiple added entities may have the same primary key.” on db.SaveChanges()). I’ve also had the same problem with many to many relationships, but I don’t get the error there, because the bridge table allows AutoMapper to happily create the duplicates without violating any constraints.
In other words, when mapping a ShipTarget(there’s lots of these), when it maps the TargetType field(there’s only a few TargetTypes), it always creates a new TargetType, rather than checking whether it already exists in the destination and using the pre-existing instance. Since TargetType is a codes tables, I would want the same instance for a particular value shared across many ShipTargets.
Note that all the mapping is being done in one pass before save changes is called. So absolutely none of the TargetTypes exist in the destination database, so I expect it to create one of each from the source code table, but it is creating duplicates as if each ShipTarget is referencing a unique TargetType.
My current mapping is this(simplified, hopefully no typos):
var src2NewShip = Mapper.CreateMap<SourceDataModel.Ship, Ship>()
.ForMember(newShp => newShp.Targets, c => c.MapFrom(srcShp => srcShp.ShipTargets));
var srcShipTargetType2NewTargetType = Mapper.CreateMap<SourceDataModel.ShipTargetType, TargetType>();
var srcShipTarget2SrcTarget = Mapper.CreateMap<SourceDataModel.ShipTarget, ShipTarget>()
.ForMember(newTarget => newTarget.TargetType, c => c.MapFrom(srcTarget => srcTarget.ShipTargetType));
How do I ensure that it only creates as many TargetTypes as there are ShipTargetTypes in the source db? Instead of duplicating them when they are referenced by more than one ShipTarget.
This pseudo code expresses one idea I had to solve the problem, but this seems pretty convoluted and I’m not sure exactly how to get it working right anyhow:
var srcShipTarget2SrcTarget = Mapper.CreateMap<SourceDataModel.ShipTarget, ShipTarget>()
.ForMember(newTarget => newTarget.TargetType, c => c.MapFrom(srcTarget =>
{
newDb.Ships.SelectMany(s => s.ShipTargets).FirstOrDefault(st=>st.TargetType.UniqueName == srcTarget.UniqueName );
//here I would return the found instance, or call upon automapper to map srcTarget to a new TargetType and return that
//essentially, use existing, or return new
);
public class TargetType
{
[Key]
public int TargetTypeKey { get; set; }
public string UniqueName { get; set; }
...
}
public class ShipTarget
{
[Key]
public int ShipTargetKey { get; set; }
public int ShipKey { get; set; }
[ForeignKey("ShipKey")]
public Ship Ship { get; set; }
public int TargetTypeKey { get; set; }
[ForeignKey("TargetTypeKey")]
public TargetType TargetType { get; set; }
...
}
public class Ship
{
[Key]
public int ShipKey { get; set; }
public virtual ICollection<ShipTarget> Targets { get; set; }
...
}
I wanted to do the below in the ConvertUsing of the TargetType so that it’d apply anywhere the code table was referenced, but I couldn’t figure out how to tell it to use a default mapping to create a new one if it didn’t exist, such as in the below where I do
Mapper.Map(srcTarget.ShipTarget, newTarget.TargetType);So instead for the member of the class referencing the code table, I tell it to initially ignore that member, and then map it in an AfterMap by checking if the entry exists in the new code table, using that entry, and if it doesn’t exist then create a new one.