+2  A: 

I haven't found that the out-of-the-box DropDownList helper works very well with model binding. I always have to grab the value of a DropDownList out of ViewData and map the value up manually in my controller prior to committing the changes to the repository or db.

Most of the time I'm using a DropDownList to show options for a custom type relationship (such as Departments for a Person). MVC can't know how to map up an object simply based on the value of a selected item in a list. Rather, you have to go grab that entity using the selected value and map it up yourself. I don't know if that's the problem here, and I haven't tried model binding a list to a model with just simple types as options (such as quantity of products to purchase or something like that), but I'd be curious to know more about this particular problem and how others are managing model binding with drop down lists.

mannish
I don't think your problem is isolated to using EF with MVC. I have the same problem using POCOs.
mannish
+3  A: 

I will try to guide you from the MVC side, as this is where the problem is, I believe

In the Create() method you create the Person object and the list of Departments from the database, then both objects are passed to the View. The View takes the data from the Department list and uses it to render an HTML form - using only Id and Name.

In the next step the form is submitted to the server as a collection of value-key pairs (standard POST). The routing engine takes the requested url from the action attribute and resolves it to PersonController.Create(Person Model) action. The argument of this method is Person, so the data binder kicks in, creates the new instance of Person class and tries to match the incoming data with properties of the Person. In case of Department the incoming value is the Id of the department (because this is what you set as a value member for the DropDownList), while the property Department on the Person class is probably of Department type. This is a mismatch, so it cannot fill the property and it is left empty.

As you can see, this is not the limitation of DropDownList, the problem is that you cannot pass all Department data to the DropDownList and have it recreated during save (like with the Person), because of a nature of the POST request, and that is why the DropDownList takes only two values from each Department (value and name).

My usual solution: as normally my models are not the same classes as my business objects, I do this by having two properties on the model: get only IEnumerable property, and another DepartmentId property (get/set). Then I use it like this:

<%= Html.DropDownList("DepartmentId", Model.Departments) %>

Then in the save action, I grab the Department from the db using DepartmentId, assign it to Person and save.

In your case (models being business objects) I would probably not try to bind the Department to the Person model automatically, but just grab the Id and do it by myself.

This is just a guess (I'm not an EF specialist), but I think you may have another issue here: if the db is a field on Controller, and it is recreated at each request, this may be some unnecessary overhead. I hope it doesn't open a db connection every time, please check it.

Hope that helps

bbmud
+1  A: 

I use a ViewModel (check out the NerdDinner tutorial, references at the bottom).

First, you need to fake a foreign key constraint by extending your model in a partial:

public partial class Person
{
  public int DepartmentId
  {
    get
    {
      if(this.DepartmentsReference.EntityKey == null) return 0;
      else 
        return (int)this.DepartmentsReference.EntityKey.EntityKeyValues[0].Value;
    }
    set
    {
      this.DepartmentsReference.EntityKey = 
          new EntityKey("YourEntities.DepartmentSet", "Id", value);
    }
  }
}

Second, create the ViewModel:

public class PersonFormViewModel
{
  public SelectList Departments { get; set: }
  public Person Pers { get; set; }

  public PersonFormViewModel(Person p, List<Department> departments)
  {
    this.Pers = p;
    this.Departments = new SelectList(departments, "Id", "Name", p.DepartmentId);
  }
}

Third, the controller action (an abbreviated create example):

public ActionResult Create()
{
  YourEntities entities = new YourEntities();
  List<Department> departments = entities.DepartmentSet.ToList();
  PersonFormViewModel viewModel = 
    new PersonFormViewModel(new Person(), departments);
  return View(modelView);
}    

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="Id")]Person personToCreate
{
  YourEntities entities = new YourEntities(); // Probably not instantiated here
  entities.AddToPersonSet(personToCreate);
  entities.SaveChanges();
  redirectToAction("Index");
}

Fourth, the view snippet:

<p>
  <label for="Name">Name:</label>
  <%= Html.TextBox("Name", Model.Pers.Name) %>
  <%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
  <label for="DepartmentId">Family:</label>
  <%= Html.DropDownList("DepartmentId", Model.Departments)%>
  <%= Html.ValidationMessage("DepartmentId", "*")%>
</p>

References:

Becky
There should be an easier way. Does MVC 2 has a solution for it? Imagine you creating a fake foreign key for each external id like DeparmentId. Our code becomes a little clutter. :-(
Junior Mayhé