Reputation: 747
I have been dealing with this for at least two days now. I don't have a book available to reference, and I cannot for the life of me find an explanation of how this is supposed to work.
What I am trying to do is a simple operation:
I have tried every way I can think of to get this to work. This should be easy. I have tried it with and without a ViewModel. I think it's clearer without the ViewModel
Note: Due to requirements, I am using .NET 3.5. Does this mean I am on an older version of EF?
Another Note: Yes, I am using VB. No, that is not my choice.
Controller:
Function Create() As ActionResult
PopulateForm()
Return View()
End Function
<HttpPost()>
Function Create(ByVal escalation As Escalation) As ActionResult
If TryUpdateModel(escalation) Then
repo.AddEscalation(escalation)
repo.Save()
Return RedirectToAction("Details", New With {.Id = escalation.Id})
End If
'The model did not save - there are validation errors'
PopulateForm()
Return View()
End Function
Private Sub PopulateForm()
ViewData("categoryList") = repo.GetAllCategories().ToList()
ViewData("statusList") = repo.GetAllStatuses().ToList()
ViewData("pathList") = New List(Of Path)
End Sub
Repository:
Public Sub AddEscalation(ByVal esc As Escalation)
esc.Created_At = DateTime.Now()
entities.AddToEscalations(esc)
End Sub
Public Sub Save()
entities.SaveChanges()
End Sub
View:
<div class="editor-label">
<%= Html.LabelFor(Function(model) model.Status)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(Function(model) model.Status, New SelectList(ViewData("statusList"), "Id", "Name"))%>
<%= Html.ValidationMessageFor(Function(model) model.Status)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(Function(model) model.Category)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(Function(model) model.Category, New SelectList(ViewData("categoryList"), "Id", "Name"))%>
<%= Html.ValidationMessageFor(Function(model) model.Category)%>
</div>
When using the DropDownListFor... model.Property, it fails in the TryUpdateModel call. The validation errors look like this in the returned form: The value '35' is invalid.
If I change it to DropDownListFor...model.Property.Id, it dies on SaveChanges() like so:
Cannot insert the value NULL into column 'Name',
table 'Escalations_Dev.dbo.Categories';
column does not allow nulls. INSERT fails.
The statement has been terminated.
In debugging, my Escalation does have a Category, with only the Id property populated and in the Detached state.
If I do the following to populate the full objects:
Public Sub AddEscalation(ByRef esc As Escalation)
esc.Created_At = DateTime.Now()
esc.Category = Me.GetCategory(esc.Category.Id)
esc.Status = Me.GetStatus(esc.Status.Id)
esc.Path = Me.GetPath(esc.Path.Id)
entities.AddToEscalations(esc)
End Sub
Public Function GetPath(ByVal id As Integer) As Path
Dim path As Path = entities.Paths.FirstOrDefault(Function(c) c.Id = id)
path.CategoryReference.Load()
Return path
End Function
I get: Entities in 'Escalations_Conn.Paths' participate in the 'FK_Paths_Categories' relationship. 0 related 'Category' were found. 1 'Category' is expected.
I really appreciate any help anyone has.
Upvotes: 2
Views: 1081
Reputation: 747
I appreciate all the help I was given.
After a lot of digging, I believe the problem is that I'm using EF1 because I'm on .NET 3.5. I know Linq to SQL is kind of deprecated, but for .NET 3.5 I'm pretty sure it is far better than EF1. As such, I've switched my project to use Linq to SQL. And it works exactly like I would imagine. I just bind my form to the foreign key:
<%= Html.DropDownListFor(Function(model) model.Category_Id,
New SelectList(ViewData("categoryList"), "Id", "Name"))%>
And voila! The model saves with the correct associations!
Upvotes: 1
Reputation: 24754
So the problem lies in the fact that you are only binding the Id of your Escalation.Category and not the name itself. Usually the correct approach is to use the Id to actually get the category and ignore binding the nested property.
If TryUpdateModel(escalation, <use ignore overload to ignore the category property>) Then
Dim existingCategory = repo.GetCategoryById( escalation.Category.Id )
escalation.Category = existingCategory
repo.AddEscalation(escalation)
repo.Save()
Return RedirectToAction("Details", New With {.Id = escalation.Id})
End If
Another alternative is to attach the bound Category to an ObjectContext so its state gets set correctly and its name values are hydrated.
If TryUpdateModel(escalation, <use ignore overload to ignore the category property>) Then
repo.Attach(escalation.Category, "Categories" )
repo.AddEscalation(escalation)
repo.Save()
Return RedirectToAction("Details", New With {.Id = escalation.Id})
End If
Since I don't use VB regularity and I don't know what repo is the above code should be considered pseudo code and you may have to look in the documentation for the correct overloads to use.
Upvotes: 2
Reputation: 17784
can't u just use ModelState.IsValid instead of TryUpdateModel that i always used on my edit actionresults. in debugging check which key of Modelstate contains error. u can also check metadata that is attached to Escalation model to check the source of error
Upvotes: 1