Typed ASP.NET MVC with ADO.NET Entity Framework
I've finally gotten this working after days of struggle.
I've got a simple database of People and Departments:
ADO.NET Entity Framework Entity Data Model diagram with Department and Person objects http://img39.imageshack.us/img39/1368/edmxdepartmentperson.gif
I can use strongly-typed ASP.NET MVC views for reference/navigation properties! See the list of departments...
ASP.NET MVC with DropDownList http://img11.imageshack.us/img11/7619/dropdownlistdepartment.gif
Part of my Person/Edit view:
<% using (Html.BeginForm()) {%>
<%= Html.Hidden("Id", Model.Id) %>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name", Model.Name) %>
</p>
<p>
<label for="DepartmentId">Department:</label>
<%= Html.DropDownList("DepartmentId", new SelectList((IEnumerable)ViewData["Departments"], "Id", "Name"))%>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
Part of my Person controller:
//
// GET: /Person/Edit/5
public ActionResult Edit(Guid id)
{
ViewData["Departments"] = ctx.Department;
Person model = (from Person p in ctx.Person
where p.Id == id
select p).FirstOrDefault();
return View(model);
}
//
// POST: /Person/Edit
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Person model)
{
ctx.AttachUpdated(model); //extension
ctx.SaveChanges();
return RedirectToAction("Index");
}
To get this working, I extended the Person EntityObject with a new DepartmentId property.
using System;
using System.Data;
using System.Data.Objects.DataClasses;
namespace ProjectName.Models
{
public partial class Person : EntityObject
{
public Guid DepartmentId
{
get
{
try
{
return (Guid)this.DepartmentReference.EntityKey.EntityKeyValues[0].Value;
}
catch
{
return Guid.Empty;
}
}
set
{
this.DepartmentReference.EntityKey = new EntityKey("JunkEntities.Department", "Id", value);
}
}
}
}
And I extended the Entity Framework ObjectContext with new AttachUpdated and ApplyReferencePropertyChanges methods:
using System;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
public static class EntityFrameworkExtensionMethods
{
public static void AttachUpdated(this ObjectContext ctx, EntityObject objectDetached)
{
if (objectDetached.EntityKey == null)
{
String entitySetName = ctx.DefaultContainerName + "." + objectDetached.GetType().Name;
Guid objectId = (Guid)objectDetached.GetType().GetProperty("Id").GetValue(objectDetached, null);
objectDetached.EntityKey = new System.Data.EntityKey(entitySetName, "Id", objectId);
}
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (ctx.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
ctx.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
ctx.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb); //extension
}
else
{
throw new ObjectNotFoundException();
}
}
}
public static void ApplyReferencePropertyChanges(this ObjectContext ctx, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
}
I just wanted to document my progress here. Please suggest improvements.
Thanks:
I've begun working with ASP.NET MVC which is why I came upon this thread, so I'm not sure if you you're still checking for improvements.
I don't like the idea of adding the new property to a partial class on the entity framework because it doesn't allow for as much change. Try labeling your Deparment DropDown "Department.Id" like this
<p>
<label for="Department.Id">Department:</label>
<%= Html.DropDownList("Department.Id", new SelectList((IEnumerable)ViewData["Departments"], "Id", "Name"))%>
</p>
The ModelBinding of the MVC Framework will pick up the value and apply it to the "Id" Property of the "Department" Navigation Property. What I found is that the other values of Department are null, but that is not significant. Now you have a way of retrieving the correct Department Entity and applying it to the Department Navigation Property of the new Person Entity created in the Model Bind to your Action parameter, something like:
newPerson.Department = ctx.Department.First(d => d.DepartmentId == newPerson.Department.Id);
In doing it this way, you don't need to update your Entity at all for a property it should have.
Improve your Edit controlling so that it handles the exceptions that are thrown and redisplays the input the user has typed in so far. I'm sure you were about to ;)
Update your view to have validators:
<label for="Name">Name:</label>
<%= Html.TextBox("Name", Model.Name) %>
<%= Html.ValidationMessage("Name", "*") %>
and then utilize them in your editing:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Person Model)
{
try
{
ctx.AttachUpdated(Model); //extension
ctx.SaveChanges();
return RedirectToAction("Index");
}
catch
{
foreach (var err in Model.Errors)
ModelState.AddModelError(err.PropertyName, err.ErrorMessage)
return View(Model);
}
}
链接地址: http://www.djcxy.com/p/33542.html