I am trying to use one view to update an object and all its child collections (based on a one-to-many relationship in a SQL Server database with an Entity Framework model).
It was suggested to use AutoMapper, and I tried this and made it work. (see Attempting to use AutoMapper for a model with child collections, getting a zero error in Asp.Net MVC 3 ).
But the solution is really hard to maintain. And when I try the simple one that I had to start with, using the entity object directly as a model (the "Consultant" object, the parent of all child collections), I can return all the correct changed data back to POST, and I can use the UpdateModel for them receipt, including child collections. Just. Of course, UpdateModel only worked after creating a custom mediation from the tooltip here in SO:
From my custom mediation:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { bindingContext.ModelMetadata.ConvertEmptyStringToNull = false; return base.BindModel(controllerContext, bindingContext); } protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; propertyMetadata.Model = value; string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);
Here is my simple POST editing method:
[HttpPost] [ValidateInput(false)] //To allow HTML in description box public ActionResult Edit(int id, FormCollection collection) { Consultant consultant = _repository.GetConsultant(id); UpdateModel(consultant); _repository.Save(); return RedirectToAction("Index"); }
But after that, UpdateModel worked. The next step, the problem is trying to call SaveChanges in a context that fails. I get this error:
The operation failed: the relation cannot be changed because one or more properties of the foreign key are nonzero. When a relationship change occurs, the related foreign-key property is null. If the foreign key does not support null values, a new relationship must be defined, a foreign key property must be assigned another non-zero value, or an unrelated object must be deleted.
I do not understand what is wrong. I see all the correct values ββin the "Consultant" object, I just canβt save it in the database. The AutoMapper route in this case (although an interesting tool) does not work very well, which greatly complicates my code and makes the application, which should be quite simple, a nightmare to support.
Can anyone make it clear why I get this error and how to overcome it?
UPDATE:
Reading some posts here, I found one that seemed slightly related: How to update a model in a database from asp.net MVC2 using Entity Framework? . I do not know if this is related to this, but when I checked the consultant object after POST, it seems that this object itself has an entitykey, but the individual elements in the collection are not (EntityKeySet = null). However, each item has a valid identifier. I do not pretend to understand this with EntityKey, so please explain if it has anything to do with my problem, and if so, how to resolve it ...
UPDATE 2:
I thought of something that might be relevant to my problems: The View uses the technique described by Stephen Sanderson (see http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length- list-aspnet-mvc-2-style / ), and when debugging, it seems to me that UpdateModel cannot match the elements in the collection in the view with those in the "Consultant" object. I am wondering if this is related to indexing in this technique. Here is the helper from this code (I can't do it myself, but it uses Guid to create indexes, which can be a problem):
public static class HtmlPrefixScopeExtensions { private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName) { var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName); string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
But then again, I would not think that this should be a problem, since the hidden input contains the identifier in the value attribute, and I thought that UpdateModel just looked at the field name to get the program (collection) and Name (property), and then the value for id ...? And again, during the update, there seems to be an incorrectness. In any case, html from FireBug is generated here:
<td> <input type="hidden" value="1" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Id" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Id" data-val-required="The Id field is required." data-val-number="The field Id must be a number." data-val="true"> <input type="text" value="Visual Studio" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Name"> <span data-valmsg-replace="true" data-valmsg-for="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" class="field-validation-valid"></span> </td>
Does anyone know if this is a problem? And if so, how can I get around this to be able to easily update collections using UpdateModel? (Although it is still possible to add or remove elements in the view before POST, that was the purpose of this technique to start with).