MVC: Edit and create in same [HTTPOST] action method - asp.net-mvc

I am using MVC3-Viewmodel model first on my project.
When a user enters a value in my DDL and TextArea and then click on my form button it will basicly execute a ajax url.post to my POST action, right now my Post Action method creates and saves it. But what I want is some type of check, example:
step 1: If SelectQuestion has any answer
step 2: If answer exist do an update
step 3: if answer do not exist create a new and save it.
This is how my controller looks like now:
[HttpPost]
public JsonResult AnswerForm(int id, SelectedQuestionViewModel model)
{
bool result = false;
var goalCardQuestionAnswer = new GoalCardQuestionAnswer(); // Creates an instance of the entity that I want to fill with data
SelectedQuestion SelectedQ = answerNKIRepository.GetSelectedQuestionByID(model.QuestionID); // Retrieve SelectedQuestion from my repository with my QuestionID.
goalCardQuestionAnswer.SelectedQuestion = SelectedQ; // Filling my entity with SelectedQ
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID; // filling my foreign key with the QuestionID
goalCardQuestionAnswer.Comment = model.Comment; // Filling my entity attribute with data
goalCardQuestionAnswer.Grade = model.Grade; // Filling my entity attribute with data
answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer); // adding my object
answerNKIRepository.Save(); // saving
result = true;
return Json(result);
}
Comment and Grade are nullable aswell.
The entitys are associated like
[Question](1)------(*)[SelectedQuestion](1)-----(0..1)[GoalCardQuestionAnswer]
Any kind of help is appreciated.
Thanks in advance!

I achieved my question and the answer is following:
[HttpPost]
public JsonResult AnswerForm(int id, SelectedQuestionViewModel model)
{
SelectedQuestion SelectedQ = answerNKIRepository.GetSelectedQuestionByID(model.QuestionID);
if (SelectedQ.GoalCardQuestionAnswer == null)
{
var goalCardQuestionAnswer = new GoalCardQuestionAnswer();
goalCardQuestionAnswer.SelectedQuestion = SelectedQ;
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID;
goalCardQuestionAnswer.Comment = model.Comment;
goalCardQuestionAnswer.Grade = model.Grade;
this.answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer);
this.answerNKIRepository.Save();
const bool Result = true;
return this.Json(Result);
}
else
{
if (SelectedQ.GoalCardQuestionAnswer != null)
{
SelectedQ.GoalCardQuestionAnswer.Comment = model.Comment;
}
if (SelectedQ.GoalCardQuestionAnswer != null)
{
SelectedQ.GoalCardQuestionAnswer.Grade = model.Grade;
}
const bool Result = false;
return this.Json(Result);
}
}

Related

Update operation not working in MVC5 with EF

Please Help
This is how i'm updating....
[HttpPost, ActionName("Edit")]
public ActionResult Edit(Students model)
{
if (ModelState.IsValid)
{
using (DbAccess db = new DbAccess())
{
var ID=db.students.Find(model.id);
db.Entry(ID).State = EntityState.Modified;
//ID.name = model.name;
//ID.address = model.address;
//ID.age = model.age;
//ID.email = model.email;
//ID.isActive = model.isActive;
db.SaveChanges();
}
}
return View(model);
}
In the above code when i update separately than it works well but when i use db.Entry(...)...... than it doesn't work for me and it also not shows any error
You can try...
[HttpPost, ActionName("Edit")]
public ActionResult Edit(Students model)
{
if (ModelState.IsValid)
{
using (DbAccess db = new DbAccess())
{
var ID = db.students.Find(model.id);
//db.Entry(ID).State = EntityState.Modified;
ID.name = model.name;
ID.address = model.address;
ID.age = model.age;
ID.email = model.email;
ID.isActive = model.isActive;
db.SaveChanges();
}
}
return View(model);
}
When you are doing var ID=db.students.Find(model.id);
You don't need to use
db.Entry(ID).State = EntityState.Modified;
Because when you do Find you have real object of your dbContext, when you make changes on it(except on it primary key of course), your SaveChanges will detect it and update it accordingly.
You need to set State only when you are doing something like this
Students students = new Students(); //a new object which is not created using `Find`
students.name = "Heartlion";
students.address = "address text";
db.Entry(students).State = EntityState.Modified;
db.SaveChanges();
just replace this:
db.Entry(ID).State = EntityState.Modified;
//ID.name = model.name;
//ID.address = model.address;
//ID.age = model.age;
//ID.email = model.email;
//ID.isActive = model.isActive;
with:
UpdateModel(ID);
and you'll be fine;
In order for your method to work, you need to do like this:
db.Students .Attach(ID);
var entry = db.Entry(ID);
entry.State = EntityState.Modified;
entry.Property(x => x.name).IsModified = true;
entry.Property(x => x.address).IsModified = true;
....
db.SaveChanges();
Basically you need to attach the updated model then to tell to the context, which props are modified.
I have in my repository, another method which I use in some situations, when the update is not done in the controller, the method is:
public void Update(TEntity newEntity)
{
var oldEntity = Context.Set<TEntity>().Find(newEntity.Id);
Context.Entry(oldEntity).CurrentValues.SetValues(newEntity);
}
In this method, the newEntity is comming from the view, and the oldEntity is from the context.
UpdateModel can be used only in controllers, when I call the update outside of the controller, I use the above method. I prefer this methods because I do not need to explicitly tell which props are been modified. When you have a form with 10 props, and user is updating only 3 or 4, you need a lot of logic to determine which were modified.
PS - my entities are all implementing IEntityId, which is an interface with common properties, like Id, UpdateDate, InsertDate ..., this is why I can call newEntity.Id

Best Way to Update only modified fields with Entity Framework

Currently I am doing like this:
For Example:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
Just wondering is there any elegant way to do this with less effort and optimal performance?
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
In this example, you have a single entity called Person:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
First, create a view model:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
Anyway, the result when SaveChanges is called is the following SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = #0
WHERE ([Id] = #1)
',N'#0 datetime2(7),#1 int',#0='2015-01-01 00:00:00',#1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
I hope this helps you.
I swear by EntityFramework.Extended. Nuget Link
It lets you write:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
Please try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
I have provided my code sample below; Great if it can help someone else
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
I still didn't find a nice solution for my problem, so I created a work around. When loading the Entity, I directly make a copy of it and name it entityInit. When saving the Entity, I compare the both to see, what really was changed. All the unchanged Properties, I set to unchanged and fill them with the Database-Values. This was necessary for my Entities without Tracking:
// load entity without tracking
var entityWithoutTracking = Context.Person.AsNoTracking().FirstOrDefault(x => x.ID == _entity.ID);
var entityInit = CopyEntity(entityWithoutTracking);
// do business logic and change entity
entityWithoutTracking.surname = newValue;
// for saving, find entity in context
var entity = Context.Person.FirstOrDefault(x => x.ID == _entity.ID);
var entry = Context.Entry(entity);
entry.CurrentValues.SetValues(entityWithoutTracking);
entry.State = EntityState.Modified;
// get List of all changed properties (in my case these are all existing properties, including those which shouldn't have changed)
var changedPropertiesList = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
foreach (var checkProperty in changedPropertiesList)
{
try
{
var p1 = entityWithoutTracking.GetType().GetProperty(checkProperty).GetValue(entityWithoutTracking);
var p2 = entityInit.GetType().GetProperty(checkProperty).GetValue(entityInit);
if ((p1 == null && p2 == null) || p1.Equals(p2))
{
entry.Property(checkProperty).CurrentValue = entry.Property(checkProperty).OriginalValue; // restore DB-Value
entry.Property(checkProperty).IsModified = false; // throws Exception for Primary Keys
}
} catch(Exception) { }
}
Context.SaveChanges(); // only surname will be updated
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
In EF you can do like this
var result = db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
if(result != null){
result.Name = newName;
result.DOB = newDOB;
db.Person.Update(result);
}
Or you can use
using (var db= new MyDbContext())
{
var result= db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
result.Name= newName;
result.DOB = newDOB;
db.Update(result);
db.SaveChanges();
}
For more detail please EntityFramework Core - Update Only One Field
No Worry guys
Just write raw sql query
db.Database.ExecuteSqlCommand("Update Person set Name='"+_entity.Name+"' where Id = " + _entity.ID + "");

Keeping a page's state

I have already looked at these links for references.
Link 1: ASP.Net MVC and state - how to keep state between requests
Link 2: ASP.NET MVC: Keeping last page state
I have a few pages that a user will be filling out. We will call these pages Page 1. If they get to a field that they need to select from, drop down, but need to create a new item to be included in the drop down, because it will be used again later, they go to a new page, Page 2, to create the item. After create they create the item they are returned to Page 1 to finishing filling out the form. The problem is that the Page 1 is now erased because is a new page load. I would like for this to persist for when they come back so they don't have to refill out fields.
The route I am currently Link2 using a cookie. I don't know how to set the cookie's info before it gets to the next page, or how to pass it to that page before since it is going to a GET method and not a POST.
GET method for Page 1:
public ActionResult Create()
{
var courseTitles = (from title in db.CourseTitles
join type in db.CourseTypes on title.Type equals type.CourseTypeID
select new
{
CourseTitleID = title.CourseTitleID,
Title = title.Title + " - " + type.Type
});
Course course = new Course();
if (Request.Cookies["CourseInfo"] != null) //If it's not null, set the model.
{
HttpCookie cookie = Request.Cookies["CourseInfo"];
course.ClassNumber = Convert.ToInt32(cookie.Values["ClassNumber"]);
course.CourseStartDate = Convert.ToDateTime(cookie.Values["StartDate"]);
course.CourseEndDate = Convert.ToDateTime(cookie.Values["EndDate"]);
ViewBag.CourseList = new SelectList(courseTitles, "CourseTitleID", "Title", cookie.Values["CourseTitle"]);
return View(course);
}
ViewBag.CourseList = new SelectList(courseTitles, "CourseTitleID", "Title");
return View();
}
GET and POST method for Page 2:
public ActionResult NewCourseTitle()
{
ViewBag.Type = new SelectList(db.CourseTypes, "CourseTypeID", "Type");
return View();
}
//
//Post:
[HttpPost]
public ActionResult NewCourseTitle(CourseTitle courseTitle)
{
if (ModelState.IsValid)
{
db.CourseTitles.AddObject(courseTitle);
db.SaveChanges();
return RedirectToAction("Create", "Course");
}
return View();
}
Let me know if you need more code.
You can use TempData to store objects between requests:
public ActionResult Create()
{
var courseTitles = (from title in db.CourseTitles
join type in db.CourseTypes on title.Type equals type.CourseTypeID
select new
{
CourseTitleID = title.CourseTitleID,
Title = title.Title + " - " + type.Type
});
Course course = new Course();
if (TempData["CourseInfo"] != null) //If it's not null, set the model.
{
course = TempData["CourseInfo"] as Course;
ViewBag.CourseList = new SelectList(courseTitles, "CourseTitleID", "Title", course.Title);
return View(course);
}
ViewBag.CourseList = new SelectList(courseTitles, "CourseTitleID", "Title");
return View();
}
In order to store the Course simply use TempData["CourseInfo"] = course
TempData exposes couple of options that define for how long its content is going to be persisted. You can read about it here
You could use some JavaScript to modify the GET request to NewCourseTitle so that it will contain the course data that the user entered.
With jQuery it could look roughly like this:
$(function () {
var newCourseTitleLink = $('#new-course-title-link');
newCourseTitleLink.on("click", function ()
{
document.location.href = newCourseTitleLink.attr('href') + '?' + $('#course-data-form').serialize();
});
});
Then you can create a cookie in your action method NewCourseTitle:
public ActionResult NewCourseTitle(int classNumber, ... /*other form values*/)
{
var cookie = new HttpCookie("CourseInfo");
cookie.Values.Add("ClassNumber", classNumber.ToString());
...
Response.SetCookie(cookie);
ViewBag.Type = new SelectList(db.CourseTypes, "CourseTypeID", "Type");
return View();
}

MVC Entity Framework - Inserting Data - A dependent property in a ReferentialConstraint is mapped to a store-generated column

I need help with trying to insert a record using MVC and Entity Framework. I have a dynamically created form which can contain many questions. When Editing, I want to delete the existing answers (which it does successfully) and insert new answers.
I am getting the following error:
Cannot insert explicit value for identity column in table 'tblModeratorReportAnswers' when IDENTITY_INSERT is set to OFF.
If I add the following line in my DbContext class
modelBuilder.Entity<QuestionAnswer>().Property(p => p.AnswerID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); I get this error:
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'AnswerID'.
Here's my code that is doing the update
//
// POST: /Home/Edit/1
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection formCollection, int moderatorReportId)
{
ModeratorReport reportToEdit = repository.GetModeratorReportById(moderatorReportId);
List<QuestionAnswer> originalReportAnswers = repository.GetAllModeratorReportAnswers(moderatorReportId).ToList();
foreach (QuestionAnswer answer in originalReportAnswers) {
repository.DeleteAnswer(answer);
}
repository.Save();
int sectionID;
int questionID;
foreach (string key in formCollection.AllKeys)
{
var value = formCollection[key.ToString()];
Match m = Regex.Match(key, "section(\\d+)_question(\\d+)");
if (m.Success) {
QuestionAnswer newAnswer = new QuestionAnswer();
sectionID = Convert.ToInt16(m.Groups[1].Value.ToString());
questionID = Convert.ToInt16(m.Groups[2].Value.ToString());
newAnswer.ModeratorReportID = moderatorReportId;
newAnswer.QuestionID = questionID;
newAnswer.Answer = value;
repository.AddAnswer(newAnswer);
}
}
repository.Save();
reportToEdit.Status = "SUBJECTOFFICER SAVED";
AuditItem auditItem = new AuditItem();
auditItem.ModeratorReportID = moderatorReportId;
auditItem.Status = "SUBJECTOFFICER SAVED";
auditItem.AuditDate = DateTime.Now;
auditItem.Description = "The Moderator report ID: " + moderatorReportId + " was saved.";
auditItem.UserID = User.Identity.Name;
db.Audit.Add(auditItem);
repository.Save();
return RedirectToAction("Details", new { id = moderatorReportId });
}
...and in my repository
//
// Persistance
public void Save()
{
db.SaveChanges();
}
public void AddAnswer(QuestionAnswer answer)
{
db.Answers.Add(answer);
Save();
}
public void DeleteAnswer(QuestionAnswer answer)
{
db.Answers.Attach(answer);
db.Answers.Remove(answer);
}
I have also checked all my Primary Keys, Foreign Keys and they are all ok. The Primary Keys are all set to 'Is Identity'.
I've been trying to sort this problem out all day. I have no idea what to do to resolve it. If anyone can give my any advice, it'd be much appreciated.
Maybe it's my inexperience with ASP.NET MVC and Entity Framework, but I have now resolved this issue by changing the logic of that I update the report.
Instead of deleting the answers and reinserting them. I now retrieve the answers and change Answer property to be the new answer. Then just use db.SaveChanges().
//
// POST: /Home/Edit/1
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection formCollection, int moderatorReportId)
{
ModeratorReport reportToEdit = repository.GetModeratorReportById(moderatorReportId);
List<QuestionAnswer> originalReportAnswers = repository.GetAllModeratorReportAnswers(moderatorReportId).ToList();
int sectionID;
int questionID;
foreach (string key in formCollection.AllKeys)
{
var value = formCollection[key.ToString()];
Match m = Regex.Match(key, "section(\\d+)_question(\\d+)");
if (m.Success) {
QuestionAnswer newAnswer = new QuestionAnswer();
sectionID = Convert.ToInt16(m.Groups[1].Value.ToString());
questionID = Convert.ToInt16(m.Groups[2].Value.ToString());
foreach(QuestionAnswer answerToEdit in originalReportAnswers) {
if (answerToEdit.QuestionID == questionID)
{
answerToEdit.Answer = value;
}
}
}
}
repository.Save();
reportToEdit.Status = "SAVED";
AuditItem auditItem = new AuditItem();
auditItem.ModeratorReportID = moderatorReportId;
auditItem.Status = "SAVED";
auditItem.AuditDate = DateTime.Now;
auditItem.Description = "The Moderator report ID was saved.";
auditItem.UserID = User.Identity.Name;
db.Audit.Add(auditItem);
repository.Save();
return RedirectToAction("Details", new { id = moderatorReportId });
}
Cannot insert explicit value for identity column in table
'tblModeratorReportAnswers' when IDENTITY_INSERT is set to OFF.
This error says that you are explicitly inserting value into autogenerated column (identity column).
A dependent property in a ReferentialConstraint is mapped to a
store-generated column. Column: 'AnswerID'.
This error says that there is some incorrectly configured relation where autogenerated AnswerID is considered as FK - that is not supported. Identity and Computed properties must not be FKs.

Validate model on initial request

I'm returning a model to my view on the initial load of a page, the model is populated from the DB, I want to validate the model so that when the user receives the page a validation summary show the errors if any.
I have tried using TryValidateModel(model) but this does not work, it does not update the ModelState, the reasion I assume is that it will only validate against what is populated from the ModelBinder
Is there anyway around this? I just want to validate the model first without the user having to post it back...
[Authorize, HttpGet, ActionName("StepOne")]
public ActionResult StepOneGET(StepOneModel model)
{
var individual = _onsideService.Get(User.Identity.Name);
model.PersonalInformation = new PersonalInformationModel
{
FirstName = individual.FirstName,
LastName = individual.LastName,
DoB = individual.DateOfBirth.ToString("dd/MM/yyyy"),
Email = individual.DefaultEmail.EmailAddress,
Phone = individual.DefaultPhone.Number,
AddressLine1 = location.Address1,
AddressLine2 = location.Address2,
City = location.City,
PostCode = location.PostalCode,
Country = location.Country
};
// NOTE: Does not update ModelState
TryValidateModel(model);
// Need to return potential errors to user on page load
return View(model);
}
Here is a snippet provided in another question here at SO. I don't take any credit for it, but it should do exactly what you want.
public static IList<KeyValuePair<string, string>> GetErrors(object obj)
{
// get the name of the buddy class for obj
MetadataTypeAttribute metadataAttrib = obj.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault() as MetadataTypeAttribute;
// if metadataAttrib is null, then obj doesn't have a buddy class, and in such a case, we'll work with the model class
Type buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : obj.GetType();
var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast<PropertyDescriptor>();
var modelClassProperties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();
var errors = from buddyProp in buddyClassProperties
join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name // as this is an inner join, it will return only the properties that are in both the buddy and model classes
from attribute in buddyProp.Attributes.OfType<ValidationAttribute>() // get only the attributes of type ValidationAttribute
where !attribute.IsValid(modelProp.GetValue(obj))
select new KeyValuePair<string, string>(buddyProp.Name, attribute.FormatErrorMessage(string.Empty));
return errors.ToList();
}

Resources