【问题标题】:Many-to-many relationships using EF Code First使用 EF Code First 的多对多关系
【发布时间】:2011-07-27 19:14:42
【问题描述】:

我有两个这样定义的类:

public class Questionnaire
    {
        public int QuestionnaireID { get; set; }
        public string Title { get; set; }
        public bool Active { get; set; }
        public virtual ICollection<Question> Questions { get; set; }
        public virtual ICollection<Vendor> Vendors { get; set; }
    }

public class Vendor
    {
        public int VendorID { get; set; }

        public string VendorName { get; set; }

        public virtual ICollection<Questionnaire> OpenQuestionnaires { get; set; }

        public virtual ICollection<Questionnaire> SubmittedQuestionnaires { get; set; }

        public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
    }

我相信这是在这些类之间建立多对多关系的正确方法,并且在构建项目时,我希望创建三个表。

但是,当我尝试将一份问卷与两个不同的供应商关联时,在尝试保存更改时收到以下错误 (context.SaveChanges()):

*违反了多重性约束。关系“QuestionnaireApp.Models.Vendor_OpenQuestionnaires”的角色“Vendor_OpenQuestionnaires_Source”具有多重性 1 或 0..1。*

如果我只将问卷分配给一个供应商,请保存更改,然后将其分配给另一个供应商并再次保存更改,我不再收到错误;然而,问卷仅与分配给它的最后一个供应商相关,这表明(充其量)正在创建一对多关系。

我希望我声明这些类之间的多对多关系的方式有问题,或者我需要在上下文类中添加一些东西来“鼓励”关系,但可能不支持这样的多对多关系,或者无法使用“代码优先”创建?

感谢您的宝贵时间,

杰森

【问题讨论】:

  • 我对您使用的任何一种技术都不熟悉,但在 SQL 中,要建立多对多关系,您需要一个桥接表,它与两个表都具有一对多,因此聚合了多对多.我在你的设计中没有看到这样的结构..
  • 我认为你需要明确地告诉 EF 这是一个多对多的关系,它引导 EF。
  • 一般来说,如果您在每个父对象中都有对应子集合的 ICollection,EF 将根据约定自动为您生成介入的桥接表。 EF 模型隐藏了干预表。

标签: c# .net entity-framework asp.net-mvc-3 ef-code-first


【解决方案1】:

如果您没有任何 Fluent API 代码,您的预期映射依赖于 EF Code First 约定。您希望在这里使用的约定是AssociationInverseDiscoveryConvention。现在,如果您查看 Intellisense(可能还有文档),它会说明这种约定:

将导航属性检测为每个属性的倒数的约定 其他当只有一对导航属性之间存在 相关类型

现在,问题来了:QuestionnaireVendor 之间不只有“一对”导航属性。 Vendor 中有 两个 集合,引用 QuestionnaireQuestionnaire 中有一个集合引用 Vendor。结果是这个约定没有得到应用,EF 映射实际上是三个一对多的关系,只有一端暴露为模型中的导航属性。

此外,您的模型无法实现您想要实现的映射:您无法将 一端 Questionnaire.Vendors 映射到 两端 Vendor.OpenQuestionnaires 和 @987654330 @。

一种解决方法是通过以下方式更改模型:

public class Vendor
{
    public int VendorID { get; set; }

    public string VendorName { get; set; }

    [NotMapped]
    public IEnumerable<Questionnaire> OpenQuestionnaires
    {
        get { return Questionnaires.Where(q => q.IsActive); }
    }

    [NotMapped]
    public IEnumerable<Questionnaire> SubmittedQuestionnaires
    {
        get { return Questionnaires.Where(q => !q.IsActive); }
    }

    public virtual ICollection<Questionnaire> Questionnaires { get; set; }
    public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}

现在Vendor.Questionnaires 映射到Questionnaire.VendorsAssociationInverseDiscoveryConvention 应该检测到这一点)并且辅助属性OpenQuestionnairesSubmittedQuestionnaires 允许您拉出选定的项目。 (我不确定IsActive是不是你的标志。否则你必须引入一些新的标志。)

[NotMapped] 属性只是为了使其明确。这可能不是必需的,因为 EF 不会映射 IEnumerable 集合和只读属性,反正只有一个 getter。

【讨论】:

  • 感谢您的详细解释。事实证明,我能够使用上面发布的更改获得所需的内容,但我相信我会从您为 EF 内部工作提供的额外见解中受益。
【解决方案2】:

想想,经过一个小时左右的搜索,我在发布问题 30 秒后去寻找确切的答案。

解决方案是将以下内容添加到上下文类中:

modelBuilder.Entity<Vendor>()
                .HasMany<Questionnaire>(x => x.OpenQuestionnaires)
                .WithMany(x => x.Vendors)
                .Map(x =>
                    {
                        x.MapLeftKey("vID");
                        x.MapRightKey("qID");
                        x.ToTable("VendorQuestionnaires");
                    });

我通过阅读这篇 Stack Overflow 帖子找到了答案:EF Code First Many-to-Many not working

【讨论】:

  • 我明白了,那么您在 VendorQuestionnaire 之间只有通过 OpenQuestionnaires 属性的多对多关系。 SubmittedQuestionnaires 呢?此集合是否属于VendorQuestionnaire 之间的另一个一对多关系?至少我希望 EF 会按照惯例映射它。
  • 给定的问卷可以在 OpenQuestionnaires 或 SubmittedQuestionnaires 中,不能同时在两者中。所以有了这个改变,我能够做到这一点并得到预期的结果,我还不完全理解它在“幕后”做了什么,但它似乎正在工作:)
  • 没关系,如果你已经测试过它并且它对你有用,那就没问题了。毫无疑问,这是您上面所拥有的有效映射。让我感到困惑的是,OpenQuestionnairesSubmittedQuestionnaires 现在在某种程度上是“不对称的”:您可以在 Vendors 集合中从一个问卷导航到将该问卷作为 open 问卷的供应商但不适用于提交此问卷的供应商,因为QuestionnaireVendor 类中的SubmittedQuestionnaires 集合之间没有公开关系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多