完整插入/更新/删除实体框架中的子实体

时间:2014-10-14 17:40:09

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

我知道之前有人问过,但经过长时间的搜索和编码后,我无法采用干净利落的方法。这就是我所拥有的:

public class QuestionModel
{
    public int QuestionID { get; set; }
    public string QuestionText { get; set; }

    public IList<QuestionChoiceModel> Choices { get; set; }
}

public class QuestionChoiceModel
{
    public int ChoiceID { get; set; }
    public string ChoiceText { get; set; }
}

我在这个ASP.Net MVC应用程序中使用EF 5。 Ninject使用InRequestScope()的通用存储库模式和依赖注入已经到位并且运行顺利。这些模型在没有问题的情况下映射到实体/从实体映射。

向数据库添加新问题很简单。我设置了一些QuestionChoice实例的Question属性,EF处理其余的。

问题在于更新。假设我们在带有3个QuestionChoices的数据库中有一个问题:

ChoiceID    QuestionID    ChoiceText
--------    ----------    ----------
1           1             blah blah
2           1             blah blah
3           1             blah blah

当问题的编辑页面打开(GET:/ Questions / Edit / 1)时,我在Razor中使用foreach显示这3个选项。我已经写了一些JQuery代码,如果用户愿意,可以为输入元素添加或删除所需的标记。因此,可以在客户端上编辑ID = 1的QuestionChoice,可以删除ID = 2,并且可以添加新的ID = 4。当用户按下Save按钮(POST:/ Questions / Edit / 1)时,表单数据将完美地绑定到QuestionModel。模型正确映射到Question实体。这就是故事开始的地方!

现在,Question实体有一个QuestionChoices集合,其中一些已经在数据库中,一些应该添加到数据库中,一些应该从数据库中删除。

我读过很多帖子,比如: Entity Framework not saving modified children

我可以用那种肮脏的方式处理编辑。还有新记录:

this._context.Entry(choice).State = EntityState.Added;

但我正在寻找一种更优雅的方式。并处理应删除的记录。在这种情况下使用EF处理子实体的完整插入/更新/删除是否有一种好的方法?老实说,我期待更多来自EF。

2 个答案:

答案 0 :(得分:7)

这是一个棘手的问题。不幸的是,我无法提供您喜欢的解决方案。我不相信这是可能的。 EF无法跟踪对您的实体所做的更改,除非它们是在检索实体的上下文中进行的 - 这在Web环境中是不可能的。唯一可行的方法是在POST到/ Questions / Edit / 1之后检索Question对象(在上下文中),并在POSTed Question和从中检索的问题之间执行一种“合并”。数据库。这包括使用您的POST QuestionModel为您的QuestionChoiceModel和从数据库中检索到的每个QuestionModel分配属性。我会说这也不是很好的做法,因为你会忘记包含一个属性。它会发生。

我可以提供的最佳(也是最简单)解决方案是使用上面的QuestionModel方法添加/修改您的QuestionChoiceModel(s).Entry()。您将牺牲“最佳实践”来获得一个不易出错的解决方案。

QuestionModel questionFromDb;
QuestionModel questionFromPost;

QuestionModelChoice[] deletedChoices = questionFromDb.Choices.Where(c => !questionFromPost.Choices.Any(c2 => c2.Id == c.Id));


using (var db = new DbContext())
{
    db.Entry(questionFromPost).State = questionFromPost.Id == 0 ? EntityState.Added : EntityState.Modified;

    foreach(var choice in questionFromPost.Choices)
    {
        db.Entry(choice).State = choice.Id == 0 ? EntityState.Added : EntityState.Modified;
    }

    foreach(var deletedChoice in deletedChoices)
    {
        db.Entry(deletedChoice).State = EntityState.Deleted;
    }

    db.SaveChanges();
}

答案 1 :(得分:0)

这只是概念证明。

控制器有func UpdateModel ,但它不适用于包含子记录的更复杂的模型。寻找 TestUpdate

规则#1:每个表都有PK Id列。

规则#2:必须设置每个FK。

规则#3:需要设置级联删除。如果你想删除相关记录。

规则#4:新记录需要Id = 0或更高才能为空但Id不能为空。

public class TestController<T> : Controller where T : class
{
    const string PK = "Id";

    protected Models.Entities con;
    protected System.Data.Entity.DbSet<T> model;
    public TestController()
    {
        con = new Models.Entities();
        model = con.Set<T>();
    }

    // GET: Default
    public virtual ActionResult Index()
    {
        ViewBag.Result = TempData["Result"];
        TempData["Result"] = null;

        var list = model.ToList();
        return View(list);
    }

    [HttpGet]
    public virtual ActionResult AddEdit(string id)
    {
        int nId = 0;
        int.TryParse(id, out nId);

        var item = model.Find(nId);
        return View(item);

    }

    [HttpPost]
    public virtual ActionResult AddEdit(T item)
    {
        TestUpdate(item);

        con.SaveChanges();

        return RedirectToAction("Index");
    }

    [HttpGet]
    public virtual ActionResult Remove(string id)
    {
        int nId = 0;
        int.TryParse(id, out nId);
        if (nId != 0)
        {
            var item = model.Find(nId);
            con.Entry(item).State = System.Data.Entity.EntityState.Deleted;
            con.SaveChanges();
        }
        return Redirect(Request.UrlReferrer.ToString());
    }

    private void TestUpdate(object item)
    {
        var props = item.GetType().GetProperties();
        foreach (var prop in props)
        {
            object value = prop.GetValue(item);
            if (prop.PropertyType.IsInterface && value != null)
            {
                foreach (var iItem in (System.Collections.IEnumerable)value)
                {
                    TestUpdate(iItem);
                }
            }
        }

        int id = (int)item.GetType().GetProperty(PK).GetValue(item);
        if (id == 0)
        {
            con.Entry(item).State = System.Data.Entity.EntityState.Added;
        }
        else
        {
            con.Entry(item).State = System.Data.Entity.EntityState.Modified;
        }

    }

}

这是项目https://github.com/mertuarez/AspMVC_EF/

您需要为模型创建Controler并创建视图以进行操作。 如果是AddEdit操作,则必须为子类型创建编辑器模板。