The documentation comes from the Markdown files in the source code, so is always up-to-date but available only in English. Enjoy!
Inheritances (and interface implementation) is maybe the most popular feature of Object Oriented Programming since encapsulation is for paranoid people and nobody understands polymorphism :) and it is the one that has more difficulties to map to the relational world.
Signum Framework approach is a very practical one. We put all the responsibility of inheritance mapping on Polymorphic Foreign Keys because they allow three different scenarios, all of them very important:
SchemaBuilderSettings
allow us to connect different modules (from the UI to the DB) in a different assembly and integrate them easily.At the end of this page you could read a bit more about why we ended up implementing inheritance in this way.
So Inheritance is all about Polymorphic Foreign Keys (PFK). What the hell are they???
A PFK is just FK that could point to entities of different types. We have them in two flavors:
This FieldAttributes take a params Type[]
as constructor, what it does is to allow the field to have objects of any of the defined types. Let's supose we have a PlayerEntity
entity with the following field:
[ImplemetedBy(typeof(RevolverEntity), typeof(BazookaEntity), typeof(MachineGunEntity)]
IWeapon weapon;
The actual implementation in the database is just multiple foreign keys, each one with different tables of each mapped type (RevolverEntity
, BazookaEntity
, MachineGunEntity
). Due to that, the types should be:
Entity
(they need their own table)IWeapon
).When there are many common fields in the different implementations of an ImplementedBy
field (in this example, if RevolverEntity
, BazookaEntity
and MachineGunEntity
share many fields, declared in IWeapon
) writing polymorphic foreign key can be quite slow. For example:
Database.Query<PlayerEntity>().Select(p=>p.Weapon.Ammunition > 0)
This query will need to join with the three different implementations and coalesce each of the three Ammunition columns. This case is even worst:
Database.Query<PlayerEntity>().Select(p=>p.Weapon.Provider.Name)
In this case, the ProviderEntity
table will be joined with the three different tables, creating a slow join due to the use or ORs
.
In our experience, the best idea many times is to fusion all the common fields in a single class (WeaponEntity
) with an field of type WeaponExtensionEntity
implemented by RevolverWX
, BazookaWS
and MachineGunWS
containing the different fields.
When this is not an option, use CombineStrategyAttribute
on the ImplementedByField
, or use CombineSwitch
and CombineUnion
extensions method on each particular query could help you tuning the performance using SQL SWITCH
(default) or UNION
in each case.
This FieldAttributes, instead of mapping a finite number of Types and creating this number of FK in the database, assumes that almost 'every entity' could fit in this field.
[ImplemetedByAll] // there are too many kinds of weapon in the world to enumerate it...
Entity weapon;
The implementation in the database uses just two columns:
TypeEntity
table.Think of TypeEntity
as Signum Engine's equivalent to System.Type. It's a table containing a row for each concrete Entity
included in the schema.
That's all you need to know about Inheritance in Signum Engine.... unless you want to know more :).
You can use PFK with Lite seamlessly. In fact, the whole reason Lite are covariant is to support these kind of scenarios.
[ImplemetedBy(typeof(RevolverEntity), typeof(BazookaEntity), typeof(MachineGunEntity)]
Lite<IWeapon> weapon;
The ability of using SchemaBuilderSetting
to override attributes on entities that you don't control lets you integrate different modules in a type-safe and elegant way. Take a look in Schema.
Nothing to learn. Saving and retrieving entities with PFK it is just transparent.
We support polymorphic foreign keys in queries also.
ImplementedBy
references and ImplementedByAll
references in any combination!!Given the next simple hierarchy:
public abstract class PersonEntity : Entity
{
string name;
(..)
}
public class SoldierEntity : PersonEntity
{
WeaponEntity weapon;
(..)
}
public class TeacherEntity : PersonEntity
{
BookEntity book;
(..)
}
There are different ways of persisting with inheritance hierarchies, using NHibernate's terminology:
PersonEntity {Id, ToStr, Discriminator, Name, idWeapon, idBook, }
. Every Soldier and Teacher goes to the this table using Discriminator values {'S', 'T'}
to differentiate between them. This approach has some problems:TeacherEntity
.PersonEntity { Id, ToStr, Name }
, SoldierEntity{idPerson, idWeapon}
and TeacherEntity{idPerson, idBook}
. Problems:Id
column in SoldierEntity
(or TeacherEntity
),then ArmyEntity
and SchoolEntity
will have the same problems for referencing concrete classes.Id
column on SoldierEntity
(or TeacherEntity
), you will have two different Ids: For a soldier idPerson
and idSoldier
, and for a teacher idPerson
and idTeacher
. This creates ambiguities.SoldierEntity { id, ToStr, Name, idWeapon }
and TeacherEntity { id, ToStr, Name, idBook }
. Since PersonEntity
is an abstract class there's no point in having its own table. Problems:When we designed inheritances in our framework we went just for the option #3 because it is the simplest.
With #1 and #2 you need to add a 'hierarchy concept' in the framework, something that embraces the three classes and puts them together in the same table (#1) or in the same table hierarchy (#2).
Since interfaces allow some kind of multiple inheritance, the same entity could potentially be part of different hierarchies, and this is not suported by #1 or #2.
The algorithm to know where an entity actually resides becomes more complex with hierarchies and we also avoid the type mismatch of solution #1.
However, the main reason for going for the PFK-only solution is that all these complex solutions solve only the first initial (Saving Entity Hierarchies), while PFK also allows solve the 2nd problem (reference to anything) and 3rd problem (connect modules), that are almost as useful as the first one.
© Signum Software. All Rights Reserved.
Powered by Signum Framework