查看模型虚拟属性和下拉列表

时间:2014-01-16 17:36:22

标签: c# asp.net-mvc entity-framework

我无法掌握创建视图模型的正确方法并使用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> 字段,我做错了,但可能情况并非如此。

提前致谢。非常感谢任何见解/帮助。

5 个答案:

答案 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>

作为旁注,在模型和视图模型中,您应该尝试不要在名称中重复前缀:

  • Model.ModelId
  • Warranty.WarrantyId

除非它是外键或值:

  • Model.WarrantyId

为什么呢?使用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。