Many-to-many relationships using EF Code First

无人久伴 提交于 2019-12-07 18:48:13

问题


I have two classes defined as such:

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; }
    }

I beleive this is the correct way to establish a many-to-many relationship between these classes, and when the project is built, I would expect three tables to be created.

However, when I attempt to to relate one Questionnaire to two different Vendors, I receive the following error when attempting to save the changes (context.SaveChanges()):

*Multiplicity constraint violated. The role 'Vendor_OpenQuestionnaires_Source' of the relationship 'QuestionnaireApp.Models.Vendor_OpenQuestionnaires' has multiplicity 1 or 0..1.*

If I assign a Questionnaire to only one Vendor, save the changes and then assign it to another and again save changes I no longer get the error; however the Questionaire is then related only to the last Vendor to which it was assigned, indicating that (at best) there is a one-to-many relationship being created.

I'm hoping that there is something wrong with the way I'm declaring the many-to-many relationship between these classes, or perhaps there is something I need to add to the context class to "encourage" the relationsip, but perhaps many-to-many relationships like this are not supported, or cannot be created using "Code First"?

Thank you for your time,

Jason


回答1:


If you don't have any Fluent API code your expected mapping relies on EF Code First conventions. The convention which you expect to kick in here is the AssociationInverseDiscoveryConvention. Now if you look in Intellisense (and probably also documentation) it says about this convention:

Convention to detect navigation properties to be inverses of each other when only one pair of navigation properties exists between the related types.

Now, that's the problem: You don't have only "one pair" of navigation properties between Questionnaire and Vendor. You have two collections in Vendor refering to Questionnaire and one collection in Questionnaire refering to Vendor. The result is that this convention doesn't get applied and EF maps actually three one-to-many relationships with only one end exposed as navigation property in the model.

Moreover the mapping you want to achieve is not possible with your model: You cannot map the one end Questionnaire.Vendors to the two ends Vendor.OpenQuestionnaires and Vendor.SubmittedQuestionnaires.

One workaround is to change your model the following way:

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; }
}

Now Vendor.Questionnaires is mapped to Questionnaire.Vendors (AssociationInverseDiscoveryConvention should detect this) and the helper properties OpenQuestionnaires and SubmittedQuestionnaires allow you to pull out the selected items. (I'm not sure if IsActive is your distinguishing flag. Otherwise you have to introduce some new flag.)

The [NotMapped] attribute is just here to make it explicite. It is probably not necessary because EF won't map IEnumerable collections and readonly properties with only a getter anyway.




回答2:


Go figure, after an hour or so of searching, I go and find the exact answer 30 seconds after I post my question.

The solution was to add the following to the context class:

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

I found the answer by reading this Stack Overflow post: EF Code First Many-to-Many not working



来源:https://stackoverflow.com/questions/6849920/many-to-many-relationships-using-ef-code-first

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!