Using Natural Keys
LightSpeed adopts the convention that entities are identified by surrogate keys; that is, opaque identifiers with no business meaning, whose sole purpose is to be unique within a table. Because surrogate keys aren’t derived from application data, their actual values are not important, so LightSpeed can assign values using one of the identity methods discussed under Identity Generation in the chapter Controlling the Database Mapping.
Many existing databases don’t use surrogate keys. Instead, they identify entities using a piece of application data which is presumed to be unique, but has business meaning. Such pieces of application data are called natural keys.
Natural keys can be problematic, because as applications and business environments change, application data can turn out to be not as unique, or not as immutable, as originally assumed. So if you have a database which uses natural keys, it’s worth evaluating whether you could change it over to use surrogate keys. Unfortunately, this often isn’t possible for reasons of application compatibility or technical risk.
Assuming you have to use natural keys, this means LightSpeed can no longer assign its own Ids to entities, because the Id has to be derived from the business data in some entity‑specific way. Instead, you must implement your own Id generation logic.
When LightSpeed needs to assign an Id to an entity, it calls the entity’s GeneratedId method. By default, this hands off to the identity method specified in the LightSpeedContext or on the entity.
When you need to use a natural key, you can override GeneratedId to return an item of business data.
protected override object GeneratedId()
LightSpeed maps databases columns to fields. Each column is mapped at most once. Consequently, a natural key column, which is mapped to the implicit Id field, cannot also be mapped to another persistent field. Therefore, assuming you need to store the natural key in the entity while waiting for LightSpeed to call GeneratedId, the temporary field you store it in must be transient.
public partial class Product : Entity<string>
If the _productCode field were not transient, LightSpeed would try to map it to a ProductCode column in the database. If there was no ProductCode column, this would cause an error. If ProductCode was the natural key (Id) column, then that column would now be mapped twice, once to Id and once to _productCode – also an error. (If there was a ProductCode column, independent of the natural key, then there would be no error, but this is unlikely, as it would mean the product key was stored in two places. However, something like this could happen if the natural key were derived from other columns, but not identical to those columns.)
LightSpeed assigns an Id to an entity when that entity first joins a unit of work. Your GeneratedId implementation will be called at this point. Therefore, you must make sure that any business data you need for the natural key is ready before the entity joins a unit of work. Ids are immutable, so you do not get a second chance!
An entity joins a unit of work when you call IUnitOfWork.Add for that entity or any associated entity, or when you associate it with another entity that is already part of a unit of work (by using an association setter or by calling EntityCollection<T>.Add). It is important to remember that entities can be implicitly added to a unit of work through an association.
It is strongly recommended that you initialise the natural key data immediately after creating a new entity. This minimises the chance that you might accidentally add the entity to a unit of work – and thereby trigger a call to GeneratedId – before the natural key data is available. It is also a good idea to put a check, such as a Debug.Assert, in GeneratedId to verify that it is not called before the data is available.
A natural key is never a database autoincrement/identity column. You must therefore never specify the IdentityColumn identity method when using natural keys. Any other identity method will be ignored, because your GeneratedId override takes precedence, but using IdentityColumn will result in an error because LightSpeed performs special handling for it during an INSERT. (Specifically, it prevents LightSpeed sending the Id to the database in the INSERT.)