Say I have an object hierarchy that is supposed to model product categories that are organized into hierarchies, trees to be precise, with leaves as the categories themselves.
We have multiple product category hierarchies, identified by a unique name, for example, there could be a category hierarchy called "By Sex" that is based on product designation by sex (men, women) or a product category hierarchy called "By Region" that is based on designation by country regions:
By Region -> CEE -> [ { Central-Europe -> [ CECountry1, CECountry2, ... ], { Eastern-Europe -> [ EECountry1, EECountry2, ... ] } etc., where By Region is the hierarchy which contains nodes (e.g.: Region1 CEE) and sub-nodes (of the same class) which only hold a ParentId back-reference to their parent node (e.g.: CECountry1, CECountry2, EECountry1, EECountry2), including the root node, which has ParentId = null.
Visual representation:
By Region (hierarchy)
|-- CEE (node)
| |-- CE (node)
| | |-- Central Europe Country 1 (leaf)
| | |-- Central Europe Country 2 (leaf)
| | |-- ... (leaf)
| | |-- ... (leaf)
| |-- EE (node)
| |-- Eastern Europe Country 1 (leaf)
| |-- Eastern Europe Country 1 (leaf)
| |-- ... (leaf)
| |-- ... (leaf)
|-- EMEA (node)
| |-- Europe (node)
| | |-- ... (leaf)
| | |-- ... (leaf)
| |-- Middle East (node)
| | |-- ... (leaf)
| | |-- ... (leaf)
| |-- Africa (node)
| |-- ... (leaf)
| |-- ... (leaf)
...
My entities then look like this:
public class ProductCategoryHierarchy
{
public string Name { get; set; }
public List<ProductCategoryHierarchyNode> Nodes { get; set; }
}
public class ProductCategoryHierarchyNode
{
public string Name { get; set; }
public int HierarchyId { get; set; }
public int? ParentId { get; set; }
}
public class ProductCategory
{
public string Name { get; set; }
public int HierarchyId { get; set; }
public int HierarchyNodeId { get; set; }
}
And I've configured EF like this:
// set up ProductCategoryHierarchy and ProductCategoryHierarchyNode by extension,
// because it's owned by ProductCategoryHierarchy:
public void Configure(EntityTypeBuilder<ProductCategoryHierarchy> builder)
{
// Set up the Nodes property as an owned entity collection, a part of this aggregate:
builder.OwnsMany(hierarchy => hierarchy.Nodes, ownedNode => {
ownedNode.WithOwner().HasForeignKey(node => node.HierarchyId);
ownedNode.ToTable("ProductCategoryHierarchyNode", Schemas.Products);
ownedNode.HasKey(node => node.Id); // otherwise EF would create compound key { Id, HierarchyId } because of OwnsMany
// TODO: Want to add referential integrity for ownedNode.ParentId,
// which references another node of same type,
// but currently not possible for owned type to "have itself"
// or "own itself", it seems?
});
}
// set up ProductCategory:
public void Configure(EntityTypeBuilder<ProductCategory> builder)
{
builder.HasOne<ProductCategoryHierarchy>()
.WithMany()
.HasForeignKey(productCategory => productCategory.HierarchyId);
// now, ProductCategory also has a HierarchyNodeId field but I cannot
// map it here because referencing ProductCategoryHierarchyNode would
// trigger an EF error, because that entity is already owned by another.
}
There are basically 2 things I am missing from the current configuration.
One is that I cannot seem to be able to have the ownedNode above reference "itself", or an object of the same type as itself. That is because, for example if I try this:
ownedNode.HasOne<ProductCategoryHierarchyNode>()
.WithMany()
.HasForeignKey(n => n.ParentId);
then EF complains:
The relationship from 'ProductCategoryHierarchyNode' to 'ProductCategoryHierarchyNode' is not supported because the owned entity type 'ProductCategoryHierarchyNode' cannot be on the principal side of a non-ownership relationship. Remove the relationship or configure the foreign key to be on 'ProductCategoryHierarchyNode'.
The second problem is similar, but it's on ProductCategory, where I can have a HierarchyNodeId field that should point to the hierarchy node under which the category is enlisted. For example: By Region -> CEE -> Central-Europe -> Country1, Country2, etc., these countries are instances of ProductCategory, By Region is an instance of ProductCategoryHierarchy and CEE is an instance of ProductCategoryHierarchyNode. Here, it seems there no way to make ProductCategory.HierarchyNodeId a FK to ProductCategoryHierarchyNode because ProductCategoryHierarchyNode is owned by ProductCategoryHierarchy and we can no longer map it in another enity.
I'll be working on an MRE soon, so please excuse me for now. If you have any ideas or have gone through this before and know that it's not possible to express, except by customizing the generated migration script, please tell me.