views:

128

answers:

3

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:

  1. Load a create form populated with 3 dropdownlist elements which reference other tables
  2. Fill out the form
  3. Submit the form and save the entity

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.

A: 

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

Muhammad Adeel Zahid
Thanks Muhammad,ModelState.IsValid() returns false, I believe with the same issues as TryUpdateModel() had:Message "The parameter conversion from type 'System.String' to type 'EscalationTool.Category' failed because no type converter can convert between these types."This is why I tried to change the form to reference Model.Category.Id instead.
coderj
don't u have field Escalation.CategoryID where category id would be the actual foreign key in database referring to category table. if so u should change type to Escalation.categoryID.
Muhammad Adeel Zahid
I thought that too, but as I'm using .NET 3.5, Foreign Keys are not exposed as properties. Maybe that would be the solution if I was on 4.0, and since I'm not on 4.0 I am forced to use some inelegant workaround?
coderj
+2  A: 

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.

jfar
Thanks jfar. I wasn't able to get the ExcludeProperties part of TryUpdateModel to work, but I did do the rest of what you suggested. I've added to the bottom of the question showing the results.
coderj
@coderj, I'll try to help later, off for a bit but I know exactly whats wrong
jfar
A: 

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!

coderj