使用以下简单的关系数据库结构:Order有一个或多个OrderItems,每个OrderItem都有一个OrderItemStatus。
Order, OrderItem and OrderItemStatus tables linked with foreign key relationships http://www.mindthe.net/images/OrdersDB.jpg 实体框架v4用于与数据库通信,并且已从此模式生成实体。实例连接恰好在示例中称为EnumTestEntities。
Order Repository类的精简版本如下所示:
public class OrderRepository
{
private EnumTestEntities entities = new EnumTestEntities();
// Query Methods
public Order Get(int id)
{
return entities.Orders.SingleOrDefault(d => d.OrderID == id);
}
// Persistence
public void Save()
{
entities.SaveChanges();
}
}
MVC2应用程序使用Entity Framework模型来驱动视图。我正在使用MVC2的EditorFor功能来驱动编辑视图。
当回复对模型的任何更改时,将调用以下代码:
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
// Get the current Order out of the database by ID
Order order = orderRepository.Get(id);
var orderItems = order.OrderItems;
try
{
// Update the Order from the values posted from the View
UpdateModel(order, "");
// Without the ValueProvider suffix it does not attempt to update the order items
UpdateModel(order.OrderItems, "OrderItems.OrderItems");
// All the Save() does is call SaveChanges() on the database context
orderRepository.Save();
return RedirectToAction("Details", new { id = order.OrderID });
}
catch (Exception e)
{
return View(order); // Inserted while debugging
}
}
对UpdateModel的第二次调用具有ValueProvider后缀,该后缀与MVC2为View中的OrderItem的外键集合生成的自动生成的HTML输入名称前缀相匹配。
使用UpdateModel更新Order的OrderItems集合后,对数据库上下文调用SaveChanges()会产生以下异常:
"The operation failed: The relationship could not be changed because one or more
of the foreign-key properties is non-nullable. When a change is made to a
relationship, the related foreign-key property is set to a null value. If the
foreign-key does not support null values, a new relationship must be defined,
the foreign-key property must be assigned another non-null value, or the
unrelated object must be deleted."
通过此代码进行调试时,我仍然可以看到EntityKeys不是null,并且看起来与它们应该是相同的值。当您不从数据库更改任何提取的订单详细信息时,仍会发生这种情况。此外,数据库的实体连接在Get和SaveChanges的行为之间不会发生变化,因此它似乎也不是上下文问题。
任何可能导致此问题的想法?我知道EF4已经完成了外键属性的工作,但任何人都可以了解如何使用EF4和MVC2来简化更新;而不是必须手动填充每个属性。我曾希望EditorFor和DisplayFor的简单性也会扩展到Controllers更新数据。
谢谢
答案 0 :(得分:2)
我怀疑它正在绊倒OrderItemStatus。 formValues无法包含必要的OrderItem对象,因此对UpdateModel的调用无法正确更新它。也许它将它设置为null并在进程中孤立OrderItemStatus对象。
在调用保存更改之前,转储ObjectStateEntries的内容并使用以下代码查看它尝试保存回数据库的内容:
var items = context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added | System.Data.EntityState.Modified);
foreach (var item in items)
{
...
您还可以下载ASP.NET MVC2代码,将其链接并逐步执行UpdateModel以查看它正在执行的操作。
答案 1 :(得分:1)
您不应该直接使用表单更新您的实体,我认为这也意味着您将订单实体发送到GET中的视图?
将属性映射到Dto / Resource / ViewModel /<在此处插入其他重载,滥用的术语>。
这样,UpdateModel的神奇之处就是设置它不应该设置的东西(包括阻止黑客设置你不想要的属性)
我怀疑这会解决您的问题,因为UpdateModel可能会让您的实体失望。
首先从最佳实践开始,看看你得到了什么;)
答案 2 :(得分:1)
我写了一个非常简单的automapper-esque工具,它将实体的属性复制到模型,反之亦然。它看起来很长,但是这些线条都是评论。如果您使用它,您可以指定(实际上,您必须指定)允许将哪些属性从模型复制到实体,以防止格式错误的条目攻击。您可以在此处查看更多内容:http://kendoll.net/entity_framework_to_mvc2_model_conversion
/// <summary>
/// This is a class with a couple of static methods that make it easy to copy
/// an MVC2 model's property values to an Entity-Framework entity.
/// </summary>
/// <typeparam name="T">The type of the model declared in your MVC2 project.</typeparam>
/// <typeparam name="t">The type of the entity declared in your Entity Framework project.</typeparam>
public class EntityModeler<T, t>
where T : EntityModel<t>
where t : System.Data.Objects.DataClasses.EntityObject
{
/// <summary>
/// A new model of type T is created whose properties will have the same
/// value as the entity parameter respective of the property name.
/// </summary>
/// <param name="entity">
/// The entity whose property values should be copied to the model.
/// </param>
/// <returns>The new model object of type T.</returns>
public static T ModelEntity(t entity)
{
// create an object of type model and populate its properties
// with the value of the entity's properties. Then return the
// model.
var model = System.Activator.CreateInstance<T>();
model.UpdateModel(entity);
return model;
}
/// <summary>
/// A new IEnumerable set of models is created whose properties have the
/// same value as the entities' properties in the parameter.
/// </summary>
/// <param name="entities">
/// The entities whose properties should be copied to the models.
/// </param>
/// <returns>An IEnumerable set of models of type T.</returns>
public static IEnumerable<T> ModelEntities(IEnumerable<t> entities)
{
// Loop through all the entities in the entity list.
// Create a model type for each entity. This model will have its
// UpdateModel method called to copy the entity property values to
// the model. Then just add the model to a list and return the
// list.
var list = new List<T>();
foreach (var entity in entities)
list.Add(EntityModeler<T, t>.ModelEntity(entity));
return list;
}
}
/// <summary>
/// This is the base class for all MVC2 models that can be converted to an entity defined
/// by a class in an entity framework. Each child class should define its entity's type
/// using the "T" type parameter. This class provides methods to update its model properties
/// based on an entity and vice versa.
/// </summary>
/// <typeparam name="T">
/// The type of the entity that corresponds to the model that inherits this base class. This
/// object must descend from the base EntityObject type.
/// </typeparam>
public abstract class EntityModel<T>
where T : System.Data.Objects.DataClasses.EntityObject
{
/// <summary>
/// This method updates the inheriting model's properties to correspond with the
/// properties of an entity. The entity must be of the type defined by the inheriting
/// model. Reflection is used to get the public, readable properties from the entity
/// and the public, writable properties from the model. Then, we just write the entity's
/// value to the model for each property that shares the same name.
/// </summary>
/// <remarks>
/// Please note that this method will only copy an entity's public properties. Fields
/// are ignored (though they could easily be added). Also, this method will only perform
/// a shallow copy of the entity's properties.
/// </remarks>
/// <param name="entity">
/// The entity object whose properties should be copied to this model.
/// </param>
public void UpdateModel(T entity)
{
// get all the public properties of this model in an array
var myProperties = this.GetType().GetProperties(
BindingFlags.Instance |
BindingFlags.Public
);
// now get the type of the entity
var entityType = entity.GetType();
// loop through the properties
foreach (var myProp in myProperties)
{
// try to get the property with the same name from the entity.
// If we can read from the entity property and write to this
// object's property, then set this object's property according
// to the value of the entity property. But first, check if the
// value is null. If the value is null, then the corresponding
// property in this object must be of type Nullable.
var entityProp = entityType.GetProperty(myProp.Name);
if (entityProp != null && entityProp.CanRead && myProp.CanWrite)
{
var val = entityProp.GetValue(entity, null);
if (val == null)
if (myProp.PropertyType != typeof(Nullable))
continue;
myProp.SetValue(this, entityProp.GetValue(entity, null), null);
}
}
}
/// <summary>
/// This method updates the properties of an entity based on the values of
/// this model's properties. Reflection is used to get the properties of the
/// entity and the properties of this model, then the values of the model's
/// properties are copied to those of the entity that share the same name.
/// </summary>
/// <remarks>
/// Please note that this method only works on public properties. Fields are
/// ignored (though they could easily be added). Also, this method will only
/// perform a shallow copy of the model's properties.
/// </remarks>
/// <param name="entity">
/// This is the entity object whose properties should be updated. It must be
/// of the type defined by the inheriting class.
/// </param>
/// <param name="includeProperties">
/// This is an array of string values that represent the names of the properties
/// that the method should copy. If a property's name does not exist in this
/// array, then its value will not be copied.
/// </param>
public void UpdateEntity(ref T entity, params string[] includeProperties)
{
// get all the public properties of this model in an array
var propertyInfo = this.GetType().GetProperties(
BindingFlags.Instance |
BindingFlags.Public
);
// now get the type of the entity.
var entityType = entity.GetType();
// loop through each of the properties in the property array for the model type
foreach (var myProp in propertyInfo)
{
// check if this property is in the list of properties to include
if (includeProperties.Contains(myProp.Name))
{
// now try to get the property of the entity with the same name as the
// property of this type.
var entityProp = entityType.GetProperty(myProp.Name);
// check that the entity property exists and that we can write to it.
if (entityProp != null && entityProp.CanWrite && myProp.CanRead)
{
// get the current value of this property in the model object.
// If this property can't be read, the value will be set to null.
var val = myProp.GetValue(this, null);
// if the value is null, make sure the corresponding property of the
// entity is nullable. If it isn't, do not try to set the value. Just
// go to the next value in the array.
// TODO: We need a better method to determine if an entity property
// is nullable. This method won't allow NULL to be written to nullable
// columns.
if (val == null)
if (entityProp.PropertyType != typeof(Nullable))
continue;
// we're here, so the value should be ok to set. Set the value of the
// entity object to the value of the model object.
entityProp.SetValue(entity, val, null);
}
}
}
}
/// <summary>
/// This method works similarly to the one above, only it creates a new entity
/// instead of updating an existing one. The entity will be of the type T which
/// was declared by the inheriting class.
/// </summary>
/// <param name="includeProperties">
/// This is an array of string values that represent the names of the properties
/// that the method should copy. If a property's name does not exist in this
/// array, then its value will not be copied.
/// </param>
/// <returns>
/// The return value is the new entity with its properties set equal to
/// the respective properties of this model (well, at least those that were
/// specified in the parameter).
/// </returns>
public T CreateEntity(params string[] includeProperties)
{
var entity = System.Activator.CreateInstance<T>();
this.UpdateEntity(ref entity, includeProperties);
return entity as T;
}
}
以下是关于如何使用它的简短示例:
// Create your model with any properties you choose. You won't
// have to worry about people hacking your forms, because you'll
// define the properties that are allowed to be inserted/updated
// as you convert the model to an entity.
public class PersonModel : EntityModel<PersonEntity>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string SocialSecurity { get; set; }
}
// Create your MVC2 controller with an Index and Edit page.
public class PersonController : Controller
{
public ActionResult Index()
{
// Create a list of models based on every Person entity in your
// data set.
var models =
EntityModeler<PersonModel, PersonEntity>.ModelEntities(
YourEntities.PersonSet
);
return View("Index", models);
}
[HttpPost]
public ActionResult Edit(PersonModel model)
{
if (!ModelState.IsValid)
return View(model);
// Create an Person entity based on the Person model submitted
// by a user, allowing only the FirstName, LastName, and Address
// properties to be copied. This will protect the properties in
// your entity that shouldn't be editable by users (for example,
// a SocialSecurity property).
var entity = model.CreateEntity(
"FirstName",
"LastName",
"Address"
);
// The entity is ready to be used by your Entity Framework project,
// so you can add your UPDATE logic and error checking here.
// YourEntities.AddToPersonSet(entity);
// ...
// ...
return RedirectToAction("Details", new { id = entity.Id });
}
}