Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Editing an object in entity framework and saving it to the database in ASP.NET MVC 2.0

So I know that EF entities track their own changes and persist them to the database when savechanges is called, but what about this scenario...

I have a page that is designed to edit a blog post. It has two action methods.

    [HttpGet]
    public ViewResult EditBlogPost(int Id)
    {
        //This action method gets the blog post by the id and returns the edit blog post page.
        BlogPost blogPost = db.BlogPosts.Where(x => x.Id == Id).FirstOrDefault();
        if (blogPost == null)
        {
            ViewData["message"] = "Blog post not found.";
            return View("Result");
        }
        return View("ManageBlogPost", blogPost);
    }

    [HttpPost]
    public ViewResult EditBlogPost(BlogPost blogPost)
    {
        //This one is where I'm having issues. When model binding populates blogPost, is it auto-tracking still? For some reason SaveChanges() doesn't seem to persist the updates.
        if (!ModelState.IsValid)
            return View("ManageBlogPost");
        db.AttachTo("BlogPosts", blogPost); //I tried this method, it seemed to be what I wanted, but it didn't help.
        db.SaveChanges();
        ViewData["message"] = "Blog post edited successfully.";
        return View("Result");
    }

Here is the view that these return:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Master.Master" Inherits="System.Web.Mvc.ViewPage<BlogProject.Models.BlogPost>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">

    <% if (Model != null)
       { %>
            <h2>Edit Blog Post</h2>
    <% }
       else
       { %>
            <h2>Add Blog Post</h2>
    <% } %>
    <% using (Html.BeginForm())
       { %>
            <% if (Model != null)
               { %>
                    <%: Html.HiddenFor(x => x.Id)%> <!-- Is this the way to keep un-editable data hidden from the edit form and have them repopulate on the next model bind? What if someone went to modify their POST using something like Fiddler? Couldn't they theoretically edit these fields before the POST? -->
                    <%: Html.HiddenFor(x => x.Date) %>
                    <%: Html.HiddenFor(x => x.Author) %>
                    <%: Html.HiddenFor(x => x.FriendlyUrl) %>
            <% } %>
            Title:<br />
            <%: Html.TextBoxFor(x => x.Title, new { Style = "Width: 90%;" })%>
            <br />
            <br />
            Summary:<br />
            <%: Html.TextAreaFor(x => x.Summary, new { Style = "Width: 90%; Height: 50px;" }) %>
            <br />
            <br />
            Body:<br />
            <%: Html.TextAreaFor(x => x.Body, new { Style = "Height: 250px; Width: 90%;" })%>
            <br />
            <br />
            <input type="submit" value="Submit" />
    <% } %>

</asp:Content>

I'm a little confused here. Adding blog posts seems to work fine, but editing them is another story.

like image 511
Chev Avatar asked Feb 16 '11 22:02

Chev


People also ask

How Entity Framework can save data in database in C#?

Insert Data Add methods add a new entity to a context (instance of DbContext) which will insert a new record in the database when you call the SaveChanges() method. In the above example, context. Students. Add(std) adds a newly created instance of the Student entity to a context with Added EntityState.

How do I save changes in Entity Framework?

In Entity Framework, the SaveChanges() method internally creates a transaction and wraps all INSERT, UPDATE and DELETE operations under it. Multiple SaveChanges() calls, create separate transactions, perform CRUD operations and then commit each transaction.


2 Answers

The solution is not to take in the blog post object in your edit action method. Instead, do something that looks like this:

[HttpPost]
public ViewResult EditBlogPost(int postID)
{
    var post = db.BlogPosts.Single(p => p.PostID = postID);
    TryUpdateModel(post);

    if (!ModelState.IsValid)
        return View("ManageBlogPost");

    db.SaveChanges();

    ViewData["message"] = "Blog post edited successfully.";
    return View("Result");
}

This way the object is attached to the context and the EF can track changes properly. The UpdateModel method is a time saver that automatically matches fields in the form collection to the fields in the model and updates them for you.

Here is the documentation for UpdateModel: MSDN

like image 197
Shea Daniels Avatar answered Sep 24 '22 14:09

Shea Daniels


Entity Framework is only able to track changes on objects that are attached to a context. Objects produced by a context are automatically attached to the context that produces them. Since the object you're getting is produced by MVC, the Entity Framework doesn't know which values have been updated and which haven't been.

There are a couple of tricks you can use to tell Entity Framework that the item has been modified. One is to retrieve the entity from the context, set the changed values on that context-bound object, and then save your changes. Another is to do something like this to explicitly tell the context's ObjectStateManager that certain properties have changed:

    /// <summary>
    /// Reattach and mark specific fields on the entity as modified.
    /// </summary>
    /// <param name="objectContext">The context to attach the entity object.</param>
    /// <param name="setName">The string representation of the set that should contain the given entity object.</param>
    /// <param name="entity">The detached entity object.</param>
    /// <param name="modifiedFields">Names of fields that have been modified.</param>
    public static void AttachAsModified(this ObjectContext objectContext, string setName, object entity,
                                        IEnumerable<String> modifiedFields)
    {
        objectContext.AttachTo(setName, entity);
        ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(entity);
        foreach (String field in modifiedFields)
        {
            stateEntry.SetModifiedProperty(field);
        }
    }

My team ended up developing a framework that automatically loads the object from the context, figures out which properties are different from the new one that got passed in, and sets those properties to the values provided by the unattached object. That approach may be worth looking into for you as well. There is a slight possibility that something like AutoMapper could be made to do this for you, but I'm not sure.

Edit

Shea mentions using the controller's UpdateModel method to effectively put off the setting of the property values until after you have retrieved the entity object from your context. This sounds like as good an approach as any to me (assuming you're okay with having data-access code in your controller), but you may want to use an override to make it so you can still have the method bind to the same object type:

[HttpPost]
public ViewResult EditBlogPost(BlogPost blogPost)
{
    //This one is where I'm having issues. When model binding populates blogPost, is it auto-tracking still? For some reason SaveChanges() doesn't seem to persist the updates.
    if (!ModelState.IsValid)
        return View("ManageBlogPost");
    var dbPost = db.BlogPosts.Single(bp => bp.BlogPostId = blogPost.Id);
    UpdateModel(dbPost, "blogPost");
    db.SaveChanges();
    ViewData["message"] = "Blog post edited successfully.";
    return View("Result");
}
like image 2
StriplingWarrior Avatar answered Sep 21 '22 14:09

StriplingWarrior