MVC 4 EF - 产品/多个类别

时间:2015-02-04 11:02:15

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

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

我试图理解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>
}

当调用初始创建时,我可以看到我的资产,它有类别。但是在返回create方法中,它为null。我无法解决原因。我知道我没有在视图中编辑这些类别,我无法做到这一点。我不明白的是,为什么我的模型留下了类别,但没有回来。

我的创建回报(此处我的资产类别为空)

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

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

3 个答案:

答案 0 :(得分:4)

  

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

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

  

当调用初始创建时,我可以看到我的资产,它有类别。但是在返回create方法中,它为null。我无法解决原因。我知道我没有在视图中编辑这些类别,我无法做到这一点。我不明白的是,为什么我的模型留下了类别,但没有回来。

这就是原因。您在视图中没有做任何事情来编辑这些类别。没有字段可以与表单数据一起发布,因此,您的操作中由模型绑定器实例化的类不包含任何类别的数据。这是关键。进入视图的类实例是而不是在post之后返​​回的同一个类实例。每一件都是独一无二的。邮政行动不知道任何事情发生在它之前;它只是发布了任何数据。假设操作采用特定类的参数,模型绑定器将尝试新建该类的实例并将发布的数据绑定到该类的适当属性。它并不关心最初发送到视图的内容;它甚至不关心它与哪个班级合作。

  

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

这是有趣的部分。首先,您必须使用视图模型。如果您不熟悉视图模型,它们只是用作视图模型的类,因此名称。您在这里传递的内容Asset在技术上是一个&#34;实体&#34;,这是一个用于数据传输的类,通常是来自/来自数据库。虽然可以将实体用作视图的模型,但正如您在此处所做的那样,它并不适合它。

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

视图模型对于您的目的非常重要的原因是HTML中的表单元素具有某些限制。它们只能处理可以表示为字符串的数据:像int,bools,实际字符串等等。它们特别不适合处理复杂对象,例如Category类。换句话说,回发表示Category s的整数id列表是完全可以实现的,但回发用户选择的完整Category实例是完全不可信的。< / p>

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

对于您的方案,您需要一个视图模型,如:

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属性,以便视图中的选择列表包含一些数据。但是,基于上面关于需要回发的数据的讨论,否则它将为null,因为选择列表的实际内容永远不会发布,您需要在每个创建和编辑中填充此内容GET和POST都有动作。因此,最好将此逻辑分解为控制器上的每个操作都可以调用的私有方法:

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

然后,在您的创建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为多对多关系创建一个连接表,该关系由一个复合主键组成,该复合主键由关系每一侧的外键组成。

答案 1 :(得分:0)

<强> 1     return View();

您没有在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在您的情况下将始终为空,因为您不能像在案件中那样回复列表。
  2. 尝试在循环中显示它们然后MVC模型Binder将能够将您的列表绑定到Model:

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

答案 2 :(得分:0)

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

试试这个并查看它们是否已填写:

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