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.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();
}
9
  • 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)? Commented Jan 27, 2017 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. Commented Jan 27, 2017 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 Commented Jan 27, 2017 at 22:04
  • hi Gert I will create a db diagram now Commented Jan 27, 2017 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. Commented Jan 27, 2017 at 22:17

1 Answer 1

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.
Sign up to request clarification or add additional context in comments.

8 Comments

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<ImageModel> Images { get; set; } public virtual PostModel Post { get; set; } in my view model. Thanks for any help.
reference only started happening when i swapped from this <input id="ImagePath" title="Upload a product image" multiple="multiple" type="file" name="files" /> to this @Html.TextBoxFor(model => model.ImagePath, new { type = "file" , multiple = "multiple" , name= "files"}) but are they not the same ?
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.
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.
In the 2nd dot point I suggested you should be using a view model with a property IEnumerable<HttpPostedFileBase> 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)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.