我无法掌握创建视图模型的正确方法并使用Entity Framework将信息保存回数据库,我似乎无法找到我正在寻找的信息,所以请原谅我,如果我忽略了它。
我遇到了这篇文章here,他似乎在问同样的问题,但没有得到答案。
我的主要问题是,
出于编辑目的,如果我的ProductModel model
关系Warranty model
,我应该在视图模型中使用virtual property Warranty
,还是应该使用{{1} }
如果我应该使用虚拟财产,为什么这段代码不能正确保存int WarrantyId
?
我是否需要明确标记或填充保修以进行更新?
请注意,这不会填充我的编辑视图并按预期选择列表。
我的(简化)代码设置如下:
型号:
Warranty
查看型号:
public int ModelId{ get; set; }
public int ModelNumber { get; set; }
public virtual Warranty Warranty { get; set;}
控制器(GET):
public int ModelId { get; set; }
[Required(ErrorMessage = "Model Number required")]
[StringLength(25, ErrorMessage = "Must be under 25 characters")]
[Display(Name="Model Number")]
public string ModelNumber { get; set; }
//related objects and necesary properties
public virtual Warranty Warranty { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
控制器(POST)(正在进行中):
public ActionResult Edit(int? id)
{
//check the id
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//get the model and make sure the object is populated
var model = _modelService.GetModel(id.Value);
if (model == null)
{
return HttpNotFound();
}
//pass our entity (db) model to our view model
var editModelModel = new EditModelModel();
editModelModel.InjectFrom(model);
//warranty select list
editModelModel.WarrantySelectListItems = WarrantySelectList(editModelModel.Warranty.WarrantyId);
//option multi select list
editModelModel.OptionSelectListItems = OptionSelectList();
return View(editModelModel);
}
查看(简化):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditModelModel editModelModel)
{
if (!ModelState.IsValid)
{
return View(editModelModel);
}
var modelEntity = new Model();
modelEntity.InjectFrom(editModelModel);
_modelService.Update(modelEntity);
_unitOfWork.Save();
return RedirectToAction("Index");
}
同样,我只想知道设置这些视图模型和模型的正确/最佳方式,以便EF尽可能多地完成工作。 我觉得如果我必须创建一个<div class="form-group">
@Html.Label("Warranty", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(x => x.Warranty.WarrantyId, Model.WarrantySelectListItems, "--Select--")
@Html.ValidationMessageFor(model => model.Warranty.WarrantyId)
</div>
</div>
字段,我做错了,但可能情况并非如此。
提前致谢。非常感谢任何见解/帮助。
答案 0 :(得分:5)
出于编辑目的,如果我有一个具有的ProductModel模型 保修模式关系,我应该使用虚拟财产 视图模型中的保修或我应该使用int WarrantyId吗?
您不会对virtual
的属性使用ViewModel
关键字,因为ViewModel
与实体框架无关。
使用virtual
关键字的原因是允许在Entity Framework中进行延迟加载。在您的情况下,如果您添加virtual
关键字
在产品POCO类中的保修导航属性,您可以访问以下保修属性:
Model.Warranty.WarrantyId
它没有将保修信息保存到您的数据库中的原因是您需要在Product类中定义Warranty外键属性。
在您的情况下,如果您使用代码优先方法而产品是您的POCO类,请保持简单如下:
public class Product
{
public int ModelId { get; set; }
public int ModelNumber { get; set; }
public int WarrantyId {get;set;}
[ForeignKey("WarrantyId ")]
public virtual Warranty Warranty { get; set; }
}
然后你的ViewModel:
public class MyViewModel
{
public Product Product { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
}
最后你的观点
@model MyViewModel
@Html.DropDownList("Product.Warranty.WarrantyId", Model.WarrantySelectListItems, "--Select--")
@Html.ValidationMessageFor("Product.Warranty.WarrantyId")
当然,您需要更改操作方法以满足ViewModel。
答案 1 :(得分:2)
出于编辑目的,如果我有一个具有保修模型关系的ProductModel模型,我应该在视图模型中使用虚拟属性Warranty还是我应该使用int WarrantyId?
您不应在视图模型中使用虚拟属性。视图模型仅表示显示视图所需的数据切片。当您从实体映射到该视图模型时,您不需要将任何内容标记为虚拟。如果您想知道virtual
在实体框架方面做了什么,请参阅this answer。
此外,您应该只包含呈现该视图所需的信息。因此,如果您只需要视图中的WarrantyId
,那么只需包含该内容。
由于您还在POST操作中将模型绑定回到相同的视图模型,因此您应该非常具体地了解您希望视图模型表示什么,否则您将自己对over-posting attack开放。
我觉得如果我必须创建一个WarrantyId字段,我做错了,但可能情况并非如此。
事实并非如此。您的每个观点都应该是独立的。当您第一次开始使用视图模型时,每次观看一次,您的初始反应就是违反DRY。但是,每个视图都有不同的要求。就视图模型本身而言,最明显的区别是验证。如果您在视图中使用实体,则所有这些视图都与您应用于实体的验证规则相关联。 (如果您不希望用户能够编辑整个实体,您也很容易被过度发布。)
但是,通过为视图设置单独的视图模型,并在视图模型本身上应用验证规则,您现在可以在视图中具有不同的验证要求。例如:
public class ViewAViewModel
{
[Required]
public int WarrantyId { get; set; }
}
public class ViewBViewModel
{
// No longer required.
public int WarrantyId { get; set; }
}
如果您已将Warranty
直接包含在这两个视图中,那么您将无法使用一组验证规则。
除此之外,我想知道为什么你的模型上有这个(我假设它是一个实体):
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
这不属于这里。这是演示详细信息,它不应存在于您的业务对象中。它应该存在于您的视图模型中。
答案 2 :(得分:1)
你正在处理的是导航属性(模型类的虚拟属性),这很好地解释了它们:
http://msdn.microsoft.com/en-us/data/jj713564.aspx
定义这些内容的棘手部分实际上就是如何为数据库设置DbContext。关于此的官方文件在这里:
http://msdn.microsoft.com/en-us/data/jj591620
简单的父子关系很容易处理,还有其他情况(比较棘手),你可以定义一些物理来自表中同一行的模型,但我不认为你是在这处理。
MVC部分是一个单独的问题,理想情况下你应该这样对待它。控制器代码应该只将真正的“工作”委托给其他类。工作单元格,如果你选择使用它,在你遇到很多东西要坚持/编辑多个表或实体集的情况之前,并不是真的有必要,你可能会想到希望一切都失败或整体成功。如果您只是处理单个对象的简单持久性,请不要将其与工作单元格单独复杂化。
使用EF或任何ORM框架时要记住的另一件事是,它需要跟踪更改或与现有记录进行比较,因此在您完成此操作时,键值变得非常重要。
答案 3 :(得分:1)
ViewModel是您可识别UI的数据的简化视图,仅包含UI呈现和用户输入所需的信息。
做更多工作似乎有误 - 为什么不直接使用模型?但是对于复杂的系统,您最终会遇到很多复杂问题,而且通常需要更改模型来容纳UI,这很麻烦。
此外,ViewModels允许您在没有数据库且没有这种复杂性的情况下测试UI。你真的解耦了UI问题和数据建模问题。
我通常最终都不会在UI上使用模型,总是通过ViewModel来最终简化我的生活,即使它最先工作也是如此。
所以让我们做一些改变。
查看模型(为清晰起见,已重命名为EditViewModel):
public int ModelId { get; set; }
// Removed for clarity, include needed properties in the UI
public int WarrantyId { get; set; }
public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
控制器(GET):
public ActionResult Edit(int? id)
{
//check the id
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//get the model and make sure the object is populated
var model = _modelService.GetModel(id.Value);
if (model == null)
{
return HttpNotFound();
}
//pass our entity (db) model to our view model
var editViewModel = new EditViewModel();
editViewModel.InjectFrom(model);
// You could instead create a custom injection like FlatLoopValueInjection
// That would flatten and remove duplicates from
// Model.Warranty.WarrantyId to ViewModel.WarrantyId
editViewModel.WarrantyId = model.Warranty.Id;
//warranty select list
editViewModel.WarrantySelectListItems = WarrantySelectList(editViewModel.WarrantyId);
return View(editViewModel);
}
自定义注入展平 - FlatLoopValueInjection:
http://valueinjecter.codeplex.com/wikipage?title=flattening&referringTitle=Home
控制器(POST):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel editViewModel)
{
if (!ModelState.IsValid)
{
return View(editViewModel);
}
// You need to reconstruct the model itself, there are faster ways but I wanted
// to showcase the logic behind it
// I didn't do any null check or anything to simplify
// Load the model used from the database
var modelEntity = _modelService.GetModel(editViewModel.ModelId);
// You can do an InjectFrom for the other properties you need
// with custom Injection to unflatten
modelEntity.InjectFrom(editViewModel);
// Load the selected warranty from the database
var warrantyEntity = _warrantyService.GetWarranty(editViewModel.WarrantyId);
// Update the warranty of the model with the one loaded
modelEntity.Warranty = warrantyEntity;
_modelService.Update(modelEntity);
_unitOfWork.Save();
return RedirectToAction("Index");
}
现在在你看来:
<div class="form-group">
@Html.Label("Warranty", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(x => x.WarrantyId, Model.WarrantySelectListItems, "--Select--")
@Html.ValidationMessageFor(model => model.WarrantyId)
</div>
</div>
作为旁注,在模型和视图模型中,您应该尝试不要在名称中重复前缀:
除非它是外键或值:
为什么呢?使用InjectFrom:
按照惯例更容易展平/不展平它们Model.Warranty.Id => (flatten) => Model.WarrantyId => (unflatten) => Model.Warranty.Id
此外,这是一种最佳做法。模型/表的名称已经告诉您实体类型,无需重复它。
答案 4 :(得分:0)
您必须在视图模型中使用int WarrantyId。 比在你看来
@Html.DropDownListFor(x => x.WarrantyId, Model.WarrantySelectListItems, "--Select--")
在Controller(POST)中获取WarrantyId(从下拉列表中选择)并从数据库中查找对象(var warranty = db.Warranties.Where(w =&gt; w.WarrantyId == editModelModel.WarrantyId或类似的东西)和该对象分配给modelEntity。