ASP.NET MVC与EF 4.1导航属性

经过几天的学习EF了解(有点……)它是如何工作的,我终于意识到我可能有一个大问题。

想象一下,我有两个实体: PaisUF 。 它们之间的关系是Pais (0..1) ... (*) UF 。 截图: http : //i.imgur.com/rSOFU.jpg 。

说,考虑到我有一个名为UFController的控制器,它有EditCreate ,这很好。 我的视图使用EditorFor帮助器(或类似的)作为输入,因此当我提交表单时,控制器将接收一个UF对象,其中包含所有数据(自动),并引用几乎为空的 Pais 。 我的视图代码(部分代码):

 @* UF attributes *@ @Html.EditorFor(m => m.Sigla) @Html.EditorFor(m => m.Descricao) @Html.EditorFor(m => m.CodigoIBGE) @Html.EditorFor(m => m.CodigoGIA) @* Pais primary key ("ID") *@ @Html.EditorFor(m => m.Pais.Codigo) // Pais id 

控制器Edit动作代码:

 [HttpPost] public ActionResult Edit(UF uf) { try { if (ModelState.IsValid) { db.UFs.Attach(uf); db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified); db.SaveChanges(); return this.ClosePage(); // An extension. Just ignore it. } } catch (Exception e) { this.ModelState.AddModelError("Model", e.Message.ToString()); } return View(uf); } 

当我提交表单时,这就是动作收到的uf

 {TOTALWeb.UF} base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF} (...) CodigoGIA: 0 CodigoIBGE: 0 Descricao: "Foobar 2001" ID: 936 Pais: {TOTALWeb.Pais} PaisReference: {System.Data.Objects.DataClasses.EntityReference} 

而且uf.Pais

 {TOTALWeb.Pais} base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais} Codigo: 0 CodigoBACEN: null CodigoGIA: null CodigoIBGE: null Descricao: null UF: {System.Data.Objects.DataClasses.EntityCollection} 

原始信息(数据库中的信息)是uf.Pais.Codigo == 716 。 所以,现在我收到了更新的信息。 控制器没有在数据库中升级FK的问题。

我不想将EntityState从uf.Pais设置为Modified因为实体本身没有更改(我没有更改该条目的信息),但关系是。

换句话说,我正在尝试做的是更改FK的值,将uf.Pais指向另一个Pais实例。 Afaik,不可能将关系状态更改为Modified (抛出exception),所以我正在寻找替代解决方案。

我已经阅读了一些我在Google上发现的有关此类问题的主题,但我仍然没有找到一个简单而优雅的解决方案。 我在stackoverflow上读到的最后一篇:

  • 如何首先使用ASP.NET MVC 3和EF 4.1代码中的导航属性(/外键)
  • 带有ADO.NETentity framework的强类型ASP.NET MVC
  • 添加实体模型时出现错误3007

几天前我问了一个关于类似问题的问题( entity framework4.1 – FK的默认实体状态? )。 我不明白EF那段时间是如何工作的,所以现在很多东西对我来说都很清楚(这就是为什么我要开一个新问题)。

对于Create操作,我一直在测试这个解决方案(由Ladislav在我的另一个问题上提出),但它会生成一个额外的选择(对我们来说最终可能很慢):

 // Here UF.Pais is null db.UFs.AddObject(uf); // Create dummy Pais var pais = new Pais { Id = "Codigo" }; // Make context aware of Pais db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow. // Now make the relation uf.Pais = pais; db.SaveChanges(); 

我可以复制这个Edit (我猜),但我不想要额外的SELECT。

所以,在简历中:我正在尝试使用导航属性将数据发送到控制器并使用快速简便的方法将它们直接保存在数据库中(不会对实体造成太多影响 – 这些很简单,但我们有巨大的而且非常复杂,有很多FK!)。 我的问题是:有一个解决方案不涉及在数据库中执行另一个查询(一个简单的查询)?

谢谢,

里卡多

PS:对任何英语错误和任何混淆感到抱歉。

更新1 :使用BennyM的解决方案(有点……)

我测试了以下代码,但它不起作用。 它抛出一个exception:“ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法使用相同的键跟踪多个对象。” 可能是因为Pais已经在上下文中了,我想?

我正在使用Entities(由EF创建)类作为上下文。 另外,我不知道Entry方法是什么,我不知道它在哪里。 只是为了“有趣”,我测试了这个:

 // Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one. Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo }; db.Paises.Attach(existingPais); // Attach the edited UF. db.UFs.Attach(editedUF); // Set the correct Pais reference (ignoring the current almost-null one). editedUF.Pais = existingPais; // Change the object state to modified. db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified); // Save changes. db.SaveChanges(); return this.ClosePage(); 

当我尝试将editedUF附加到当前上下文时抛出exception。 我现在正在研究这个想法,试图寻找其他解决方案。 另外,你是对的BennyM,将Pais附加到上下文不会产生额外的SELECT。 我不知道那个时候发生了什么,它确实对数据库没有任何作用。

不过,这是一个手动解决方案:我必须为每个FK做到这一点。 这就是我想要避免的。 你看,一些程序员,即使你解释了100次,也不会记得每个 FK都这样做。 最终会回复给我,所以我试图避免任何可能导致错误的事情(数据库或代码),以确保每个人都可以在没有任何压力的情况下工作。 🙂

我正在回答我自己的问题,因为我找到了一个简单的解决方案(至少在我的情况下)。 我的场景使用了很多视图来输入数据(这意味着我有很多实体)。 我需要一个简单易用的解决方案,因此我删除了整个Entities EDMX文件(Ctrl + A,删除!)。

然后我决定再次添加PaisUF实体,但是选中复选框以显示FK属性。 首先我虽然他们不能一起工作,但他们可以,但你需要对如何使用它有点小心。 它们现在与导航属性和暴露的FK相关联。

我无法添加FK属性的原因是因为我手动执行此操作。 使用“从数据库更新模型”再次检查正确的选项,它完美无缺。

在我的编辑视图中,我将Pais的ID设置为FK属性,而不是Pais.Codigo 。 我这样做的原因是因为FK属性是标量属性,然后我可以检测到更改。

这是Pais输入的当前视图代码(它不完全是它,但它与此类似):

 @Html.EditorFor(m => m.PaisCodigo) 

顺便说一句, PaisCodigo是FK。 是的,它可能会与Pais.Codigo有点混淆,但我们还没有确定任何命名规则(尚未)。 关于这个想法的任何建议将不胜感激。

最终的Edit操作代码是这样的(我删除了error handling,使其看起来很简单!):

 [HttpPost] public ActionResult Edit(UF editedUF) { if (ModelState.IsValid) { // Attach the edited UF into the context and change the state to Modified. db.UFs.Attach(editedUF); db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified); // Save changes. db.SaveChanges(); // Call an extension (it's a redirect action to another page, just ignore it). return this.ClosePage(); } } 

这是我提交editedUF表格时收到的editedUF

 {TOTALWeb.UF} base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF} (...) CodigoGIA: 0 CodigoIBGE: 0 CodigoPais: 0 <-- new Pais ID! Descricao: "Foobar 2000" ID: 902 Pais: {TOTALWeb.Pais} PaisReference: {System.Data.Objects.DataClasses.EntityReference} Sigla: "RI" Usuarios: {System.Data.Objects.DataClasses.EntityCollection} 

如您所见, CodigoPais指向新的Pais ID。

关于editedUF.Pais导航属性,有一个小细节。 在将它附加到上下文之前,它是null 。 但是,嘿,添加后,这是发生的事情:

 {TOTALWeb.Pais} base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais} (...) Codigo: 0 CodigoBACEN: 1058 CodigoGIA: 0 CodigoIBGE: null Descricao: "Brasil" UFs: {System.Data.Objects.DataClasses.EntityCollection} 

所以,它已被填补。 这个成本应该是一个查询,但我无法在监视器上捕获它。

换句话说,只需公开FK,使用View更改它并使用navigation属性使代码更清晰。 而已! 🙂

感谢大家,

里卡多

PS:我使用dotConnect for Oracle作为EF 4.1的基础。 我们不使用SQL Server(至少现在)。 我之前说的“监视器”是devArt的dbMonitor,所以我可以看到发送到Oracle数据库的所有查询。 而且,再次,抱歉任何英语错误!

如果在模型中包含外键。 因此,向UF实体添加PaisId属性,您可以直接设置它,它将更新关联。

 using (var db = new Context()) { db.UFs.Attach(editedUF); editedUF.PaisId = theForeignKey; db.Entry(editedUF).State = EntityState.Modified; db.SaveChanges(); } 

此外,我已经测试了您在问题中已经提到的方法,并且在附加实体时我没有得到额外的选择。 所以这也有效:

 using (var db = new Context()) { ExistingPais pais = new Pais{Id =theId}; db.Pais.Attach(pais); db.UF.Attach(editedUF); editedUF.Pais = pais; db.Entry(editedUF).State = EntityState.Modified; db.SaveChanges(); } 

假设您的代码如下所示:

 public class Pais { public int Id { get; set; } public virtual ICollection UFs { get; set; } } public class UF { public int Id { get; set; } public virtual Pais Pais { get; set; } public int PaisId { get; set; } }