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.
I.e. 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.