Reputation: 9828
I have started using this extension, and just want to say its excellent, thank you!
Now i have an issue, where an object can be moved from 1 collection, into another collection, and when i do this, i get an exception
InvalidOperationException: Multiplicity constraint violated
I am guessing this is because the object isnt being found in the original collection, and this extension is adding the object to the new collection, even though i want it too be moved, then upon saving, EF throws the exception, because i have 2 objects with the same key against my context.
But how can i get this to work?
So if i have the following object structure
MyRoot
| Collection
| MyChild
| Collection
| MyObject (1)
| MyChild
| Collection
| MyObject (2)
How can i move MyObject (1)
into the same collection as MyObject (2)
??
These are all basic objects, and here is some simple code
public class MyRoot
{
public int Id { get; set; }
public ICollection<MyChild> MyChildren { get; set; }
}
public class MyChild
{
public int Id { get; set; }
public int RootId { get; set; }
public MyRoot Root { get; set; }
public ICollection<MyObject> MyObjects { get; set; }
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
Each of these objects have a DTO, for the sake of this example, lets just say the objects are exactly the same, with extension DTO on the end (this is not the case in real application)
In my application, i then have an automapper profile, like so
internal class MyProfile: Profile
{
public MyProfile()
{
this.CreateMap<MyRoot, MyRootDTO>()
.ReverseMap();
this.CreateMap<MyChild, MyChildDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
this.CreateMap<MyObject, MyObjectDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
}
}
On my web api controller method, i have this, which is very simple
public async Task<IActionResult> UpdateAsync([FromBody] MyRootDTO model)
{
// get the object and all children, using EF6
var entity = await _service.GetAsync(model.Id);
// map
_mapper.Map(model, entity);
// pass object now updated with DTO changes to save
await _service.UpdateAsync(entity);
// return
return new OkObjectResult(_mapper.Map<MyRootDTO>(entity));
}
If someone could please help, that would be great!
Upvotes: 1
Views: 370
Reputation: 9828
To get this to work, i didnt change EF keys, but implemented a method in my AutoMapper profile. I looped through the object to see if the child was in a different list, and if so, moved the object into that new list. This way automapper will be able to match the object based on ID still.
I added the below code into the .BeforeMap
method
Not that my base level object is called Root
in this example, so parameter s
is type RootModel
(from my web api) and parameter d
is type Root
(from EF). Both RootModel
and Root
have a collection called Sections
.BeforeMap((s, d) =>
{
// we are going to check if any child has been moved from 1 parent to another, and
// if so, move the child before the mapping takes place, this way AutoMapper.Collections will not
// mark the object as orphaned in the first place!
foreach (var srcParent in s.Sections)
{
// only loop through old measures, so ID will not be zero
foreach (var srcChild in srcParent.Children.Where(e => e.Id != 0))
{
// check if the srcChild is in the same dest parent?
var destChild = d.Sections.SelectMany(e => e.Children).Where(e => e.Id == srcChild.Id).FirstOrDefault();
// make sure destination measure exists
if (destChild != null)
{
// does the destination child section id match the source section id? If not, child has been moved
if (destChild.ParentId != srcParent.Id)
{
// now we need to move the child into the new parent, so lets find the destination
// parent that the child should be moved into
var oldParent = destChild.Parent;
var newParent = d.Sections.Where(e => e.Id == srcParent.Id).FirstOrDefault();
// remove child from children collection on oldSection and add to newSection
oldParent.Children.Remove(destChild);
// if newParent is NULL, it is because this is a NEW section, so we need to add this new section
// NOTE: Root is my based level object, so your will be different
if (newParent == null)
{
newParent = new Parent();
d.Sections.Add(newParent);
newParent.Root = d;
newParent.RootId = d.Id;
}
else
{
// change references on the child
destChild.Parent = newParent;
destChild.ParentId = newParent.Id;
}
newParent.Children.Add(destChild);
}
}
}
}
})
Upvotes: 0
Reputation: 30628
I don't think your problem has anything to do with AutoMapper here, it's an Entity Framework problem. If you remove something from a child collection in EF, it doesn't automatically get deleted unless you either call a .Delete on it, or the key for the object is a composite key including the parent.
I would suggest making a composite key, such as the following:
public class MyObject
{
[Column(Order = 1)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[Column(Order = 0)]
[Key]
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
The [DatabaseGenerated]
option keeps the Id
column as an Identity - EF's default with a composite key is for no automatic identity.
You can do the same thing on your MyChild
entity.
Upvotes: 1