0

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.stack.imgur.com/2uW6r.jpg

Here is the database Diagram :https://i.stack.imgur.com/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();
}
  • Not sure I understand what your wanting to achieve. Do you want to save a single `PostModel` that contains multiple images (in which case you need a separate table for image paths with a FK to the `PostModel` ID)? –  Jan 27 '17 at 21:54
  • *and the images paths stored on a second table and give each of the a foreign key of the ImageId*. Very vague. Don't use words. Show a db diagram. – Gert Arnold Jan 27 '17 at 22:04
  • Hi Stephen, yes I have a single table currently and I am asking how can I make it so that I have a second table that stores the images. so that when the user creates a post it creates a post in the post table along with a foreign key that connects each image to that one post. Sorry if i am bad at explaining I havent worked with multiple tables before. As you can see in the post I have the second table commented out as I am not sure how to use it and I want to post how it is working currently so people can advise me on changes and how querys work etc, thanks for your help – WellThisIsAkward Jan 27 '17 at 22:04
  • hi Gert I will create a db diagram now – WellThisIsAkward Jan 27 '17 at 22:05
  • @WellThisIsAkward You have commented out what you do need to make this work - the `public virtual ImageModel ImageModel { get; set; }` property and the `ImageModel` class. In the POST method, create and save `PostModel`, get its `PostID `, then create an `ImageModel` for each file (set its `PostID` property) and save. –  Jan 27 '17 at 22:17
  • Note also, do not save the file with `FileName` only (what happens if different users save images with the same name). One option is to create a `Guid` and use that for the file name, and then you `ImageModel` model contains properties for the `Path` (using the `Guid`) and the `DisplayName` (using `file.FileName`) which will be used for display purposes –  Jan 27 '17 at 22:20
  • Hi Stephen, the commented out code was commented out encase people wanted to see how it worked currently and the commented code was how i tried to use multiple tables. but i have realized my mistake by doing that it is less clear now I have changed my post to show just what my program looks like currently and the way I am trying to use multiple tables. when i use more than one table i get a error trying to update the database. (and nice tip about the guid i will look into it after I get this program working the simpler way as its my first time using more than one table). – WellThisIsAkward Jan 27 '17 at 22:50
  • @WellThisIsAkward, If you want to notify a user, then you need to start the comment as this one does (if more than one user has previously commented) –  Jan 27 '17 at 23:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134229/discussion-between-stephen-muecke-and-wellthisisakward). –  Jan 28 '17 at 00:03

1 Answers1

1

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.
Community
  • 1
  • 1
  • I have tried updating my program with your recommendations but I am now receiving a null reference exception on the files for each method in the create post method. Also I know I have made mistakes trying to implement the View Model as I dont think I am ment to have public int CreatePostId { get; set; } public virtual ICollection Images { get; set; } public virtual PostModel Post { get; set; } in my view model. Thanks for any help. – WellThisIsAkward Jan 28 '17 at 12:29
  • reference only started happening when i swapped from this to this @Html.TextBoxFor(model => model.ImagePath, new { type = "file" , multiple = "multiple" , name= "files"}) but are they not the same ? – WellThisIsAkward Jan 28 '17 at 12:52
  • First you cannot just change the question and invalidate my answer - I have rolled back you changes. If you have a new problem, then you can ask a new question of append the new code you have tried. –  Jan 28 '17 at 20:52
  • sorry Stephen didnt know thought I was meant change the question as i make changes to my program get the entire solution if someone was looking for the complete solution for the future , but now I know for again I will mark it as solved and ask separate questions. – WellThisIsAkward Jan 28 '17 at 21:01
  • In the 2nd dot point I suggested you should be using a view model with a property `IEnumerable Images` and use `@Html.TextBoxFor(m => m.Images, new { type = "file", multiple = "multiple" })` - note there is no `new { name="files" }` in it which does absolutely nothing. If you did do that then the signature of POST method would be just `public ActionResult Create(PostVM model)` and then the loop would be `foreach (var file in model.Images)` –  Jan 28 '17 at 21:02
  • OK, I have looked at the edits you made and you have numerous problems with your attempt at a view model. I'll add some extra information later of what it should have been –  Jan 28 '17 at 21:08
  • Thanks Stephen for your help and for giving me extra information with the problems. – WellThisIsAkward Jan 28 '17 at 21:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134276/discussion-between-stephen-muecke-and-wellthisisakward). –  Jan 28 '17 at 21:22