I've taken this as a challenge of "can such a relationship be built/enforced in the database" and finally found a way to do so. However, I'd seriously recommend not doing so - it's going to be ugly. And I'd certainly not expect Entity Framework to be able to do anything intelligent with it.
What we can do is to model the sets of children explicitly. We create a special empty set and then every other set has to derive from a smaller set by adding a new element that is larger than the current largest element in that set. By adding appropriate uniqueness constraints, we can then ensure that every set can only be derived in exactly one way - and if each set than has an optional Parent
, there can only ever be one parent for a particular set.
And so here's the database table:
create table Sets (
SetID uniqueidentifier not null
constraint DF_Set_IDs DEFAULT (newsequentialid()),
SubSetID uniqueidentifier null,
ParentID int null,
ElementValue int null,
SubsetElement int null,
constraint PK_Set PRIMARY KEY (SetID),
constraint UQ_Set_XRef UNIQUE (SetID,ElementValue),
constraint CK_Set_Empty CHECK (
(SetID = '00000000-0000-0000-0000-000000000000'
and ElementValue is null and SubSetID is null) or
(SetID <> '00000000-0000-0000-0000-000000000000'
and ElementValue is not null and SubSetID is not null)
),
constraint CK_Set_LargerElement CHECK (
SubsetElement < ElementValue
),
constraint FK_Set_SubSet FOREIGN KEY (SubSetID)
references Sets (SetID),
constraint FK_Set_SubSet_XRef FOREIGN KEY (SubSetID,SubsetElement)
references Sets (SetID,ElementValue),
constraint CK_Empty_Subsets CHECK (
(SubSetID = '00000000-0000-0000-0000-000000000000'
and SubsetElement is null) or
(SubSetID <> '00000000-0000-0000-0000-000000000000'
and SubSetElement is not null)
),
constraint UQ_SubSet_And_Current UNIQUE (SubSetID,ElementValue),
constraint FK_Set_Parent FOREIGN KEY (ParentID) references Parents (ParentID)
)
And then we also want a filtered unique constraint to ensure a parent only "owns" one set:
create unique index UQ_OwnedSetParents on Sets (ParentID)
where ParentID is not null
So, let's consider each constraint in turn and what it brings to the party:
PK_Set
- primary key of this table. I've made the column a uniqueidentifier
primarily so that the only int
s appearing in this table relate to the numbers present in sets.
UQ_Set_XRef
- a "superkey" across both the actual primary key and the ElementValue
value, allowing a foreign key to check both columns if necessary.
CK_Set_Empty
- We want exactly one empty set, so we pick a special value for that row's SetID
and then ensure that it's the only set that gets to have a NULL
ElementValue
and to not be based on a subset.
CK_Set_LargerElement
- We want to ensure that as we build up sets, each set's ElementValue
is larger than the largest value in the subset it's based upon. By induction, the largest element in the subset was the Subset's ElementValue
, of which more follows.
FK_Set_SubSet
- A foreign key linking this set to a subset it is based upon
FK_Set_SubSet_XRef
- this foreign key ensures that the value we have in SubsetElement
was the ElementValue
for that subset row.
CK_Empty_Subsets
- this check is needed because foreign key references aren't checked if one of the columns contains a NULL
- so we have this check to backup FK_Set_SubSet_XRef
and to make sure that the empty set is correctly dealt with.
UK_SubSet_And_Current
- this ensures that for any given subset, at most one set builds on it using a particular element value - this is what finally makes each set uniquely representable
FK_Set_Parent
- an anticlimax at this point - the set is optionally owned by a parent
UQ_OwnedSetParents
- and this ensures that each parent only "owns" at most one parent.
As you can probably see, operating on this table is going to be somewhat complex. To enumerate the members of a set, you need to visit the specific set and the continue to access the Subset's on which each is built until you reach the empty set.
Adding a new element to a set is straightforward if it's larger than any existing element. Adding a new element that is smaller is considerably more complex, especially if some of the subsets are owned by other parents.