CTP5 EF Code First问题

6
您可以在http://code.google.com/p/contactsctp5/找到演示此问题的源代码。
我有三个模型对象。联系人,联系信息和联系信息类型。每个联系人有多个联系信息,每个联系信息都是一种联系信息类型。我猜这很简单。但是,当我尝试编辑联系人对象时,我遇到了一个问题。我从联系人存储库中拉出它。然后我运行“UpdateModel(contact);”,它使用表单中的所有值更新对象。 (使用调试监控)。但是,当我保存更改时,我会得到以下错误:
操作失败:由于一个或多个外键属性不可为空,因此无法更改关系。当对关系进行更改时,相关的外键属性设置为 null 值。如果外键不支持空值,则必须定义新的关系,将外键属性分配给另一个非空值,或删除不相关的对象。
看起来像是在我调用 update model 后,它将我的引用设为 null,这似乎破坏了一切?对于如何解决此问题,您有何想法?谢谢。
以下是我的模型:
public partial class Contact {
    public Contact() {
      this.ContactInformation = new HashSet<ContactInformation>();
    }

    public int ContactId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public virtual ICollection<ContactInformation> ContactInformation { get; set; }
 }

 public partial class ContactInformation {
    public int ContactInformationId { get; set; }
    public int ContactId { get; set; }
    public int ContactInfoTypeId { get; set; }
    public string Information { get; set; }

    public virtual Contact Contact { get; set; }
    public virtual ContactInfoType ContactInfoType { get; set; }
  }

  public partial class ContactInfoType {
    public ContactInfoType() {
      this.ContactInformation = new HashSet<ContactInformation>();
    }

    public int ContactInfoTypeId { get; set; }
    public string Type { get; set; }

    public virtual ICollection<ContactInformation> ContactInformation { get; set; }
  }

我的控制器操作:

[AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(Contact person) {
      if (this.ModelState.IsValid) {
        var contact = this.contactRepository.GetById(person.ContactId);
        UpdateModel(contact);
        this.contactRepository.Save();
        TempData["message"] = "Contact Saved.";
        return PartialView("Details", contact);
      } else {
        return PartialView(person);
      }
    }

上下文代码:

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
      modelBuilder.Entity<Contact>()
        .HasMany(c => c.ContactInformation)
        .WithRequired()
        .HasForeignKey(c => c.ContactId);

      modelBuilder.Entity<ContactInfoType>()
        .HasMany(c => c.ContactInformation)
        .WithRequired()
        .HasForeignKey(c => c.ContactInfoTypeId);
    }

错误是否与您在表单上进行的更改类型无关?我可以想象,如果您从“联系人”中的集合中删除“联系信息”,则可能会发生此类错误。如果您只更改表单上的“FirstName”而不做其他更改,错误是否也会发生? - Slauma
2个回答

3

这里有几个要点。

1 如果您设置了延迟加载,只有在要求加载时才会加载子对象。您可以通过以下查询来实现此操作。

..

context.Contacts.Include(c => c.ContactInfos).Include(c => c.ContactInfos.ContactInfoType)

请参考这篇文章,详细了解如何确保对象按照您的要求加载。
如果您不想保存联系信息和联系信息类型(因为它们未被加载或您不需要它们),则需要告诉上下文不保存不应更新的子对象。可以使用以下代码实现:

..

context.StateManager.ChangeObjectState(entity.ContactInfos.ContactInfoType, EntityState.Unchanged);

当更改/使用国家对象来处理用户数据时,我发现需要这样做。我绝对不希望用户更新此对象。

我正在撰写一篇指南,但可能需要几周时间才能在我的博客上完成

3 MVC不会存储/发送表单中没有输入的任何内容。如果您将一个对象层次结构发送到表单中,而这些值未在隐藏输入中表示,则它们将在模型上为空。因此,我通常创建可编辑的视图模型,并在其上添加ToEntity和ToModel方法,以仅显示实体的版本。这也为我提供了安全性保障,因为我不希望所有种类的用户ID都在隐藏输入中,正好让我的实体直接映射到MVC(请参见这篇有关过度发布的文章)。

我本以为只要将contactinfo属性设置为虚拟的,UpdateModel就不会介意返回的属性是否存在,但我可能是错的,因为我还没有尝试过。


我的contactinfo和contactinfotype属性被标记为虚拟的。我通过一个简单的context.Contact.SingleOrDefault(c => c.ContactId == id)从上下文中获取我的contact对象。 - user342706
好的,我重新阅读了我的答案,发现有点偏颇,现在已经进行了编辑。你尝试过显式包含吗?我通常在我的存储库GetQuery方法中添加一个默认的加载级别。它看起来像我第一点中的示例。将你的context.Contact.SingleOrDefault(c => c.ContactId == id)更改为context.Contact.Include(c => c.ContactInfos).SingleOrDefault(c => c.ContactId == id)。 - Gats
我尝试添加了包含文件,但这并没有改变任何东西,我仍然得到相同的错误。表单中的所有值都被正确地映射到person变量中,当您进行调试时一切看起来都很好。然而,当我执行updatemodel()时,它似乎会将引用设置为null,当我保存时就会失败。 - user342706
好的,看起来问题可能出在updatemodel上。就像你说的那样,它正在使引用变为空。我不是UpdateModel的专家,因为我自己很少使用它。通常我会创建ViewModels仅用于编辑,因为我的层次结构变得非常复杂。如果你在表单中实际引用了那些对象,你也可以尝试先更新子项?例如UpdateModel(contact.ContactType, "ContactType")。抱歉没能解决你的问题。 - Gats
您可以在http://code.google.com/p/contactsctp5/找到演示此问题的源代码。 - user342706

1

我通过实体框架网站上的Morteza Manavi解决了这个问题。我的问题是由于我的ContactInformation模型属性'contactid'和'contacttypeid'不可为空所引起的。一旦我修复了这个问题,UpdateModel()就正常工作了。非常感谢!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接