InvalidOperationException when using updatemodel with EF4.3.1 - asp.net-mvc

When I update my model I get an error on a child relation which I also try to update.
My model, say Order has a releationship with OrderItem. In my view I have the details of the order together with an editortemplate for the orderitems. When I update the data the link to Order is null but the orderid is filled, so it should be able to link it, TryUpdateModel returns true, the save however fails with:
InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.]
My update method:
public ActionResult ChangeOrder(Order model)
{
var order = this.orderRepository.GetOrder(model.OrderId);
if (ModelState.IsValid)
{
var success = this.TryUpdateModel(order);
}
this.orderRepository.Save();
return this.View(order);
}
I tried all solutions I saw on SO and other sources, none succeeded.
I use .Net MVC 3, EF 4.3.1 together with DBContext.

There are a number of code smells here, which I'll try to be elegant with when correcting :)
I can only assume that "Order" is your EF entity? If so, I would highly recommend keeping it separate from the view by creating a view model for your form and copying the data in to it. Your view model should really only contain properties that your form will be using or manipulating.
I also presume orderRepository.GetOrder() is a data layer call that retrieves an order from a data store?
You are also declaring potentially unused variables. "var order =" will be loaded even if your model is invalid, and "var success =" is never used.
TryUpdateModel and UpdateModel aren't very robust for real-world programming. I'm not entirely convinced they should be there at all, if I'm honest. I generally use a more abstracted approach, such as the service / factory pattern. It's more work, but gives you a lot more control.
In your case, I would recommend the following pattern. There's minimal abstraction, but it still gives you more control than using TryUpdateModel / UpdateModel:
public ActionResult ChangeOrder(OrderViewModel model) {
if(ModelState.IsValid) {
// Retrieve original order
var order = orderRepository.GetOrder(model.OrderId);
// Update primitive properties
order.Property1 = model.Property1;
order.Property2 = model.Property2;
order.Property3 = model.Property3;
order.Property4 = model.Property4;
// Update collections manually
order.Collection1 = model.Collection1.Select(x => new Collection1Item {
Prop1 = x.Prop1,
Prop2 = x.Prop2
});
try {
// Save to repository
orderRepository.SaveOrder(order);
} catch (Exception ex) {
ModelState.AddModelError("", ex.Message);
return View(model);
}
return RedirectToAction("SuccessAction");
}
return View(model);
}
Not ideal, but it should serve you a bit better...
I refer you to this post, which is similar.

I assume that the user can perform the following actions in your view:
Modify order (header) data
Delete an existing order item
Modify order item data
Add a new order item
To do a correct update of the changed object graph (order + list of order items) you need to deal with all four cases. TryUpdateModel won't be able to perform a correct update of the object graph in the database.
I write the following code directly using a context. You can abstract the use of the context away into your repository. Make sure that you use the same context instance in every repository that is involved in the following code.
public ActionResult ChangeOrder(Order model)
{
if (ModelState.IsValid)
{
// load the order from DB INCLUDING the current order items in the DB
var orderInDB = context.Orders.Include(o => o.OrderItems)
.Single(o => o.OrderId == model.OrderId);
// (1) Update modified order header properties
context.Entry(orderInDB).CurrentValues.SetValues(model);
// (2) Delete the order items from the DB
// that have been removed in the view
foreach (var item in orderInDB.OrderItems.ToList())
{
if (!model.OrderItems.Any(oi => oi.OrderItemId == item.OrderItemId))
context.OrderItems.Remove(item);
// Omitting this call "Remove from context/DB" causes
// the exception you are having
}
foreach (var item in model.OrderItems)
{
var orderItem = orderInDB.OrderItems
.SingleOrDefault(oi => oi.OrderItemId == item.OrderItemId);
if (orderItem != null)
{
// (3) Existing order item: Update modified item properties
context.Entry(orderItem).CurrentValues.SetValues(item);
}
else
{
// (4) New order item: Add it
orderInDB.OrderItems.Add(item);
}
}
context.SaveChanges();
return RedirectToAction("Index"); // or some other view
}
return View(model);
}

Related

The relationship could not be changed because one or more of the foreign-key properties is non-nullable in MVC 4

I am getting this error after click Save (update) my form:
The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Here is my controller (case "Save" in swich couse problem):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserModel userModel, string act = null, int idx = 0)
{
using (var dbContext = new userDbEntities())
{
if (userModel.User == null)
{
userModel.User = new UsersTable();
}
var newUser = userModel.User.userID == 0;
userModel.CustomTypes = dbContext.CustomTypes.ToList();
switch (act)
{
case "addcustom":
userModel.User.CustomerTables.Add(new CustomerTable
{
CustomType = new CustomType(),
UsersTable = userModel.User
});
break;
case "deletecustom":
userModel.User.CustomerTables.RemoveAt(idx);
break;
case "save":
foreach (var customer in userModel.User.CustomerTables)
{
customer.CustomType = dbContext.CustomTypes.Find(customer.CustomType.Id_NewCustomerType);
}
var dbUser = dbContext.UsersTables.Find(userModel.User.userID);
dbUser.TimeZoneId = userModel.User.TimeZoneId;
foreach (var custom in userModel.User.CustomerTables)
{
if (custom.CustomerID == 0)
dbUser.CustomerTables.Add(custom);
}
foreach (var custom in dbUser.CustomerTables.ToList())
{
var modelCustom =
userModel.User.CustomerTables.FirstOrDefault(o => o.CustomerID == custom.CustomerID);
if (modelCustom != null) //update it
{
custom.CustomType =
dbContext.CustomTypes.Find(modelCustom.CustomType.Id_NewCustomerType);
}
if (userModel.User.CustomerTables.All(o => o.CustomerID != custom.CustomerID))
dbUser.CustomerTables.Remove(custom);
}
dbContext.SaveChanges();
break;
} // end switch statements
return View("Edit", userModel);
}
}
Any idea what is wrong...
try something like following.
foreach (var child in modifiedParent.ChildItems)
{
context.Childs.Attach(child);
context.Entry(child).State = EntityState.Modified;
}
context.SaveChanges();
See the following link.
http://social.msdn.microsoft.com/Forums/en-US/1833117c-7a93-4b69-a133-b7fd764db810/the-operation-failed-the-relationship-could-not-be-changed-because-one-or-more-of-the-foreignkey?forum=adodotnetentityframework
There is an easier way to solve this problem.
Actually this is because you have one to many relationship. When you want to delete 1 Side, you have two scenarios; You can choose cascade delete, or you can choose none.
If you select first option it will delete all the many side entities when you delete the 1 side.
You can set this option in the Entity Diagram. You Only got to select the relationship and set "End1 OnDelete" property to Cascade.
I was experiencing this message, it turned out that I was marking the object as Modified after I had removed it from it's collection instead of deleting it.
For example you don't want to call
context.Entry(child).State = EntityState.Modified;
if in fact you are trying to delete the child.
instead, as well as removing the child from the collection try something like context.DeleteObject(child)
I haven't got the exact code because my experience was with Dev Express XAF ObjectSpace which can be cast to ObjectContext using ((EFObjectSpace)objectSpace).ObjectContext;
I experienced this problem, it's complicated to explain (so please forgive the analogy), the good news is that the solution is very simple.
Assume I have a record with a one to many relationship, which also has a relationship. (A "Car" has a number of "People"; "People" have a number of "Children"). The error occurrs when removing people from car. Doing a db.Cars.Remove(person);. Caused an unrelated error. To resolve that issue I did a db.Cars.Persons.RemoveRange(children); <--- After this I experienced the error as stated in this question. To resolve this remove the child's children from the database (not the "Parent" but the database iteself) db.Children.RemoveRange(childrenOfPeople);.
This is the cleanest solution to the problem using entity framework correctly without setting the state of records. Hope that helps someone.

Exclude property from updating when SaveChanges() is called

There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;

Prevent ModelState.IsValid from validating attached entities?

Is there a way to override ModelState.IsValid?
Some of the entities to be validated are just attached entities, so all the fields except the ID are not to be validate as the entity is in state Unchanged.
Is there a way to do this?
Has anyone faced this issue before?
Update
Say I have the following action:
[HttpPost]
public ActionResult SaveEntity(MyEntity entity)
{
var isValid = ModelState.IsValid; //false
}
Since the model validates all properties and all descendant properties of entity, there has to be a way to check on each entity of those descendants, whether it's attached to the context, and if it is, remove error from ModelState, something like the following:
public ActionResult TryValidateDetachedModel(MyEntity entity, DbContext context)
{
foreach (var ms in ModelState.Where(ms => ms.Value.Errors.Any()).ToArray())
// should iterate over something like GetAllEntityTypesMetadata()
{
var entity = GetEntityFromMetadata(ms);
if (context.Entry(entity).State == EntityState.Unchanged)
{
ms.Value.Errors.Clear();
}
}
}
What I'm trying to do in the above pseudo code is to check the entities in the validation chain, and if one of them is attached as Unchanged, skip validation / remove its errors.
Right now I have to do it hard-coded manually by checking ModelState.Key, I'm looking for a more generic and efficient way.
To clear all errors use next
ModelState.Clear();
regards
Here's what I do to ensure the validation only applies to the current entity:
foreach (var key in ModelState.Keys)
if (key.Split('.').Length > 2)
ModelState[key].Errors.Clear();
if (!ModelState.IsValid)
return BadRequest(ModelState);
The check for the occurrences of . means: if the modelstate key is something like currentDTO.relatedDTO.field then that validation error is ignored (cleared). If it's just id or currentDTO.validateThisField, then it doesn't get cleared.

Is there an equivalent of Rails ActiveRecord::Callbacks in ASP MVC?

Is there an equivalent of Rails ActiveRecord::Callbacks in ASP MVC?
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
I'm in a situation where we are not using identities for our primary key. We do this for reasons specific to our DB sharding design. Because of this we have a lookup table to find the next ID for a specific table. I'd like to automatically get this value and set it in an abstract class whenever a model is created/updated and before it is saved. I also need to update the lookup table with an incremented 'nextID' after the save is successful.
I'm open to other solutions on how to do this without callbacks as well.
So you need the callback just to increment ID in the lookup table? AFAIK there is no equivalent in ASP.NET, may be you could try with Async Controllers (http://msdn.microsoft.com/en-us/library/ee728598%28v=vs.100%29.aspx) and wait for a state change from the successful save, but I would prefer use a service specifically for this like Snowflake (https://github.com/twitter/snowflake/).
I found a solution using overrides as opposed to callbacks. It's my hope that ASP mvc adds support for callbacks as the framework continues to mature because callbacks allow for cleaner code by allowing the OnSave event to exist in the model[s] that the event is concerned with rather than the centralized DbContext class (separation of concerns).
Solution:
The SaveChanges method can be overridden in the Context Class (Entity Framework Power Tools creates the Context class is the 'Models' directory).
public override int SaveChanges()
{
// create a cache for id values in case their are multiple added entries in the dbcontext for the same entitytype
Dictionary<string, UniqueID> idCache = new Dictionary<string, UniqueID>();
IEnumerable<DbEntityEntry> changes = this.ChangeTracker.Entries();
foreach (var entry in changes)
{
//check if this is a new row (do nothing if its only a row update because there is no id change)
if (entry.State == System.Data.EntityState.Added)
{
//determine the table name and ID field (by convention)
string tableName = entry.Entity.GetType().Name;
string idField = entry.Entity.GetType().Name + "ID";
UniqueID id = null;
//if we've already looked this up, then use the cache
if (idCache.ContainsKey(tableName))
{
id = idCache[tableName];
}
//if we havn't looked this up before get it and add it to the cache
else
{
id = this.UniqueIDs.Find(tableName, idField);
//if it doesn't already exist in the lookup table create a new row
if (id == null)
{
id = new UniqueID(tableName, idField, 1);
// since this is a new entry add it
this.UniqueIDs.Add(id);
}
else
{
// set the state to modified
this.Entry(id).State = System.Data.EntityState.Modified;
}
}
entry.CurrentValues[tableName + "ID"] = id.NextID;
id.NextID = id.NextID + 1;
}
}
return base.SaveChanges();
}

Calling UpdateModel with a collection of complex data types reset all non-bound values?

I'm not sure if this is a bug in the DefaultModelBinder class or what.
But UpdateModel usually doesn't change any values of the model except the ones it found a match for.
Take a look at the following:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Edit(List<int> Ids)
{
// Load list of persons from the database
List<Person> people = GetFromDatabase(Ids);
// shouldn't this update only the Name & Age properties of each Person object
// in the collection and leave the rest of the properties (e.g. Id, Address)
// with their original value (whatever they were when retrieved from the db)
UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" });
// ...
}
What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0)
so what is going on here?
UPDATE:
I stepped through mvc source code (particularly DefaultModelBinder class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: UpdateCollection(...) which creates an inner ModelBindingContext that has a null Model property. Afterwards, that context is sent to the method BindComplexModel(...) which checks the Model property for null and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to UpdateCollection(...) to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.
You just gave me an idea to dig into ASP.NET MVC 2 source code.
I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called
This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..
public class Borrowers
{
public string FirstName{get;set;}
public string LastName{get;set;}
public List<Address> Addresses{get;set;}
}
I guess that, I will need to dig deeper to find out what is going on.
UPDATE:
The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE

Resources