I created a view to display data from the database
public ActionResult UpdateDetail()
{
using (UsersDatabaseEntities ude = new UsersDatabaseEntities())
{
ude.Configuration.ValidateOnSaveEnabled = false;
return View(ude.Users.Where(a => a.Email == User.Identity.Name).FirstOrDefault());
}
}
Then, I tried to edit and save to database
[HttpPost]
public ActionResult UpdateDetail([Bind(Exclude = "IsEmailVerified,ActivationCode")] User user)
{
if (ModelState.IsValid)
{
using (UsersDatabaseEntities ude = new UsersDatabaseEntities())
{
ude.Entry(user).State = EntityState.Modified;
ude.SaveChanges();
}
}
return View(user);
}
The problem is, it seems to not be saved to the database. i tried to call UpdateDetail again, and it shows the data do not saved.
I do not find syntax error.
does the data truly saved?
Add the debugger inside your UpdateDetail action and also write the if(ModelState.IsValid){ //..logic goes there } else{ return View();} check the modelstate don't have any error then try to update User and seem like it's happen when we have some non-nullable field and passing to the null values into them.
Related
I'm teaching myself C# and MVC but have a background in SQL. When updating an existing master-detail set of records in a single action (let's say for instance a customer order and order details), updating the master record is no problem. Regarding the detail records, I'm seeing examples that simply delete all existing details and then add them back in rather than add, delete or update only what's changed. That seems easy and effective but involves unnecessary changes to database records and might be an issue in complex relationships.
I've tried writing code that checks the existing values against posted values to determine the right EntityState (Added, Deleted, Modified, Unchanged) for each detail. Accomplishing this using LINQ Except and Intersect works but seems to cause an unexpected performance hit.
(Instead, I could load the original values in an "oldValue" hidden field in the original GET request to compare to the POST values except that would be unreliable in a multi-user environment and seems like a bad idea.)
I'll be happy to provide code examples, but my question is more about best practices. Is there a preferred method for updating existing master-detail sets of records?
EDIT: I've added the code below in response to questions. In this example, our application allows additional attributes to be attached to a product, kept in a separate table ProductAttributes. The view allows the user to edit both the product and the attributes on the same webpage and save at the same time. The code works fine but seems slow and lags at SaveChanges.
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
// Establish entity states for product attributes.
List<ProductAttribute> existingAttributes = new List<ProductAttribute>();
existingAttributes = db.ProductAttributes.AsNoTracking()
.Where(x => x.Sku == product.Sku).ToList();
// Review each attribute that DID NOT previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Except(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value didn't exist and still doesn't.
db.Entry(pa).State = EntityState.Unchanged;
}
else
{
// New value exists that didn't before.
db.Entry(pa).State = EntityState.Added;
}
}
// Review each attribute that DID previously exist.
foreach (ProductAttribute pa in product.ProductAttributes
.Intersect(existingAttributes, new ProductAttributeComparer()))
{
if (pa.Value is null)
{
// Value existed and has been cleared.
db.Entry(pa).State = EntityState.Deleted;
}
else
{
if (pa.Value != existingAttributes
.FirstOrDefault(x => x.Attribute == pa.Attribute).Value)
{
// Value has been altered.
db.Entry(pa).State = EntityState.Modified;
}
else
{
// Value has not been altered.
db.Entry(pa).State = EntityState.Unchanged;
}
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = product.ProductId });
}
return View(product);
}
internal class ProductAttributeComparer : IEqualityComparer<ProductAttribute>
{
public bool Equals(ProductAttribute x, ProductAttribute y)
{
if (string.Equals(x.Attribute, y.Attribute,
StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
public int GetHashCode(ProductAttribute obj)
{
return obj.Attribute.GetHashCode();
}
}
}
In the Create action of the controller, based on user input, we plan to populate the model object with some data, to minimize data entry:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Item item, string str)
{
// if only str is provided
if (string.IsNullOrEmpty(item.KeyInfo) && !string.IsNullOrEmpty(str))
{
Helpers.FillItemModel(item, str); //fill data
}
else if (ModelState.IsValid)
{
_context.Add(item);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Create));
}
return View(item);
}
However, although we can confirm the item object has been populated with data to several fields, by setting break point at the last line: return View(item), when the browser gets the response, all fields are empty.
But if we comment out the entire code segment, only leave the return statement and do a post with some data that was manually entered, the browser will receive correct data in all fields.
Thank you for your time.
To update ModelState value you have to reset the ModelState first as follows:
// if only str is provided
if (string.IsNullOrEmpty(item.KeyInfo) && !string.IsNullOrEmpty(str))
{
ModelState.Clear();
Helpers.FillItemModel(item, str); //fill data
}
ModelState.Clear() will reset the whole model. If you don't want that and just want to update few fields value keeping other field value intact then use ModelState["fieldName"].Value = "newValue in your helper class.
I’m confused on how to work with updating two entities while using a ViewModel. I’ve done some searching but I’m not coming up with anything that seems to help me understand this.
I have two entities Person and Address. I use a ViewModel to combine the two together in order to display them in my Edit.cshtml. At the top of my View I declare the ViewModel to be used.
Now, I get to the actual Edit portion. I’m assuming I would have to make an update to each entity and then create a new ViewModel to return to the View since the Veiw is expecting a ViewModel?
I’m also curious if there is a way to let the View know that the update was successful so I can display a message at the top of the View or if it would be better to just redirect to the Index View.
Is this a correct way of coding this or is there a more streamlined way to accomplish the same thing?
Top of my .cshtml page
#model Project.Models.MemberViewModel
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyAccount(MemberViewModel model)
{
if (model.PersonId == 0)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
MemberViewModel updatedVM = new MemberViewModel();
using (var db = new DataContext())
{
Person currentPerson = db.Person.Find(model.PersonId);
if (currentPerson == null)
{
return HttpNotFound();
}
db.Entry(currentPerson).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
Address currentAddress = db.Address.Find(model.PersonId);
if (currentAddress == null)
{
return HttpNotFound();
}
db.Entry(currentAddress).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
////update and return a ViewModel
//updatedVM.FirstName = currentPerson.FirstName;
//updatedVM.LastName = currentPerson.LastName;
//updatedVM.Address = currentAddress.Address1;
//updatedVM.City = currentAddress.City;
//updatedVM.State = currentAddress.State;
//updatedVM.Zip = currentAddress.Zip;
}
return View(updatedVM);
}
In my opinion, the best practice would be to redirect to Index.cshtml after successfully saving the data (I think this is the whole idea of keeping MVC clean):
return RedirectToAction("Index");
I would only show an error message if there is trouble with the data.This can be done similar to this:
You can add these to the ViewModel:
bool hasErrors;
string errorMessage;
and in the view you would implement at the top of the page:
#if(Model.hasErrors)
{
<div>Model.errorMessage</div>
}
The idea is to validate the data inside the ViewModel constructor, or inside the controller, and if there is something wrong just set hasErrors = true and a custom errorMessage and show it in the view.cshtml.
Hope it helps.
I have a problem transfering data from one view to another via the controler actions.
I the first view is a grid.mvc-Grid displayed. By select on row of the grid I get the ID for that object.
by transfering this to an action in the controler I try to filter the data. That works fine.
Here is the filter:
[HttpGet]
public ActionResult PersonenById(int id)
{
var personen = new ObservableCollection<Person>();
//Getting the data here :-)
foreach (DataRow r in access.Rows)
{
Person p = new Person();
//do some stuff
personen.Add(p);
}
//return PartialView("Personen", personen); //does not work
TempData["personen"] = personen;
return RedirectToAction("Personen"); // redirect to another view
}
In method II the view is filled:
public ActionResult Personen()
{
var persons = new ObservableCollection<Person>();
if (TempData["Persons"] == null)
{
}
return View(persons); //Works perfect
}
else
{
persons = (ObservableCollection<Person>) TempData["Persons"];
return View(persons);//does not redirect to that View
}
}
(Sorry for the strange formating. :-))
Is there any different way to send data from a view to another?
I tried:
return partial;
return View("Persons",persons);
and a lot other stuff.
You can redirect in a .cshtml view.
Eg:
Context.Response.StatusCode = 403;
Context.Response.Redirect(
$"{Context.Request.PathBase}/Error/403", false);
Should work like this:
return RedirectToAction("Personen", model);
Also, the "Personen" action should have the model as an argument, like this:
public ActionResult Personen(Person model) ...
LE: I have also noticed you have tried to send the data through the TempData object. Make sure the indexed object's name is the same (e.g. TempData["person"] everywhere)
Hope it answers your question.
Currently I am attempting to add a new row to a database table through AJAX which is working fine. But then I try to update a different table and I get an error. Here is my code and the error I am encountering.
Error
The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
Line 41: _db.ChampionCounters.Attach(champion);
Code
[HttpPost]
public ActionResult VoteYes(int id)
{
string results;
if (Request.IsAuthenticated)
{
var checkFirst =
from c in _db.UserCounterLinks
where c.counterId == id && c.userName == User.Identity.Name
select c;
if (checkFirst.Any())
{
results = "You have already voted on this counter.";
return Json(results);
}
var userVoteLink = new UserCounterLink { counterId = id, userName = User.Identity.Name, userAgree = true };
_db.UserCounterLinks.AddObject(userVoteLink);
var champion = _db.ChampionCounters.SingleOrDefault(c => c.id == id);
if (champion != null)
{
champion.positiveVotes++;
_db.ChampionCounters.Attach(champion);
}
_db.SaveChanges();
results = "Voted";
} else
{
results = "You must be logged in to vote.";
}
return Json(results);
}
Summary
The code above is from the controller that handles the Ajax post. Like I said the userVoteLink table creates a record just fine. But when I try to update the other table ChampionCounters the error is thrown.
Thanks in advance!
You don't need to attach the instance because the context is already tracking that instance. Just remove the _db.ChampionCounters.Attach(champion); line.