简体   繁体   English

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

[英]Complete Insert/Update/Delete of Child Entities in Entity Framework

I know it has been asked before, but after long hours of searching and coding I can't get to a working and clean approach. 我知道它之前已被问过,但经过长时间的搜索和编码后,我无法采用干净利落的方法。 Here is what I have: 这是我有的:

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; }
}

I'm using EF 5 for this ASP.Net MVC application. 我在这个ASP.Net MVC应用程序中使用EF 5。 Generic Repository Pattern and Dependency Injection with Ninject using InRequestScope() are in place and work smoothly. 使用InRequestScope() Ninject的通用存储库模式和依赖注入已经到位并且运行顺畅。 These models are mapped to/from entities without a problem. 这些模型映射到实体/从实体映射没有问题。

Adding new Questions to database is straight forward. 向数据库添加新问题很简单。 I set Question property of some QuestionChoice instances, and EF handles the rest. 我设置了一些QuestionChoice实例的Question属性,EF处理其余的。

The problem is about updates. 问题是关于更新。 Assume we have a Question in database with 3 QuestionChoices: 假设我们在3个QuestionChoices的数据库中有一个问题:

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

When edit page of a Question opens (GET: /Questions/Edit/1), I show these 3 choices using a foreach in Razor. 当打开问题的编辑页面(GET:/ Questions / Edit / 1)时,我在Razor中使用foreach显示这3个选项。 I've written some JQuery code that adds or deletes required markup for input elements if user wants to. 我已经写了一些JQuery代码,如果用户愿意,可以为输入元素添加或删除所需的标记。 So, the QuestionChoice with ID=1 might be edited on client, ID=2 might be deleted, and a new ID=4 might be added. 因此,可以在客户端上编辑ID = 1的QuestionChoice,可以删除ID = 2,并且可以添加新的ID = 4。 The form data is bound back to a QuestionModel perfectly when user presses the Save button (POST: /Questions/Edit/1). 当用户按下Save按钮(POST:/ Questions / Edit / 1)时,表单数据将完美地绑定到QuestionModel。 The model is mapped to a Question entity correctly. 模型正确映射到Question实体。 That is where the story begins! 这就是故事开始的地方!

Now the Question entity has a collection of QuestionChoices some of which are already in database, some should be added to database, and some should be deleted from database. 现在,Question实体有一个QuestionChoices集合,其中一些已经在数据库中,一些应该添加到数据库中,一些应该从数据库中删除。

I've read many posts like: Entity Framework not saving modified children 我读了许多帖子: Entity Framework没有保存修改过的孩子

I can handle edits in that dirty way. 我可以用那种肮脏的方式处理编辑。 And also new records by: 还有新记录:

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

But I'm looking for a more elegant way. 但我正在寻找一种更优雅的方式。 And also handle records that should be deleted. 并处理应删除的记录。 Is there a good approach to handle complete insert/update/delete of child entities in this scenario using EF? 在这种情况下使用EF处理子实体的完整插入/更新/删除是否有一种好的方法? Honestly, I expected more from EF. 老实说,我对EF的期望更高。

This is a tough problem. 这是一个棘手的问题。 Unfortunately, I cannot offer the solution that you prefer. 不幸的是,我无法提供您喜欢的解决方案。 I do not believe it is possible. 我不相信这是可能的。 EF cannot track changes made to your entities unless they are made within the context that the entities are retrieved - which is impossible in a web environment. EF无法跟踪对您的实体所做的更改,除非它们是在检索实体的上下文中进行的 - 这在Web环境中是不可能的。 The only way for this to be possible would be to retrieve the Question object (within a context) after the POST to /Questions/Edit/1, and to perform a type of "merge" between the POSTed Question, and the Question retrieved from the database. 唯一可行的方法是在POST到/ Questions / Edit / 1之后检索Question对象(在上下文中),并在POSTed Question和从中检索的问题之间执行一种“合并”。数据库。 This would include assigning properties on your QuestionModel and each QuestionChoiceModel retrieved from the database using your POSTed QuestionModel . 这将包括使用您的POSTed QuestionModel您的QuestionModel和从数据库中检索的每个QuestionChoiceModel分配属性。 I will say that this wouldn't be great practice either, because you WILL forget to include a property. 我会说这也不是很好的做法,因为你会忘记包含一个属性。 It will happen. 它会发生。

The best (and easiest) solution I can provide would be to add/edit your QuestionModel and QuestionChoiceModel(s) using the .Entry() method above. 我可以提供的最好(也是最简单)的解决方案是使用上面的.Entry()方法添加/编辑QuestionModelQuestionChoiceModel(s) You will sacrifice "best practice" here for a solution that will be less error-prone. 您将在此处牺牲“最佳实践”,以获得不易出错的解决方案。

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();
}

This is just proof of concept. 这只是概念的证明。

Controler have func UpdateModel but it wont work with more complex model which have included child records. Controler有func UpdateModel,但它不适用于包含子记录的更复杂的模型。 Look for TestUpdate . 寻找TestUpdate

Rule#1: Every Table have PK Id column. 规则#1:每个表都有PK Id列。

Rule#2: Every FK have to be set. 规则#2:必须设置每个FK。

Rule#3: Cascade delete need to be set. 规则#3:需要设置级联删除。 if you want remove related record. 如果你想删除相关记录。

Rule#4: New Record need to have Id = 0 or better will be Null but Id cant be null. 规则#4:新记录需要Id = 0或更好是Null但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;
        }

    }

}

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

You need to create Controler for model and create view for action. 您需要为模型创建Controler并创建视图以进行操作。 In case of AddEdit action you have to create editor Template for subtypes. 如果是AddEdit操作,则必须为子类型创建编辑器模板。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM