Good day,
I'm write asp.net mvc3 project with bundle of SQL Server 2012 & EF, I'm use dropdownlist for create & update entry in database. Everything works fine when I add page, but when I'm try to Edit it's unable to save changes. I was try to take brackpoint and in debugging mode Inner exception told that
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
This is my code:
[Table("Pages", Schema = "myprojectname")]
[Bind(Exclude = "PageId")]
public class Page
{
public int PageId { get; set; }
public string PageContent { get; set; }
public int PageTitleId { get; set; }
public virtual PageTitle PageTitle { get; set; }
}
[Table("PageTitles", Schema = "myprojectname")]
public class PageTitle
{
public int PageTitleId { get; set; }
public string Title { get; set; }
public List<Page> Pages { get; set; }
}
public DbSet<Page> Pages { get; set; }
public DbSet<PageTitle> PageTitles { get; set; }
public ActionResult PageEdit(int id)
{
Page page = context.Pages.Find(id);
ViewBag.PageTitleId = new SelectList(context.PageTitles, "PageTitleId", "Title", page.PageTitleId);
return View(page);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult PageEdit(Page page)
{
try
{
if (ModelState.IsValid)
{
context.Entry(page).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index", "Administrator");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to save changes. Please try again");
}
ViewBag.PageTitleId = new SelectList(context.PageTitles, "PageTitleId", "Title", page.PageTitleId);
return View(page);
}
#model BalticCenter.Web.Models.Entities.SitePages.Page
#{
ViewBag.Title = "PageEdit";
Layout = "~/Views/Shared/_AdministratorLayout.cshtml";
}
<h2>PageEdit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Page</legend>
#Html.HiddenFor(x => x.PageId)
<div class="editor-label">
#Html.LabelFor(model => model.PageTitleId, "PageTitle")
</div>
<div class="editor-field">
#Html.DropDownList("PageTitleId", String.Empty)
#Html.ValidationMessageFor(model => model.PageTitleId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PageContent)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.PageContent)
#Html.ValidationMessageFor(model => model.PageContent)
</div>
<p>
<input type="submit" value="Edit" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I don't see my mistake. Any ideas?
You have your class Page decorated with [Bind(Exclude = "PageId")]
This means that when you set EntityState to Modified the Entity have no primeary key (id)
context.Entry(page).State = EntityState.Modified;
context.SaveChanges();
And there fore the database can't save the changes
Related
I have a simple mvc5 code first application, It has a ms SQL database in the back-end and and a form in the front-end.
While I insert into database via the front end form, it does not generate any error, everything seems OK but when i check the back end database table, then all values in the newly inserted row are showing as NULL.
This is my code for model:
public class students
{
public int Id { get; set; }
[Display(Name = "Name")]
public string st_name { get; set; }
[Display(Name = "Father's Name")]
public string st_father_name { get; set; }
public string st_contact { get; set; }
}
This is the View Model class:
public class AddStudentViewModel
{
public students stdntss { get; set; }
}
This is the controller:
public ActionResult Index()
{
var std = _context.stdnts;
if (std==null)
{
return Content("Nothing Found");
}
return View(std);
}
public ActionResult AddStudent()
{
return View();
}
[HttpPost]
public ActionResult Insert(students st)
{
_context.stdnts.Add(st);
_context.SaveChanges();
return RedirectToAction("Index","Students");
}
And finally this is the view:
#model school2.ViewModels.AddStudentViewModel
#{
ViewBag.Title = "AddStudent";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>New student's registration form</h2>
#using (Html.BeginForm("Insert","Students"))
{
<div class="form-group">
#Html.LabelFor(m=> m.stdntss.st_name)
#Html.TextBoxFor(m=> m.stdntss.st_name, new { #class="form-control"})
</div>
<div class="form-group">
#Html.LabelFor(m => m.stdntss.st_father_name)
#Html.TextBoxFor(m => m.stdntss.st_father_name, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.stdntss.st_contact)
#Html.TextBoxFor(m => m.stdntss.st_contact, new { #class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Save</button>
}
Kindly assist me if anyone has any clue?
One way to solve this is to change the POST method to accept the same model as the view.
try changing
public ActionResult Insert(students st)
{
_context.stdnts.Add(st);
_context.SaveChanges();
return RedirectToAction("Index","Students");
}
to
public ActionResult Insert(AddStudentViewModel st)
{
_context.stdnts.Add(st.stdntss );
_context.SaveChanges();
return RedirectToAction("Index","Students");
}
or changing the model of the form to simply be student.
I think that change #Html.TextBoxFor(model=> model.stdntss.st_name, new { #class="form-control"}). because call Model, #model school2.ViewModels.AddStudentViewModel . Variable default Model.
I am using MVC in order to build a blog. What I want is to save post comments to its corresponding place in the database but it does not work.
My post model is as follows:
public class Post
{
[Key]
public int PostId { get; set; }
public string Title { get; set; }
public DateTime CreatedDate{get;set;}
public DateTime UpdateDate { get; set; }
public string Body { get; set; }
public ICollection<Comment> Comments { get; set;}
public ICollection<Tag> Tags { get; set; }
}
My Comment model is as follows:
public class Comment
{
[Key]
public int CommentId { get; set; }
public int PostId { get; set; }
[ForeignKey("PostId")]
public virtual Post Post{get; set;}
public string CommentCreateDate { get; set; }
public string CommentUpdateDate { get; set; }
public string CommeterName { get; set; }
public string EmailAddress { get; set; }
public string CommentText { get; set; }
public bool Approved { get; set; }
}
I have the following Action Methods:
[HttpGet]
public ActionResult CreateComment()
{
return View();
}
[HttpPost]
[ValidateInput(false)]
public ActionResult CreateComment(int id, string name, string email, string txt, bool aproved = false)
{
Post post = GetPost(id);
Comment comment = new Comment();
comment.Post = post;
comment.CommentCreateDate = DateTime.Now.ToString();
comment.CommeterName = name;
comment.EmailAddress = email;
comment.CommentText = txt;
comment.Approved = aproved;
db.Comments.Add(comment);
db.SaveChanges();
m_commentList.Add(comment);
return RedirectToAction("CreateComment", new { id = id });
}
And in my view I am trying this:
#model Blog.Models.Comment
#{
ViewBag.Title = "CreateComment";
}
<h2>Create a Comment</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Enter Comment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CommeterName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CommeterName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CommentText)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.CommentText)
</div>
<p>
<input type="submit" value="Create comment" />
</p>
</fieldset>
}
I get no exception but none of the data from the form is being saved. Only the data that is set in the action result, that is, CommentCreateDate and Approved. I am not sure what I am doing wrong.
I have tried a second option which is to include the id of the comment in BeginForm() as follows:
#using (Html.BeginForm(new {id = Model.CommentId}))
{
<fieldset>
<legend>Enter Comment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CommeterName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CommeterName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CommentText)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.CommentText)
</div>
<p>
<input type="submit" value="Create comment" />
</p>
</fieldset>
}
This will give me a null reference exception even if I use the new keyword :
System.NullReferenceException: Object reference not set to an instance of an object.
Why is this happening? Can anybody help?
Thank you
Your action signature should be:
public ActionResult CreateComment(Comment model)
The names generated for the form fields will bind back to the properties of the same model class. There's no way for the framework to know, for example, that the CommenterName property should match up to the name parameter of the action.
Your second example makes very little sense - you're trying to write out the ID but you have never set one. In fact, you don't even pass a Comment to the view with the form, which is why you get a NullReferenceException:
[HttpGet]
public ActionResult CreateComment()
{
return View();
}
Also, you should be careful with what fields you expose to your models and actions. For example, a user could easily force their comment to be approved just by adding the following markup via their browser's development console:
<input type="hidden" name="approved" value="true" />
Anything that is either in your model properties or a parameter to your action can be set by the user.
An altogether better option would be to use a dedicated model class for the form:
public class CreateCommentViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Text { get; set; }
}
Then to map this to your Comment in your action with:
[HttpPost]
public ActionResult CreateComment(CommentViewModel model)
{
var comment = new Comment();
comment.CommenterName = model.Name;
// etc...
}
This prevents the user from being able to set things like Approved and CreatedDate.
I have edit page and controller that creates new model object and fills some data from db into this object then send a model object to view. When I click the submit button, some fields in this object have been cleared.
For example:
Before:
user_id
name
birth_date
username
password
id_role
email
After (Fields that are not null or empty):
name
username
birth_date
The model:
public partial class Users
{
public Users()
{
this.Albums = new HashSet<Albums>();
this.History = new HashSet<History>();
this.Country = new HashSet<Country>();
this.Artists = new HashSet<Artists>();
this.SelectedCountries = new List<string>();
}
[DisplayName("User ID")]
public System.Guid user_id { get; set; }
[DisplayName("Name")]
public string name { get; set; }
[DisplayName("Birth date")]
public Nullable<System.DateTime> birth_date { get; set; }
[DisplayName("Username")]
public string username { get; set; }
[DisplayName("Password")]
public string password { get; set; }
[DisplayName("Rights")]
public System.Guid id_role { get; set; }
[DisplayName("User Email")]
public string email { get; set; }
public bool isRemember { get; set; }
public virtual ICollection<Albums> Albums { get; set; }
public virtual ICollection<History> History { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<Country> Country { get; set; }
public virtual ICollection<Artists> Artists { get; set; }
public virtual List<string> SelectedCountries { get; set; }
}
Edit method:
public ActionResult Edit()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
var userName = HttpContext.User.Identity.Name;
var user = db.Users.Where(x => x.username == userName).FirstOrDefault();
ViewBag.Countries = new MultiSelectList(db.Country, "id_country", "name", user.SelectedCountries);
return View(user);
}
return HttpNotFound();
}
Edit method for handling post request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Users users)
{
if (ModelState.IsValid)
{
foreach (var country in users.SelectedCountries)
{
var dbCountry = db.Country.Find(new Guid(country));
if (dbCountry != null)
users.Country.Add(dbCountry);
}
db.Entry(users).State = System.Data.Entity.EntityState.Modified;
//There handle of string array goes
db.SaveChanges();
return RedirectToAction("Index");
}
return View(users);
}
View:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Users</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.birth_date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.birth_date)
#Html.ValidationMessageFor(model => model.birth_date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.username)
#Html.ValidationMessageFor(model => model.username)
</div>
<div class="editor-label">
#Html.Label("Country")
</div>
<div class="editor-field">
#Html.DropDownList("SelectedCountries", (ViewBag.Countries as MultiSelectList), new { multiple = "multiple", #class = "chosen", style = "width: 350px;"})
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Thanks in advance :)
You will only receive values that are in your form. Http is stateless..
What you need to do.. is create a ViewModel. That ViewModel is the subset of properties from your domain model that are displayed in the view. Like this:
public class UserViewModel {
public string Name { get; set; }
public string Username { get; set; }
public DateTime? DateofBirth { get; set; }
}
Use this model in your view. Then, in your controller.. get the user and update the appropriate fields:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel viewModel) {
var user = db.Users.Where(x => x.username == viewModel.Username).FirstOrDefault();
user.Name = viewModel.Name;
user.Username = viewModel.Username;
// .. etc.
db.SaveChanges();
}
If you are worried about all of the manual mapping involved in this, there exists frameworks to help you with that:
Automapper
ValueInjector
You are heading down a very very daunting path if you start adding hidden fields into your view. Its a maintenance nightmare and very error prone.
The post operation only collects the values you have in the form.
If you want the other values to proceed in your controllers post-method, you can for example, add hidden fields.
#Html.HiddenFor(x => x.HiddenPostBack)
I have some problem posting a form with 'complex type' model:
I have a Model:
public class CircleEditViewModel
{
[Key]
public int CircleId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public bool IsSystem { get; set; }
public class UserInCircle
{
public UserInCircle(User user)
{
this.UserId = user.UserId;
FullName = user.FullName;
}
public int UserId { get; set; }
public byte[] Picture { get; set; }
public string FullName { get; set; }
public bool isInCircle { get; set; }
}
public List<UserInCircle> Users { get; set; }
}
My first problem was that at post event, my Users where null.. so i followed a few posts on here (like MVC- Model Binding on a Complex Type) to use a for instead of a foreach,but since i did so, my form won't post anymore:
View:
#model Wims.Website.ViewModels.CircleEditViewModel
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse('form');
});
</script>
#using (Ajax.BeginForm(Html.ViewContext.RouteData.Values["Action"].ToString(), null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "SaveDone(data)" }, new { id = "editform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Circle</legend>
#Html.Label(DateTime.Now.ToString());
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
if (Model.Users != null)
{
for (int i = 0; i < Model.Users.Count; i++)
{
<div class="userDetail">
<div>
<div>
#Html.DisplayFor(model => Model.Users[i].isInCircle);
</div>
<div class="iconDiv">
#Html.Image("~/Content/Images/defaultUser.jpg", Model.Users[i].FullName, null);
</div>
<div>
#Html.TextBoxFor(model => Model.Users[i].FullName)
#Html.HiddenFor(model => Model.Users[i].UserId)
</div>
</div>
</div>
<div style="clear: both"></div>
}
}
#Html.GenerateSecureDataControls(model => model.CircleId)
<input type="submit" value="Save" />
}
My view is rendered as a partial loaded thru ajax (not sure it makes any difference here).
Any idea why it won't post? If i remove all the '[]' like 'Users[0].FullName' to Users0.FullName i will post, but of course it won't be bound.
Thanks for your help
Edit just in case needed: Action:
[HttpPost]
public ActionResult Edit(CircleEditViewModel circleData, FormCollection collection)
{
if (ModelState.IsValid)
{
using (var logic = new CircleLogic())
{
Circle circle = logic.GetCircleById(circleData.CircleId, WebMatrix.WebData.WebSecurity.CurrentUserId);
if (circle == null)
{
return HttpNotFound();
}
else
{
circle.Name = circleData.Name;
logic.UpdateCircle(circle, GetSelectedUser(collection));
}
return PartialView("_CircleAndUsers", GetData(logic, circle.CircleId));
}
}
return this.Json(new { success = false, viewdata = RenderRazorViewToString("_CircleAndUsers", circleData) });
}
Pablo Romeo was right, i added a default ctor and it worked.
I'm building a MVC3 project, using Razor and Entity Framework code first. I have two models:
public class Translation
{
public int TranslationId { get; set; }
public string Pt { get; set; }
public string Es { get; set; }
public string En { get; set; }
}
public class Page
{
public int PageId { get; set; }
public Translation Title { get; set; }
public Translation Description { get; set; }
public int? ParentPageId { get; set; } // page can have a parent page
public Page ParentPage { get; set; }
}
I created the PagesController for CRUD for the Page model. Then I edited the Create and Edit views adding inputs for the Translation properties:
<div class="editor-field">
#Html.EditorFor(model => model.Title.Pt)
#Html.ValidationMessageFor(model => model.Title.Pt)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title.Es)
#Html.ValidationMessageFor(model => model.Title.Es)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title.En)
#Html.ValidationMessageFor(model => model.Title.En)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description.Pt)
#Html.ValidationMessageFor(model => model.Description.Pt)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description.Es)
#Html.ValidationMessageFor(model => model.Description.Es)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description.En)
#Html.ValidationMessageFor(model => model.Description.En)
</div>
It works well on Create, adding two new lines to the Translations table (one refered by Title_TranslationId and the other Description_TranslationId) with the input contents. But when Updating, I get the mentioned error at line:
db.Entry(page).State = EntityState.Modified;
The PagesController is as it was created, so no extra attaches are being made. If I delete one of the Translations inputs at the Edit view, it doesn't throw the error, but doesn't update the translations anyway.
The PagesController code:
private AdminEntities db = new AdminEntities();
public ViewResult Index()
{
return View(db.Pages.ToList());
}
public ViewResult Details(int id)
{
Page page = db.Pages.Find(id);
return View(page);
}
public ActionResult Create()
{
ViewBag.ParentPageId = new SelectList(db.Pages, "ParentPageId", "Description");
return View();
}
[HttpPost]
public ActionResult Create(Page page)
{
if (ModelState.IsValid)
{
db.Pages.Add(page);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ParentPageId = new SelectList(db.Pages, "ParentPageId", "Description", page.ParentPageId);
return View(page);
}
public ActionResult Edit(int id)
{
Page page = db.Pages.Find(id);
ViewBag.ParentPageId = new SelectList(db.Pages, "ParentPageId", "Description", page.ParentPageId);
return View(page);
}
[HttpPost]
public ActionResult Edit(Page page)
{
if (ModelState.IsValid)
{
db.Entry(page).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ParentPageId = new SelectList(db.Pages, "ParentPageId", "Description", page.ParentPageId);
return View(page);
}
}
Note that ViewBag.ParentPageId feeds a Pages list that i didn't include in the view code, because it works if I remove the Translation stuff.
Any ideas?
EDIT: Maybe it is too complicated for EF to handle? Maybe I should change my aproach.
SOLVED
I was making two beginners' mistakes.
Mistake #1: Forgot to add the Translation properties Id in the view, so they can be bind in the POST method:
#Html.HiddenFor(model => model.Title.TranslationId)
#Html.HiddenFor(model => model.Description.TranslationId)
Mistake #2: Translation properties, as they are instances of the Translation class, must be set as modified before updating:
db.Entry(page.Title).State = EntityState.Modified;
db.Entry(page.Description).State = EntityState.Modified;
db.Entry(page).State = EntityState.Modified;
db.SaveChanges();
It works now. I lost a few hours but at least I think I've learned some stuff :)
Try using
db.Pages.Attach(page);
db.ObjectStateManager.ChangeObjectState(page, EntityState.Modified);
instead of db.Entry(page).State = EntityState.Modified;
it will attach the entity as modified
i'm using this because I have already created a new instance, and populated the properties I need to update.
var key=this.CreateEntityKey("Pages",page);
ObjectStateEntry ose;
if(this.ObjectStateManager.TryGetObjectStateEntry(key, out ose)){
var entity=(Page)ose.Entity;
Pages.Detach(entity);
}
this.Pages.Attach(page);