Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework with strongly-typed MVC

I'm using the ASP.NET MVC and the ADO.NET Entity Framework together.

I want my Views and Controllers strongly-typed.

But how am I supposed to handle entity associations?

Here's a simple example:

A Person has one Department. A Department has zero or more People.

Entity Data Model of Person and Department entities

My controller passes an instance of a Person object and a collection of all the Department objects to the View.

public class PersonController : Controller
{
    ...

    //
    // GET: /Person/Create

    public ActionResult Create()
    {
        Person Model = new Person();
        Model.Id = Guid.NewGuid();
        ViewData["Departments"] = db.Department;
        return View(Model);
    } 
    ...
}

My View has a "Department" DropDownList with all departments as options.

<% using (Html.BeginForm()) {%>

    <fieldset>
        <legend>Fields</legend>
        <p>
            <label for="Id">Id:</label>
            <%= Html.TextBox("Id") %>
            <%= Html.ValidationMessage("Id", "*") %>
        </p>
        <p>
            <label for="Name">Name:</label>
            <%= Html.TextBox("Name") %>
            <%= Html.ValidationMessage("Name", "*") %>
        </p>
        <p>
            <label for="Department">Family:</label>
            <%= Html.DropDownList("Department", new SelectList((IEnumerable)ViewData["Departments"], "Id", "Name"))%>
            <%= Html.ValidationMessage("Department", "*")%>
        </p>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>

<% } %>

But, the Person object posted to the controller has no Department and fails!

public class PersonController : Controller
{
    ...

    //
    // POST: /Person/Create

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Person Model)
    {
        try
        {
            db.AddToPerson(Model);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }
    ...
}

Why isn't the selected Department from DropDownList "Department" automatically added to the model Person?

How do I use the ADO.NET Entity Framework and ASP.NET MVC with strongly-typed Views and Controllers?

like image 629
Zack Peterson Avatar asked May 21 '09 21:05

Zack Peterson


2 Answers

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

like image 184
bbmud Avatar answered Oct 13 '22 12:10

bbmud


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:

  • NerdDinner Tutorial
  • How to Fake Foreign Key Properties
like image 20
Becky Avatar answered Oct 13 '22 12:10

Becky