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

时间:2022-09-11 16:11:05

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. Generic Repository Pattern and Dependency Injection with Ninject using InRequestScope() are in place and work smoothly. These models are mapped to/from entities without a problem.

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

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. I've written some JQuery code that adds or deletes required markup for input elements if user wants to. So, the QuestionChoice with ID=1 might be edited on client, ID=2 might be deleted, and a new ID=4 might be added. The form data is bound back to a QuestionModel perfectly when user presses the Save button (POST: /Questions/Edit/1). The model is mapped to a Question entity correctly. That is where the story begins!

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

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? Honestly, I expected more from EF.

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

2 个解决方案

#1


7  

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. 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. This would include assigning properties on your QuestionModel and each QuestionChoiceModel retrieved from the database using your POSTed QuestionModel. I will say that this wouldn't be great practice either, because you WILL forget to include a property. It will happen.

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

The best (and easiest) solution I can provide would be to add/edit your QuestionModel and QuestionChoiceModel(s) using the .Entry() method above. You will sacrifice "best practice" here for a solution that will be less error-prone.

我可以提供的最好(也是最简单)的解决方案是使用上面的.Entry()方法添加/编辑QuestionModel和QuestionChoiceModel。您将在此处牺牲“最佳实践”,以获得不易出错的解决方案。

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

#2


0  

This is just proof of concept.

这只是概念的证明。

Controler have func UpdateModel but it wont work with more complex model which have included child records. Look for TestUpdate.

控制器有函数UpdateModel但它不适用于包含子记录的更复杂的模型。寻找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. if you want remove related record.

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

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. In case of AddEdit action you have to create editor Template for subtypes.

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

#1


7  

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. 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. This would include assigning properties on your QuestionModel and each QuestionChoiceModel retrieved from the database using your POSTed QuestionModel. I will say that this wouldn't be great practice either, because you WILL forget to include a property. It will happen.

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

The best (and easiest) solution I can provide would be to add/edit your QuestionModel and QuestionChoiceModel(s) using the .Entry() method above. You will sacrifice "best practice" here for a solution that will be less error-prone.

我可以提供的最好(也是最简单)的解决方案是使用上面的.Entry()方法添加/编辑QuestionModel和QuestionChoiceModel。您将在此处牺牲“最佳实践”,以获得不易出错的解决方案。

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

#2


0  

This is just proof of concept.

这只是概念的证明。

Controler have func UpdateModel but it wont work with more complex model which have included child records. Look for TestUpdate.

控制器有函数UpdateModel但它不适用于包含子记录的更复杂的模型。寻找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. if you want remove related record.

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

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. In case of AddEdit action you have to create editor Template for subtypes.

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