Search code examples
c#asp.netasp.net-mvcentity-frameworkcode-first

update exception using multiple tables


Hi everyone so I am trying to create an application using asp.net mvc with a code first database that allows the users to be able to create a blog post with as many images as they wish.I am currently trying to have the image path in one table and the heading,body text in the other table along with a foreign key to the image path.So that I can create one post with multiple images. This is my first time using multiple tables and currently I am getting an error when it reaches this line context.SaveChanges(); in the save method when I am trying to create a post and save it to the db. Thank you for any help with this issue.

An exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll but was not handled in user code

Additional information: An error occurred while updating the entries. See the inner exception for details

I was able to get the program to work when I was using one table but it had this issue : https://i.sstatic.net/2uW6r.jpg

Here is the database Diagram :https://i.sstatic.net/9XE7T.jpg

Query that I tried to make but am not sure where to use in my code.

var query = db.PostModel.Where(x => x.PostID == PostId).Select(x => new
{
    PostID = x.PostID,
    ImageId = x.ImageModel.ImageId,
    ImagePath = x.ImageModel.ImagePath,
    Heading = x.PostModel.Heading,
    PostBody = x.PostModel.PostBody
}).FirstOrDefault();

My program

View to Create posts

@model Crud.Models.PostModel
....
@using (Html.BeginForm("Create", "Home", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <form action="" method="post" enctype="multipart/form-data">
        @Html.LabelFor(model => model.ImageModel.ImagePath)
        <input id="ImagePath" title="Upload a product image" multiple="multiple" type="file" name="files" />
        @Html.LabelFor(model => model.Heading)
        <input id="Heading" title="Heading" name="Heading" />
        @Html.LabelFor(model => model.PostBody)
        <input id="PostBody" title="PostBody" name="PostBody" />
        <p><input type="submit" value="Create" /></p>
    </form>
}

View to display posts

@model IEnumerable<Crud.Models.PostModel>
....  
@foreach (var item in Model)
{
    <div>@Html.DisplayFor(modelItem => item.Heading)</div>
    <div>@Html.DisplayFor(modelItem => item.PostBody)</div>
    <div><img class="img-thumbnail" width="150" height="150" src="/Img/@item.ImageModel.ImagePath" /></div>
}

Models

public partial class PostModel
{
    [Key]
    [HiddenInput(DisplayValue = false)]
    public int PostID { get; set; }
    public string Heading { get; set; }
    public string PostBody { get; set; }
    [ForeignKey("ImageModel")]
    public int ImageId { get; set; }
    public virtual ImageModel ImageModel { get; set; }
}

public class ImageModel
{
    [Key]
    public int ImageId { get; set; }
    public string ImagePath { get; set; }
    public string PostID { get; set; }
}

DBcontext

public class EFDbContext : DbContext
{
    public DbSet<SchoolNewsModel> SchoolNews { get; set; }
    public DbSet<PostModel> Posts { get; set; }
    public DbSet<ImageModel> Images { get; set; }
}

Controller

public ViewResult Display()
{
    return View(repository.Posts);
}
public ViewResult Create()
{
    return View("Create", new PostModel());
}
[HttpPost]
public ActionResult Create(PostModel Image, IEnumerable<HttpPostedFileBase> files)
{
    if (ModelState.IsValid)
    {
        foreach (var file in files)
        {
            PostModel post = new PostModel();
            if (file.ContentLength > 0)
            {
                file.SaveAs(HttpContext.Server.MapPath("~/Img/") + file.FileName);
                //  post.ImagePath = file.FileName;
                post.PostBody = post.PostBody;
                post.Heading = post.Heading;
            }
            repository.Save(post);
        }
    }
    return RedirectToAction("display");
}

public ViewResult PublicPostDisplay()
{
    return View(repository.Posts);
}

Repository

public IEnumerable<PostModel> Posts
{
    get { return context.Posts; }
}

public void Save(PostModel Image)
{
    if (Image.PostID == 0)
    {
        context.Posts.Add(Image);
    }
    else
    {
        PostModel dbEntry = context.Posts.Find(Image.PostID);
        if (dbEntry != null)
        {
            dbEntry.ImageModel.ImagePath = Image.ImageModel.ImagePath;
        }
    }
    context.SaveChanges();
}

Solution

  • You need to include the full details of the error. Its the See the inner exception for details that will give you the relevant information. However that will probably not matter since your models and relationships are incorrect.

    You want a PostModel to have multiple ImageModel so you need a one-many relationship and your PostModel needs have the following property

    public virtual ICollection<ImageModel> Images { get; set; }
    

    and delete the int ImageId and ImageModel ImageModel properties. In addition the ImageModel should contain public virtual PostModel Post { get; set; }

    Your POST method to create a new PostModel then becomes

    [HttpPost]
    public ActionResult Create(PostModel post, IEnumerable<HttpPostedFileBase> files)
    {
        if (!ModelState.IsValid)
        {
            return View(post);
        }
        foreach (var file in files)
        {
            if (file.ContentLength > 0)
            {
                file.SaveAs(HttpContext.Server.MapPath("~/Img/") + file.FileName);
                // Initialize a new ImageModel, set its properties and add it to the PostModel
                ImageModel image = new ImageModel()
                {
                    ImagePath = file.FileName
                };
                post.Images.Add(image);
            }
        }
        repository.Save(post);
        return RedirectToAction("display");
    }
    

    There are however multiple other issues with your code that you should address.

    1. First, your view has nested forms which is invalid html and not supported. You need to remove the inner <form> tag
    2. Your editing data, so always use a view model (refer What is ViewModel in MVC?) and the PostVM will include a property IEnumerable<HttpPostedFileBase> Images and in the view, bind to it using @Html.TextBoxFor(m => m.Images, new { type = "file", multiple = "multiple" })
    3. You have no validation at all, and your properties should include validation attributes, for example, a [Required] attribute on Heading and Body. The you need to include @Html.ValidationMessageFor() for each property in the view.
    4. You manual html for the inputs will not give you 2-way model binding and prevent any client side validation. Always use the HtmlHelper methods to generate form controls, e.g. @Html.TextBoxFor(..)
    5. Do not save the image with just the file name (multiple users may upload files with the same name and overwrite existing files. One option is to use a Guid for the file name, and include a additional property string DisplayName in ImageModel. Refer this answer for an example of that approach.