How do I use ModelBinders with a DbContext stored in OwinContext?

I have a simple model binding:

public class PersonBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // ...
        // id = routevalues[ id ]

        var db = controllerContext.HttpContext.GetOwinContext().Get<ApplicationDbContext>();

        return db.Set<Person>().FirstOrDefault(o => o.Id == id);
    }
}

      

And it works great. Here, for example:

public ActionResult Edit(Person entity, int? id)
{
    if (entity == null && id.HasValue)
        throw new HttpException(404, "Person not found.");

    return View(person);
}

      

The problem is when I try to store it in the database:

// DbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>()

[HttpPost]
public async Task<ActionResult> Edit(Person entity, int? id)
{
    if (entity == null && id.HasValue)
        throw new HttpException(404, "Person not found.");

    if (ModelState.IsValid)
    {

        // WORKS when inserting a new person to the database
        if (!id.HasValue)
            DbContext.People.Add(entity);
        else
        {
            // if I try to attach I get an error: see bellow
            // if I don't attach, it does nothing
        }

        await DbContext.SaveChangesAsync();
    }

    return View(entity);
}

      

Connection error:

System.InvalidOperationException: Binding of an object of type "..Person" failed because another object of the same type already has the same primary key value. This can happen when using the "Attach" method or setting the state of an object to "Unchanged" or "Modified", if any objects on the chart have conflicting key values. This may be due to the fact that some objects are new and have not yet received the database key values. In this case, use the "Add" or "Added" object state method to track the graph, and then set the state of the non-new objects to "No Changed" or "Modified" as needed.

The entity status is displayed as Detached

when I run it in a controller action:

DbContext.Entry(entity).State

      

Why is this happening? How can I fix this? Can't use binders for this?

+3


source to share


2 answers


Before you do Attach, what if you detach an existing object? Something like: (not tested - sorry, this is based on some code in an old project where we had to do something like this for various reasons, but hopefully gives an idea)

    if (!id.HasValue)
        DbContext.People.Add(entity);
    else
    {
        var attachedEntity = DbContext.People.Find(id);
        if (attachedEntity != null && DbContext.Entry(attachedEntity).State != EntityState.Detached)
        {
            DbContext.Entry(attachedEntity).State = EntityState.Detached;
            // You may need to recursively detach child entities here if any
        }
        DbContext.People.Attach(entity);
        DbContext.Entry(entity).State = EntityState.Modified; 
    }

      


EDIT:



As the main problem is that your DbContext instance is different from what the documentation for IOwinContext.Get<T>

says, "Gets a value from the OWIN environment, or returns a default (T) if not present."

http://msdn.microsoft.com/en-us/library/dn270607(v=vs.113).aspx

you can somewhere (I haven't used OWIN so that I'm not sure where the best place would be, but I would guess at the beginning of your pipeline) call IOwinContext.Set<ApplicationDbContext>

and pass a new DbContext to it so that your 2 calls HttpContext.GetOwinContext (). Get () is using the same instance?

+5


source


When you are editing, set the state of the object to changed before saving.

DbContext.Entry(entity).State = EntityState.Modified; 

      



As already now, the entity structure does not track this object, so you need to explicitly indicate that it is a modified object before trying to save it.

Another way would be to get the entity from the database so that the entity structure will keep track of it. Then you change its values ​​with the changed values, and when you do that, the infrastructure of that object will notice it and automatically change it. Then you can save your changes.

-1


source







All Articles