简体   繁体   中英

Linking multiple properties to the same table in Entity Framework

Let me foreword this by saying this is my first real experience with both Entity Framework and relational databases in general. If I am doing it completely wrong, please tell me.

I want my data structured as something like this (Cut down on the "extra" code):

Indicators {
    int SomeText1TranslationRef
    List<Translation> SomeText1Translations
    int SomeText2TranslationRef
    List<Translation> SomeText2Translations
}

Measures {
    int SomeText3TranslationRef
    List<Translation> SomeText3Translations
    int SomeText3TranslationRef
    List<Translation> SomeText4Translations
}

Translation {
    Int TranslationID
    String LanguageCode
    String Text
}

So in essence, the indicators table would have a list of SomeText1 Translations as well as SomeText2, all joined using the TranslationID through the "Ref" properties.

I have the translation properties annotated with [ForeignKey("....Ref")] .

I expected this to work magically as the rest of the framework seems to, but instead the translation table gets columns named "SomeText1TranslationRef" and "SomeText2TranslationRef".

Am I doing this wrong?

I am looking at other features of Entity Framework and see an annotation for "InverseProperty". Is it something which may help?

I'm not 100% clear on your goal, but if an Indicator can have many Text1 translations and many Text2 translations, then that is 2 many-to-many relationships. Same for Measures. EF will need a join/bridge/junction table for this (IndicatorTranslation and MeasureTranslation). You can explicitly create this table, or let EF do it behind the scenes:

Indicator {
    // other indicator fields
    public virtual List<Translation> SomeText1Translations
    public virtual List<Translation> SomeText2Translations
}

Measure {
    // other measure fields
    public virtual List<Translation> SomeText3Translations
    public virtual List<Translation> SomeText4Translations
}

Translation {
    Int TranslationID
    String LanguageCode
    String Text

    // Use inverse attributes or fluent code to tell EF how to connect relationships
    [InverseProperty("SomeText1Translations")]
    public virtual ICollection<Indicator> TranslationForIndicatorText1 { get; set; }
    [InverseProperty("SomeText2Translations")]
    public virtual ICollection<Indicator> TranslationForIndicatorText2 { get; set; }
    [InverseProperty("SomeText3Translations")]
    public virtual ICollection<Measure> TranslationForMeasureText3 { get; set; }
    [InverseProperty("SomeText4Translations")]
    public virtual ICollection<Measure> TranslationForMeasureText4 { get; set; }
}

I'm happy to be corrected if I'm wrong since it's nothing I've tried for quite a while, but as far as I'm aware, EF is still not able to create relationships from one property on a type to two different other types, or vice versa, even with constraints that would make it valid.

In your case, you would end up with the 4 navigation properties being required on your translation. (int IndicatorRef1, int IndicatorRef2, int MeasureRef3, int MeasureRef4). Most wouldn't call it a dream scenario.

I asked a similar question a couple of years ago, and have since then sort of concluded that i was foolish trying to get EF to solve all my problems.

So here's an answer to what you're trying to achieve, and perhaps even a solution to 2 of your questions:

Don't rely on EF handle any scenario. Actually, pretty much don't rely on EF to handle relationships at all other than 1-1, 1-* or *-*. And some forms of inheritance.

In most other cases, you will end up with one navigation property for each type you're trying to reference, with data being populated with nulls for each navigation property but the one specifically targeted.

The good news? You don't have to rely on EF for it. The main advantage of EF is it's productivity. For certain cases, it's still worth leveraging EF, but providing your own methods of productivity. If you want to get a set of indicators with 2 collections of translations based on a ref, simply create a method that provides it.

Something like

        public IQueryable<Indicators> SetOfIndicatorsWithTranslations()
        {
            // Untested query that might need some fixing in an actual implementation 
            return ctx.Set<Indicators>().Select(ind => new Indicators() {
                                    Text1Ref= ind.Text1Ref, // whatever the property is 
                                    Text1RefList = ctx.Set<Translation>().Where(t => t.TranslationId == ind.Text1Ref),  
                                    Text2Ref= ind.Text2Ref,  
                                    Text2RefList = ctx.Set<Translation>().Where(t => t.TranslationId == ind.Text2Ref),  
                                });
        }

Now that's a query EF will handle for you gracefully.

There are of course a lot more elegant solutions to something like it. The important part is really that it's sometimes worth doing it yourself rather than restricting yourself to the capabilities of your tool of choice. (Well, at least that's the important part that I eventually learned :) )

Long story short, the caveat is the "Core" part of .Net Core. EF Core does not support convention-over-configuration many-to-many relationships yet (See here ).

The only way to achieve this is to manually create the junction tables as Steve suggested. Here is all the information needed: https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration

In previous versions of Entity Framework, this model definition was sufficient for EF to imply the correct type of relationship and to generate the join table for it. In EF Core 1.1.0, it is necessary to include an entity in the model to represent the join table, and then add navigation properties to either side of the many-to-many relations that point to the join entity instead:

The above link will most likely be updated with time so for context purposes, here is the code which goes along with it:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
} 

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
}

public class BookCategory
{
    public int BookId { get; set; }
    public Book Book { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

Alternatively, using Fluent:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BookCategory>()
        .HasKey(bc => new { bc.BookId, bc.CategoryId });

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Book)
        .WithMany(b => b.BookCategories)
        .HasForeignKey(bc => bc.BookId);

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Category)
        .WithMany(c => c.BookCategories)
        .HasForeignKey(bc => bc.CategoryId);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM