简体   繁体   中英

Moving objects between lists AutoMapper and EF6

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!

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.

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);
                }
            }
        }
    }
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM