简体   繁体   中英

Implementing a One-to-Zero-or-One Relationship and a One-to-Many Relationship of the Same Class

I am having trouble mapping the following classes.

I want MainAboutPage to be optional (one-to-zero-or-one) and AboutSubPages is obviously one-to-many.

Ideally I want to keep the WebsiteId property on the WebsitePage class.

public class Website
{
    public int Id { get; set; }

    public virtual WebsitePage MainAboutPage { get; set; }

    public ICollection<WebsitePage> AboutSubPages { get; set; }

}


public class WebsitePage
{
    public int Id { get; set; }

    public int WebsiteId { get; set; }

    public virtual Website Website { get; set; }
}

When I use no fluent mapping I get

Unable to determine the principal end of the relationship. Multiple added entities may have the same primary key.


When I use this fluent mapping:

        modelBuilder.Entity<Wesbite>()
            .HasMany(x => x.AboutSubPages)
            .WithRequired(x => x.Website)
            .HasForeignKey(x => x.WebsiteId);

I get:

Unable to determine the principal end of the 'Wesbite_AboutSubPages' relationship. Multiple added entities may have the same primary key.


And when I use this fluent mapping:

        modelBuilder.Entity<Website>()
           .HasOptional(x => x.MainAboutPage)
           .WithRequired();

        modelBuilder.Entity<Wesbite>()
            .HasMany(x => x.AboutSubPages)
            .WithRequired(x => x.Website)
            .HasForeignKey(x => x.WebsiteId);

I get:

Unable to determine the principal end of the 'Website_MainAboutPage' relationship. Multiple added entities may have the same primary key.


And when I use this fluent mapping:

        modelBuilder.Entity<Website>()
           .HasOptional(x => x.MainAboutPage)
           .WithRequired(x => x.Website);

        modelBuilder.Entity<Wesbite>()
            .HasMany(x => x.AboutSubPages)
            .WithRequired(x => x.Website)
            .HasForeignKey(x => x.WebsiteId);

I get:

Wesbite_MainAboutPage_Target: : Multiplicity is not valid in Role 'Wesbite_MainAboutPage_Target' in relationship 'Website_MainAboutPage'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.


I have been endlessly reading the configuration samples from MS: https://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx and https://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx

My brain is pickled, please excuse me if I am missing something obvious. I'd really appreciate some pointers toward getting this set up as I'd like.

Thanks in advance.

I believe the issue will lie in that you don't have enough information for EF to differentiate between the AboutSubPages and the MainAboutPage reference relative to the Website. To have a 1..0/1 relationship for MainAboutPage on the Website, you would need a MainAboutPageId declared in the Website table.

modelBuilder.Entity<Website>()
   .HasOptional(x => x.MainAboutPage)
   .WithRequired(x => x.Website)
   .HasForeignKey(x => x.MainAboutPageId);

Or you can elect to use a Shadow Property (EF Core) or Map.MapKey (EF6) to map the relationship without the FK exposed in the entity. (Recommended)

The caveat of having both a 1..0/1 plus 1..many of entity relationship to the same related entities is that there is no way to enforce that the MainAboutPage actually belongs to the sub collection. Because the 1..0/1 relies on a FK from web page to the sub page, nothing enforces that sub page has to point back to the same website. EF and Database are perfectly happy to have WebSite ID #1 point to a sub page with a WebSite ID #2.

A better approach may be to look at just maintaining an AboutSubPages collection and adding a PageOrder numeric unique index to the SubPage entity. The "main" sub page would be the one with the lowest PageOrder for example.

Ie to select a web site details and it's "main" about page:

var websites = context.Websites
    .Select(x => new WebsiteSummaryViewModel
    {
        Name = x.Name,
        AboutPageURL = x.AboutSubPages
            .OrderBy(a => a.PageOrder)
            .Select(a => a.Url)
            .FirstOrDefault()
    }).ToList();

... as an example. This ensures that we can access a main page while ensuring that the only pages a website considers are ones assigned to it. It is possible to set up an unmapped property on the Website entity to return the "MainAboutPage" from the embedded collection, however I don't recommend using unmapped properties as they can easily slip into Linq expressions and EF will either throw an exception or perform a premature execution (EF Core) to address them.

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