ASP.NET MVC与EF 4.1导航属性

时间:2011-08-09 19:11:29

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

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

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

说,考虑到我有一个名为UFController的控制器,它有EditCreate的操作,这些都很好。我的视图使用EditorFor帮助器(或类似的)作为输入,因此当我提交表单时,控制器将收到一个UF对象,其中包含所有数据(自动)并引用< em>几乎是空的 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<TOTALWeb.Pais>}

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<TOTALWeb.UF>}

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

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

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

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

几天前我问了一个关于类似问题的问题(Entity Framework 4.1 - default EntityState for a 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的解决方案(种类......)

我测试了以下代码,但它不起作用。它抛出一个异常:“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附加到当前上下文时抛出异常。我现在正在研究这个想法,试图寻找其他解决方案。另外,你是对的BennyM,将Pais附加到上下文不会产生额外的SELECT。我不知道那个时候发生了什么,它确实对数据库没有任何作用。

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

2 个答案:

答案 0 :(得分:2)

我正在回答我自己的问题,因为我找到了一个简单的解决方案(至少在我的情况下)。我的场景使用大量的视图进行数据输入(这意味着我有很多实体)。我需要一个简单易用的解决方案,因此我删除了整个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操作代码是这样的(我删除了错误处理以使其看起来很简单!):

[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的表单时收到的内容:

{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<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}

如您所见,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<TOTALWeb.UF>}

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

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

谢谢大家,

里卡多

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

答案 1 :(得分:0)

如果在模型中包含外键。因此,向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<UF> UFs { get; set; }
} 

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