【问题标题】:MVC 4 EF - Product / Multiple CategoriesMVC 4 EF - 产品/多个类别
【发布时间】:2015-02-04 11:02:14
【问题描述】:

编辑:我想我需要某种视图模型,但我不确定如何处理这种关系。

我正在尝试理解 MVC 4 和 EF Code First,并且我正在尝试映射多对多关系。

我有两节课;

public class Asset
{

    public int Id { get; set; }
    public string Title { get; set; }        
    public string Description { get; set; }

    public virtual ICollection<Category> Categories { get; set; }
}

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Asset> Assets { get; set; }
}

所以我试图让每个资产有多个类别,每个类别可能有多个资产。

在我的创建方法上我有;

    public ActionResult Create()
    {
        var model = new Asset();

        model.Categories = _db.Categories.ToList();
        return View(model);
    }

在我看来,我可以展示这些类别的唯一方法就是说; (注意模型中的大写 M。我不能使用视图中其他地方使用的小写模型)

@model MyProject.Models.Asset
    @using (Html.BeginForm("Create", "Assets", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <div>
        @foreach (var item in Model.Categories)
        {
            <p>@item.CategoryName</p>
        }

    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
        </div>
</div>
}

调用初始创建时,我可以看到我的资产并且它具有类别。然而,在返回创建方法上,它的值为空。我不知道为什么。我知道我没有做任何事情来编辑视图中的这些类别,我做不到那么远。但我不明白的是为什么我的模型离开了类别,但没有返回。

我的创建返回(这里我的资产类别为空)

    // POST: Assets/Create
    [HttpPost]
    public ActionResult Create(Asset model)
    {
        if (!ModelState.IsValid)
        {
            //error, return to view.
            return View();
        }
        try
        {
             //do stuff
        }
        catch
        {
            return View();
        }
    }

最终在创建资产时,我希望能够列出所有类别并允许选择该新资产将属于哪些类别。如果有人能帮我解决这个问题,你就是我的英雄。但如果我能理解为什么回来的不是我发出的,那将是一个开始。

【问题讨论】:

  • 两个表之间的外键关系设置了吗?
  • 是的,抱歉,我已更新我的问题以包含它。我在视图中有一些其他字段,我正在尝试修剪问题的语法。
  • 返回视图();您没有在 Create Method 中传回模型,这就是您看不到类别的原因。返回视图(模型);
  • 在 HttpPost 我还没有。因为在 HttpPost 上,传入它的类别的资产已经为 null,所以我仍然会返回 null。我确实将模型返回到 Get 上的视图,然后我可以在视图中看到它。当它回来时,我只是看不到它们。

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


【解决方案1】:

在我看来,我可以展示这些类别的唯一方法就是说; (注意模型中的大写 M。我不能使用视图中其他地方使用的小写模型)

我一直讨厌微软通过其在线生成的视图、教程和文章使用model =&gt; model.* 约定;它只会导致混乱。在您看来,Model 是一个实际的对象实例,即您定义为视图的“模型”的实例。您在Html.EditorFor 之类的东西中看到的小写model 实际上是lambda 表达式的参数。它可以被称为任何东西。例如,Html.EditorFor(x =&gt; x.Foo) 甚至 Html.EditorFor(supercalifragilisticexpialidocious =&gt; supercalifragilisticexpialidocious.Foo) 也可以正常工作。虽然传递给此参数的值通常是Model 对象实例,但Modelmodel 是完全不同的概念。

调用初始创建时,我可以看到我的资产并且它具有类别。然而,在返回创建方法上,它的值为空。我不知道为什么。我知道我没有做任何事情来编辑视图中的这些类别,我做不到那么远。但我不明白的是,为什么我的模型会留下类别,但返回时却没有。

这就是为什么。您无需在视图中编辑这些类别。它们没有与表单数据一起发布的字段,因此,模型绑定器在您的操作中实例化的类不包含任何类别数据。这是关键。进入视图的类实例不是在类似帖子之后返回的同一个类实例。每一个都是独一无二的东西。 post 操作不知道之前发生的任何事情;它只是发布了任何数据。假设该操作采用特定类的参数,模型绑定器将尝试新建该类的实例并将发布的数据绑定到该类的适当属性。它不关心最初发送到视图的内容;它甚至不关心它正在使用什么类。

最终在创建资产时,我希望能够列出所有类别并允许选择该新资产将属于哪些类别。如果有人能帮我解决这个问题,你就是我的英雄。但如果我能理解为什么返回的不是我发出的,那将是一个开始。

这是有趣的部分。首先,您绝对必须为此使用视图模型。如果您不熟悉视图模型,它们只是用作视图模型的类,因此得名。你在这里传递的东西,Asset,在技术上是一个“实体”,它是一个用于数据传输的类,通常是到/从数据库。虽然可以将实体用作视图的模型,但正如您在此处所做的那样,它并不真正适合。

存在明显的利益冲突,因为表示数据库中某些表模式的类的需求与表示 UI 层数据的类的需求大不相同。这就是视图模型的用武之地。在最传统的意义上,视图模型只是表示需要在一个或多个视图中显示和/或编辑的数据。它可能与特定实体有许多共同的属性,或者它可能只有这些属性的子集,甚至完全不同的属性。您的应用程序的工作是从您的实体“映射”到您的视图模型,反之亦然,以便将实体保存到持久性存储的逻辑可以完全从用户如何与该数据交互的逻辑中抽象出来.

视图模型对您的目的如此重要的原因是 HTML 中的表单元素具有某些限制。它们只能处理可以表示为字符串的数据:诸如整数、布尔值、实际字符串等。它们特别不适合处理复杂的对象,例如您的 Category 类。换句话说,发回代表Categorys 的整数ID 列表是完全可以实现的,但发回用户选择的完整Category 实例则完全不可信。

由于您的实体需要一个类别列表,而您的视图只能发布一个整数列表,因此存在根本性的脱节。使用视图模型提供了一种弥补差距的方法。此外,它还允许您拥有其他属性,例如用于填充您的选择列表的类别选项列表,这完全不适合放在您的实体类中。

对于您的场景,您需要如下视图模型:

public class AssetViewModel
{
    // any other asset properties you need to edit

    public List<int> SelectedCategoryIds { get; set; }

    public IEnumerable<SelectListItem> CategoryChoices { get; set; }
}

然后,您可以使用以下方法在视图中创建多选列表:

@Html.ListBoxFor(m => m.SelectedCategoryIds, Model.CategoryChoices)

现在,用来自您的实体的数据填充您的视图模型。在创建视图中,实体尚不存在,因此您无需进行任何映射。您唯一需要做的就是填充您的CategoryChoices 属性,以便视图中的选择列表包含一些数据。但是,根据上面关于需要回发的数据的讨论,否则它将为空,因为选择列表的实际内容永远不会被发布,您需要在每个创建和编辑操作中填充它,对于 GET 和 POST。因此,最好将此逻辑分解为控制器上每个操作都可以调用的私有方法:

private void PopulateCategoryChoices(AssetViewModel model)
{
    model.CategoryChoices = db.Categories.Select(m => new SelectListItem
    {
        Value = m.Id,
        Text = m.Name
    };
}

然后,在您的 create GET 操作中,您只需新建视图模型并填充您的类别选择:

public ActionResult Create()
{
    var model = new AssetViewModel();

    PopulateCategoryChoices(model);
    return View(model);
}

在发布版本中,您现在需要将发布的数据映射到您的 Asset 实体:

[HttpPost]
public ActionResult Create(AssetViewModel model)
{
    if (ModelState.IsValid)
    {
        var asset = new Asset
        {
            Title = model.Title,
            Description = model.Description,
            // etc.
            Categories = db.Categories.Where(m => model.SelectedCategoryIds.Contains(m.Id))
        }

        db.Assets.Add(asset);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    PopulateCategoryChoices(model);
    return View(model);
}

编辑 GET 操作类似于创建版本,只是这一次,您有一个需要映射到视图模型实例的现有实体:

var asset = db.Assets.Find(id);
if (asset == null)
{
    return new HttpNotFoundResult();
}

var model = new AssetViewModel
{
    Title = asset.Title,
    Description = asset.Description,
    // etc.
    SelectedCategoryIds = asset.Categories.Select(m => m.Id).ToList()
};

同样,编辑 POST 操作类似于创建版本,但您将从视图模型映射到现有资产,而不是创建新资产。此外,由于您具有多对多关系,因此在保存类别时必须格外小心。

// map data
asset.Title = model.Title;
asset.Description = model.Description;
//etc.

// You might be tempted to do the following:
// asset.Categories = db.Categories.Where(m => model.SelectedCategoryIds.Contains(m.Id));

// Instead you must first, remove any categories that the user deselected:
asset.Categories.Where(m => !model.SelectedCategoryIds.Contains(m.Id))
    .ToList().ForEach(m => asset.Categories.Remove(m));

// Then you need to add any newly selected categories
var existingCategories = asset.Categories.Select(m => m.Id).ToList();
db.Categories.Where(m => model.SelectedCategoryIds.Except(existingCategories).Contains(m.Id))
    .ToList().ForEach(m => asset.Categories.Add(m));

这里的额外步法是必要的,以防止两次保存相同的关系,导致完整性错误。默认情况下,Entity Framework 为多对多关系创建一个连接表,该连接表由一个复合主键组成,该主键由关系每一侧的外键组成。

【讨论】:

  • 我希望我能更多地投票给你,因为你花时间解释这一点。完美,正是我想要的。谢谢好心的先生!
  • ATM 我正在​​使用(但不确定是否最好) if(model.SelectedCategoryIds == null) {asset.Categories = null; } else {asset.Categories.Where(m => !model.SelectedCategoryIds.Contains(m.Id)) .ToList().ForEach(m =>asset.Categories.Remove(m)); }
  • 类别删除行涵盖了该场景。 SelectedCategoryIds 在帖子中永远不会为空。如果用户删除了所有选择,它将只是一个空列表。类别删除代码会删除 SelectedCategoryIds 中不存在的任何类别关联。
【解决方案2】:

您的类别为空的原因是您没有在 POST 上绑定它。在 POST 期间它们不在字段中。

试试这个,看看是否填写:

@for (int i = 0; i < Model.Categories; i++)
{
    @Html.TextBoxFor(model => model.Categories[i].CategoryId)
    @Html.TextBoxFor(model => model.Categories[i].CategoryName)
}

【讨论】:

  • 我明白你在说什么,他们不受约束。这开始有意义了。有了你所拥有的,我必须将其更改为 @for (int i = 0; i 我认为这与我的问题有关 [at]model 如何与从控制器传递的实例相关,而 [at]Model 如何与顶部的类声明相关。感谢您到目前为止的帮助. 我真的很难理解这一点。
【解决方案3】:

1. 返回视图();

您没有在 Create Method 中传回模型,这就是您看不到 Model 为 NULL 的原因。

    // POST: Assets/Create
    [HttpPost]
    public ActionResult Create(Asset model)
    {
        if (!ModelState.IsValid)
        {
            //error, return to view.
            return View(model);
// If you don't pass back the model to you view you will see model is NULL
        }
        try
        {
             //do stuff
        }
        catch
        {
            return View(model);
        }
    }
  1. Cateogries 在您的情况下将始终为空,因为您无法像在您的情况下那样发回列表。

尝试循环显示它们,然后 MVC 模型 Binder 将能够将您的列表绑定到模型:

@for (int i = 0; i < Model.Categories; i++)
{
    @Html.HiddenFor(model => model.Categories[i].CategoryId)
}
  1. 如果要保存 SelectedCategories,则必须使用 MultiSelect

【讨论】:

    猜你喜欢
    • 2022-06-10
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多