在实体框架中完成子实体的插入/更新/删除
本文关键字:实体 插入 删除 更新 框架 | 更新日期: 2023-09-27 18:26:26
我知道以前有人问过这个问题,但经过长时间的搜索和编码,我无法找到一种有效且干净的方法。这是我所拥有的:
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; }
}
我将EF 5用于这个ASP.Net MVC应用程序。Generic Repository Pattern和使用InRequestScope()
的Ninject依赖注入已经到位,并且工作顺利。这些模型可以毫无问题地映射到实体/从实体映射到实体。
将新问题添加到数据库是直接的。我设置了一些QuestionChoice实例的Question属性,EF处理其余实例。
问题在于更新。假设我们在数据库中有一个问题,有3个问题选择:
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集合,其中一些已经在数据库中,一些应该添加到数据库中,还有一些应该从数据库中删除。
我读过很多帖子,比如:实体框架未保存修改的子
我可以用那种肮脏的方式处理编辑。还有新记录:
this._context.Entry(choice).State = EntityState.Added;
但我正在寻找一种更优雅的方式。还处理应该删除的记录。在这种情况下,是否有一种使用EF处理子实体的完整插入/更新/删除的好方法?老实说,我对英孚期望更高。
这是一个棘手的问题。不幸的是,我无法提供您喜欢的解决方案。我认为这是不可能的。EF无法跟踪对实体所做的更改,除非这些更改是在检索实体的上下文中进行的——这在web环境中是不可能的。唯一可行的方法是在POST到/Questions/Edit/1之后检索Question对象(在上下文中),并在POST的Question和从数据库检索的Questions之间执行一种类型的"合并"。这将包括在QuestionModel
和使用POSTed QuestionModel
从数据库检索的每个QuestionChoiceModel
上分配属性。我要说的是,这也不是一个好的做法,因为你会忘记包括一处房产。它会发生的。
我能提供的最好(也是最简单)的解决方案是使用上面的.Entry()
方法添加/编辑您的QuestionModel
和QuestionChoiceModel(s)
。在这里,您将牺牲"最佳实践"来获得不易出错的解决方案。
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();
}
这只是概念的证明
Controler具有函数UpdateModel,但它无法处理包含子记录的更复杂的模型。查找TestUpdate。
规则#1:每个表都有PK Id列。
规则#2:必须设置每个FK。
规则#3:需要设置级联删除。如果要删除相关记录。
规则#4:新记录的Id必须为0或更高。将为Null,但Id不能为Null。
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操作的情况下,您必须为子类型创建编辑器模板。