Creating Models in Code
The visual designer is an extremely convenient way to create domain models, and most LightSpeed developers use it for almost all modelling requirements. You can also build a LightSpeed model in code. Even if you use the designer for most tasks you may sometimes want to drop down to the code level, for example to add some logic to a property setter.
In fact, at run time a LightSpeed model is just code. Even if you only use the visual designer, the actual runtime model is the generated code from the designer.
The easiest way to understand LightSpeed coding conventions is to sketch out a model in the designer, then examine the generated code. You’ll see that it follows a very regular pattern, and you can copy that pattern in hand‑written code.
Creating Entity Classes
To create an entity class, define a .NET class which inherits from Entity<TId>. The TId type parameter is the identity type of the class. For example, to define a Customer class whose Id is an integer, write:
public class Customer : Entity<int> |
Entity classes must have a public default constructor. If you don’t specify a constructor, the C# compiler supplies a public default constructor, but if you specify a non‑default constructor, you’ll need to provide a default constructor as well. (See below for Visual Basic considerations.)
The persistent state of an entity is defined by its fields. It’s very important to understand that LightSpeed is interested in fields, not properties! The designer blurs this distinction, because in most entities, every field is wrapped by a property and every property wraps a persistent field. But when you hand‑write code it’s essential to understand it. For example, it means you mustn’t use C# automatic properties, because the backing field for automatic properties is compiler‑generated and will have the wrong name:
public class Customer : Entity<int> |
Instead, you must explicitly create a field with the right name. (LightSpeed also permits the underscore prefix.)
A persistent field in LightSpeed |
public class Customer : Entity<int> |
You can then create a wrapper property so that application code can access the persistent value. The property getter can just return the field value, but the property setter must call the Entity.Set method. The Set method is important because it is how LightSpeed knows that the field has changed. This is essential for knowing that at entity needs to be saved, and to support application interfaces such as IEditableObject and INotifyPropertyChanged.
A wrapper property for a persistent field |
public class Customer : Entity<int> |
You don’t have to provide a wrapper property and LightSpeed won’t care if you don’t. LightSpeed only cares about the field, and about the Set method being used to modify it.
Because LightSpeed cares only about fields, not properties, LightSpeed attributes – for example, validation attributes, or attributes that control the database mapping – must go on fields rather than properties. (The compiler will warn you if you make a mistake.)
Creating Associations Between Entities
In the designer, a one‑to‑many association is represented as a single arrow, which results at the code level in a collection, a backreference and a foreign key property. In code, you need to create all of these fields – and, normally, wrapper properties – explicitly. To represent a collection, use a field of type EntityCollection<T>; to represent an entity reference, use a field of type EntityHolder<T>. (Foreign keys are just normal scalar fields, typically of type int or Guid.)
The collection and holder fields should be marked readonly, and initialised to new collection and holder instances.
Association fields always come in pairs. If the Customer class defines a collection of Orders, then the Order class must define a reference to Customer. These pairs are reverse associations. LightSpeed will throw an exception at runtime if it can’t find the reverse for an association. Furthermore, each EntityHolder<T> must be matched with a foreign key field, whose name is the same as the EntityHolder<T> field followed by Id. For example, if the Order class has a holder named _customer, it must have a scalar field named _customerId.
A one‑to‑many association therefore looks like this in code:
Representing a one-to-many association |
public class Customer : Entity<int> |
When application code accesses an association property, you need to ensure that the association is loaded. To do this, call the Get method. This loads the association – the collection or the associated entity – if required. If the association is already loaded, Get doesn’t do anything. You’ll usually call Get from a property getter.
Application code can update a collection using the Add and Remove methods. To update an entity reference, you must call the Set method. Set updates the contents of the EntityHolder<T> and updates other information such as the foreign key field.
The conventional property wrappers for a one‑to‑many association therefore look like this in code:
Property wrappers for a one-to-many association |
public class Customer : Entity<int> |
Remember, as with simple fields, LightSpeed cares only about fields, not properties, so you’re not forced to follow this pattern or even to expose association properties at all.
One‑to‑one associations are implemented in the same way as one‑to‑many associations, except that you use an EntityHolder<T> at both ends. The usage of the EntityHolder<T> is the same, and there must be a foreign key field at one end.
Normally, LightSpeed can match associations up with their reverse associations because there is only one association between any two classes. If you have multiple associations between the same pair of classes, you must use ReverseAssociationAttribute to pair them up.
public class Customer : Entity<int> |
Many-to-Many Associations
As mentioned above, a many‑to‑many association in LightSpeed is represented by a through association. A through association is implemented in terms of a one‑to‑many association to a through entity and a many‑to‑one association from the through entity to the target entity. For example, suppose you want to model a many‑to‑many association between Contribution and Tag entities. You would need a through entity, which we will call ContributionTag, and one‑to‑many associations from Contribution to ContributionTag and Tag to ContributionTag.
Most through entities contain nothing except the associations to the entities being linked, which are represented as the EntityHolder<T> ends of one‑to‑many associations. As usual the foreign key fields are required as well. So the ContributionTag entity would look like this:
Representing a through entity in code |
public sealed class ContributionTag : Entity<int> |
Conversely, Contribution and Tag each have a reverse association which is one-to-many: each Contribution can have any number of ContributionTags and each Tag can also have any number of ContributionTags. Here’s the relevant fragment of Contribution:
The underlying one‑to‑many association for a through association |
public class Contribution : Entity<int> |
You would define a similar association from Tag to ContributionTag.
Finally, you can now implement the through association. This is a field of type ThroughAssociation<TThrough, TTarget>. The through association needs to be initialised with the EntityCollection representing the one-to-many association from the source entity (Contribution) to the through entity (ContributionTag). To load the through association, call the Get method. As with other associations, this is usually done in the property getter.
Implementing a through association |
public class Contribution : Entity<int> |
Again, the Tag entity will contain similar code for its Contributions through association.
The through entity is a true entity in the model, so you can use it to hang data and behaviour relating to the association itself. For example, to track who applied a particular tag to a particular contribution, you could put an AddedBy field on the ContributionTag entity.
Special Considerations for Visual Basic
A nice feature of .NET is the great interop between components written in different languages. LightSpeed is no exception and can be used just as easily from Visual Basic as from C#.
However, when hand‑coding entities in Visual Basic, there is one minor difference, which is due to Visual Basic classes initialising in a slightly different order from C# classes. The difference is that you must manually call the LightSpeed Initialize method from the entity constructor, as follows:
Writing an entity constructor using Visual Basic |
Public Class Status |
You only need to do this if you are hand‑coding entities. If you use the designer then it is taken care of for you.
Another small wrinkle is that Get and Set, the LightSpeed Entity methods, are reserved words in Visual Basic, and must therefore be escaped with square brackets:
Calling the Get and Set methods from Visual Basic |
Public Property StatusName() As String |
Again, if you use the designer, it will take care of this for you.
Creating Code Models from Database Tables
If you have an existing database schema, and you would prefer to develop your entities in code rather than using the designer, you can use the lsgen command‑line tool to create C# or Visual Basic classes from your database. See the Appendices for instructions on using lsgen.