有没有更好的方法来使用MVC 4和Entity Framework 5添加子记录?

时间:2013-04-08 20:33:36

标签: asp.net-mvc entity-framework asp.net-mvc-4 entity-framework-5

我正在学习MVC并结合实体框架处理其无状态特性。我的问题是,是否有更优雅的方式来处理下面的场景?

我有两个POCO实体:

public class Contest
{
    public long ID { get; set; }
    ....   .....
    public ICollection<ContestPrize> Prizes { get; set; }
}

public class ContestPrize
{
    public long ID { get; set; }

    public Contest Contest { get; set; }

}

然后我有一个MVC视图,显示比赛和与之相关的所有奖品。在那个视图中,我有一个“创建奖”链接来创建一个新的奖品,将比赛的ID传递给ContestPrize控制器,如下所示:

@Html.ActionLink("Create Prize", "Create", "ContestPrizes", new { ContestId = Model.ID }, null)

ContestId作为隐藏字段保留在提交新奖项的表单中,以便我可以在创建时检索它。奖品控制器上的创建方法如下:

    [HttpPost]
    public ActionResult Create(int ParentID, ContestPrize ContestPrize)
    {


        if (ModelState.IsValid)
        {

            db.ContestPrizes.Add(ContestPrize);
            db.SaveChanges();
            return RedirectToAction("Index", "Contests", ParentID);
        }

        return View(ContestPrize);
    }

这是我遇到一些我不喜欢的丑陋的地方。以下是问题:

  1. ModelState.IsValid测试失败。这是因为父模型的导航属性为null。它还没有填充。我可以跳过模型上的验证并直接进入数据库,但有没有办法填充它而不用下面的#2?
  2. 此时未填充父属性,但我确实拥有来自隐藏表单字段的ParentID。由于一切都是无状态的,因此db对象没有父对象(这很好)。因此,我可以执行另一个数据库读取以使用parentID获取父对象,然后执行Parent.Prizes.Add(ContestPrize)ContestPrize.Contest = Parent。但是我真的不喜欢这个,因为当我拥有创建子记录所需的所有信息时,我正在进行额外的数据库搜索以读取父级。
  3. 那么,有没有办法让Entity Framework只设置父级的ID并保存子级而不必检索父对象?或者......理想情况下,有一种方法可以使用MVC在创建时使用父对象预填充新模型,因为在进入创建视图之前我确实有父对象吗?传递ViewData中的父对象可能有多糟糕?

    我可以通过重新加载父项来完成这项工作。但是如果可能的话我想避免它。

    思想?

    更新:将ContestID添加到ContestPrize POCO,然后使用[ForeignKey("ContestID")]将导航属性修改为@NSGaga建议完美无缺,并消除了一些杂乱的ParentID传递问题。新的POCO看起来像这样:

    public class Contest
    {
        public long ID { get; set; }
        ....   .....
        public ICollection<ContestPrize> Prizes { get; set; }
    }
    
    public class ContestPrize
    {
        public long ID { get; set; }
    
        public long ContestID { get; set; }
    
        [ForeignKey("ContestID")]
        public Contest Contest { get; set; }
    
    }
    

    我可以消除传递的ParentID并在视图中设置为隐藏字段,因为MVC中的数据绑定会处理新实体上的ContestID(我仍然必须将id传递给初始视图,但这都是它只是从那里开出来的。) Controller上的新Create操作如下所示:

    [HttpPost]
    public ActionResult Create(ContestPrize ContestPrize)
    {
        if (ModelState.IsValid)
        {
            db.ContestPrizes.Add(ContestPrize);
            db.SaveChanges();
            return RedirectToAction("Index", "Contests", ContestPrize.ContestID);
        }
    
        return View(ContestPrize);
    }
    

    干净简洁,没有乱七八糟。我喜欢它。

2 个答案:

答案 0 :(得分:3)

如果您正在使用'代码优先'(所有建议),那么您可以执行类似......

的操作
public class ContestPrize
{
    public long ID { get; set; }

    public long ContestID { get; set; }
    public Contest Contest { get; set; }
}

那应该创建(按惯例)并将ContestID映射到FK。在某些复杂的情况下,您可能需要在流畅的代码中指定该关系,但这通常就足够了。

  

然后你可以通过ContestID引用父母 - 填写 -   做Add

这是粗暴的,如果您需要更多信息,请告诉我。

答案 1 :(得分:1)

您可能不应该直接使用实体作为视图模型。我建议使用这样的东西:

// Entitiy Type
public class ContestPrize { ... }

// ViewModel Type
public class ContestPrizeViewModel { ... }

// In your controller
[HttpPost]
public ActionResult Create(int ParentID, ContestPrizeViewModel ContestPrize)
{
    if (ModelState.IsValid)
    {
        var entity = db.Create<ContestPrize>();
        entity.ContestID = ParentID;
        entity.Blah = ContestPrize.Blah
        db.ContestPrizes.Add(entity);
        db.SaveChanges();
        return RedirectToAction("Index", "Contests", ParentID);
    }

    return View(ContestPrize);
}

这使您可以轻松地将域逻辑与视图逻辑分开。它可能需要额外的代码,但它是一种更强大的工作方式。