I am trying to use TryUpdateModel to restrict the properties updated by the edit method.
I want users to see all the fields in the Edit form but don't want the Title property of the Album entity to be updated. So I'm doing this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit()
{
var album = new Album();
TryUpdateModel(album, new[] { "Price","Date","Genre"});
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}.....
When executing this code, I am having an error on db.SaveChages(), which says:
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. See
http://go.microsoft.com/fwlink/?LinkId=472540 for information on
understanding and handling optimistic concurrency exceptions.
What am I doing wrong here?
Basically, it's saying that 0 rows were modified by your update. So, your primary key (whatever makes a row unique) points to an ID that does not exist in the database. You need to figure out why this is happening and instead use a key that exists in the database.
Related
I am new to asp.net mvc and recently I came across a method called AsNoTracking(). I did some research and found out that AsNoTracking() helps to speed up performance. It should be used with queries in which we do not want to save the data back to the database. So I know that AsNoTracking() helps to boost performance if you are returning hundreds to thousands of records. However, What I was wondering is that What if you are only returning one record?. For example the record of a specific employee. Is there a need to use AsNoTracking()?
Its will be useful to know that where AsNoTracking() could be needful.
In certain scenario where you post an Edit call and before actually updating the model to database you are trying to check if particular record already exists or not and then you want to place Update call it will throw exception. Below is the demonstration of such scenario.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //THIS IS WHERE THE ERROR WILL BE THROWN AS RECORD WITH ID IS ALREADY ATTACHED WITH modelFromDb OBJECT.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You will find many questions asking about error like "Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value" which can be caused by above snippet.
AsNoTracking() can prevent such exceptions. Suggested code will be like below.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.AsNoTracking().Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //WORKS PERFECTLY.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can refer one such question asked here ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value
I've been looking around and can't quite find the answer. I'm using a ViewModel in my Edit View so that I can have values for some dropdownlist. Now when I go to update my DB I'm not understanding how I can update my database record. I'm guessing I could create a new entity object, do a Find, and then update each property based on the ViewModel passed in from the Form but that sure seems like a lot of manual work.
Here I'm using the VeiwModel in the Edit View.
#model CPPCustomerCall.ViewModels.CustomerCallVM
Here is my controller's ActionResult. I changed the object type of the ActionResult to take in CustomerCallVM instead of the CustomerCall which was auto-generated. I assume since the Edit View's model is the ViewModel that's the type of object the ActionResult will receive. However, my ViewModel has more properties that aren't needed for the Entity Model to update the record. How do I go about updating my DB record in this ActionResult?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
db.Entry(customerCall).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
First, Bind and view models are mutually exclusive. If you don't want something to be eligible to be modified, then it shouldn't be on your view model, in the first place. Barring that, view models diverge from entities in the sense that they can't be saved directly. As a result, there's always some intervention present on your part to map the posted values back onto the entity, which means you can then selectively not map over certain properties that shouldn't be, regardless of whether they were posted or not. Long and short, get rid of the Bind stuff. It's just something else to maintain and a huge source of potential bugs.
That said, the code you have is workable; you're just missing the crucial part where you map the data from your view model back onto your entity. First, you need to fetch the entity from the database so you have a base to work from:
var customerCall = db.CustomerCalls.Find(id);
if (customerCall == null)
{
return new HttpNotFoundResult();
}
FWIW, your edit route should include the id in the route, according to REST conventions. Following REST isn't strictly required, but it's certainly recommended. While a web application adhering to REST doesn't mean it's a good application, not adhering to rest is generally a sure sign of a badly designed and coded application.
Then, you map over your properties. You can either do this manually:
customerCall.CustomerName = model.CustomerName;
// etc.
Or you can use a library like AutoMapper.
mapper.Map(model, customerCall);
AutoMapper requires a bit of initial setup to make this magic work, of course, so review the docs, if you're going that route. Manual mapping is easier, but far more tedious and repetitive.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
// Find The record you need
var dbObj = CustomerCalls.FirstOrDefault(x=> x.id = customerCall.id);
//Check if null
if(dbObj == null) dbObj = new CustomerCall();
/// Map your properties
// Add object to the stack
if(dbObj.id == 0){
CustomerCalls.Add(dbObj);
}else{
CustomerCalls.Update(dbObj);
}
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
First some code of what I'm trying to do:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit(Person person, HttpPostedFileBase filetoupload)
{
if (ModelState.IsValid)
{
//db is the context and person is the object to be changed
person.PeopleManagerApproved = false;
Person x = db.Persons.Find(person.ID);
//connection with the database to persist the changes.
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("../Person/Details/" + person.ID);
}
return View(person);
}
when I run this code though, the database says that it can't save because there's another entity with the same primary key. This is logical as I'm selecting it from the database as it's being altered. Is there a way however, to grab that object so I can check the previous value of a field declared within the object so I know which values have changed after editting the 'person' object.
the error message is:
Attaching an entity of type 'Smoelenboek.Models.Person' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
List checkiffalseapproved = db.Persons.AsNoTracking().Where(y => y.ID == person.ID).AsQueryable().ToList(); this way it's detached from the original database and i can still obtain the information without bothering the EF
this code fixed it
I'm debugging this method for two cases: one where there is a parent, the other where there is no parent.
If there is no parent, the new Person has an id of 0 but never actually gets saved to the db.
If there is a parent, the new Person has an id of 0 in this method, but a new record is inserted into the db with the correct value (one more than the highest in the table).
What is going on here? I know I'm doing something wrong, I'm just not sure what.
I'm using EF Codefirst.
The code for the controller method:
[HttpPost]
public ActionResult Create(CreatePersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var parent = _db.Persons.FirstOrDefault(s => s.PersonId == viewModel.ParentId);
var person = new Person() { Name = viewModel.Name };
// if it has a parent, build new relationship
if (parent != null)
{
person.Parent = parent;
parent.Children.Add(person);
};
_db.Save();
return RedirectToAction("detail", "person", new { personId = person.PersonId });
}
return View(viewModel);
}
If there is no parent, the new Person has an id of 0 but never actually gets saved to the db.
That's because you never tell EF that it should persist the entity. You only create a new Person() and that's it.
You should do:
dbContext.AddToPersons(person);
before calling dbContext.SaveChanges().
In the case when there is a parent, person is saved because of its relationship with parent.
Update
Just occurred to me: If you're doing code first you might not have the AddToPersons(...) method available on the data context. If this is so, you can use dbContext.Persons.AddObject(person) instead.
The fact with you are referring to is auto increment ID for you object. It is controlled by your ORM. You may want to check this question
You may want to check this link from msdn
Remarks Refresh has the dual purpose of allowing an object to be
refreshed with data from the data source and being the mechanism by
which conflicts can be resolved. For more information, see Saving
Changes and Managing Concurrency (Entity Framework). The order in
which objects are refreshed is nondeterministic. After the Refresh
method is called, the object’s original values will always be updated
with the data source value, but the current values might or might not
be updated with the data source value. This depends on the
RefreshMode. The StoreWins mode means that the object should be
updated to match the data source values. The ClientWins value means
that only the changes in the object context will be persisted, even if
there have been other changes in the data source. To ensure that an
object has been updated by data source-side logic, you can call the
Refresh method with the StoreWins value after you call the SaveChanges
method.
I have a small application where I am creating a customer
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateCustomer(GWCustomer customer)
{
if (string.IsNullOrEmpty(customer.CustomerName))
{
ModelState.AddModelError("CustomerName", "The name cannot be empty");
}
//...
if (ModelState.IsValid)
{
//insert in db
}
}
My problem is that the GWCustomer object has an Id, which is primary key and cannot be null. This makes the validation framework flag it as an error. But it's not an error, I haven't created the customer yet, and for now is should be null until it gets saved. How do I bypass this? Or fix it?
I never get to insert it in the DB because the ModelState is never valid.
Edit I am using Linq to SQL, and a repository pattern.
This will exclude value from binding, but not validation:
public ActionResult CreateCustomer([Bind(Exclude = "Id")]GWCustomer customer)
Even when validation occurs, you can still correct ModelState by calling:
ModelState.Remove("Id");
It will remove entries related to Id and change ModelState.Valid property to true if only Id was causing errors.
Using data layer objects in view layer is not recommended. You should definitely think about creating dedicated view model, without Id field.
Maybe you have this line in your view:
#Html.HiddenFor(model => model.Id)
Delete it and the view won't send that parameter with the model.
This is why I always say that the ViewModel objects (input and output) should be separated from the Domain Objects.
The input model should be validated in the way you are above; the domain object state should be validated before it gets written to the database (and exceptions thrown if it is somehow invalid).