Concepts - O/R Mapping
Preface
O/R Mapping, or Object Relational Mapping, is the general term for
the concept of creating mappings between tables / views and their fields, and OO classes and their fields to be
able to represent a table or view row in an OO program via a class. O/R Mapping is done using entities and
their attributes (which are physically available through table or view definitions) and by creating a class for
each entity, mapping each field in that entity class onto a field in a table or view. The management logic necessary
to read an entity's data from the database into an instance of its entity class and back, together with
the entity definitions, is called an O/R Mapper framework. Via this framework a developer can manipulate
data in the database, using classes and their methods.
LLBLGen Pro's O/R Mapping
O/R Mapping is not new, for example in the Java world it is a common technique to work with data in a database. For
developers using Microsoft platforms and .NET in particular, O/R Mapping is rather new and often mis-understood.
Most Microsoft oriented developers think in tiers, tables and raw SQL statements and look for code generators for these tiers, completely
neglecting the term O/R Mapping and the tool utilizing this technique. As with all techniques used by
developers on a variety of platforms, O/R Mapping has a list of definitions which are not always the same.
To be clear how O/R Mapping is utilized in the LLBLGen Pro system, this section defines O/R Mapping for
LLBLGen Pro. It by no means claims to be the 'correct' version of the definition of an O/R Mapping framework,
but it is a usable definition for LLBLGen Pro users.
Mapping with entities
Because LLBLGen Pro uses the relational model oriented definition of an entity, mapping of classes on tables / views
is simply done by creating an entity definition for every table / view definition selected by the user,
and an entity field definition for every table / view field in these selected tables / views. This concept is extended by offering the ability to
specify that an entity is a subtype and/or a supertype, which results in an entity being mapped onto more than one table / view (its own table / view and
all the tables / views its supertype (and that entity's supertype etc.) are mapped on). For more information about this concept which is called 'Inheritance',
please see
Entity inheritance and relational models.
LLBLGen Pro allows you to map more than one entity definition onto the same table / view, and / or partly map an entity definition onto its table / view.
Entity classes generated using these entity definitions contain all logic necessary to read an
entity's data from the database (persistent storage) or send back modified data to the persistent storage.
You can see the instance of an entity class as the in-memory representation of a table / view row's data, and the
methods exposed by the instance of the class as the tools to work with these data. Derived entities, or subtypes, are generated as
derived classes
and simply inherit the supertype's fields, logic and methods, overriding properties/methods where appropriate. A derived entity type therefore contains
the supertype's fields together with its own fields. See for more details
Entity inheritance and relational models.
LLBLGen Pro doesn't use an in-memory object cache, which means that the generated code and the runtime
library targeted by the generated code, are designed to work with entities directly in the persistent
storage (database). In-memory equivalents of data in the persistent storage (i.e. the entity objects you instantiate
when you instantiate an entity class and fill it with its data from the persistent storage), will be
kept in sync, if possible. However, code using the entity classes shouldn't rely on that, because .NET
doesn't have a way to know when a database row has changed and thus when an entity object in-memory should
be re-synced with the database. LLBLGen Pro defines a
context which can be used to create unique objects per entity row in the database.
Often it is not necessary to create unique objects per entity data however in the situations you need it, the context class is a way to
make sure an entity is loaded in just one entity object for that particular context. See
Using the context for
more detailed information about how to use this in your code.
The entity classes generated give you the ability to work with data in
the persistent storage in a typed, friendly way, however they don't contain logic often seen in Java O/R mappers,
like an application wide object cache. The reason for this is that .NET doesn't have a system like
Enterprise Java Beans (EJB) which does the persisting for you and keeps track of any changes, even in a
distributed application spanning more than one machine.
This is not a limitation however, since working with entities as they are defined by LLBLGen Pro is a matter
of data manipulation in the persistent storage and to do that with close ties to that persistent storage,
multiple users, using multiple application or threads, targeting the same database, can work with the
same data without problems, because the data manipulations are designed to be targeted directly to the
persistent storage. Read more about this topic in
Stateless persistence.
An entity's lifecycle
To get more familiar with the terms, the way LLBLGen Pro handles things and the way terms relate to each other, the
following list is defined to illustrate when what is done and why. As an example, the entity definition
'Customer' is used which targets the table 'Customer'. A new 'customer' is added, with the primary key 'SOLDES',
which is the CustomerID. For simplicity, SelfServicing is used to illustrate the lifecycle. For Adapter, it is similar, the developer uses
different methods/classes to achieve the same goal(s).
- Entity class instantiation. This action is the start of a new entity's life. You will create
a new instance of a given entity's class, in this case the CustomerEntity class as we're discussing the lifecycle of a customer entity.
The object you will have after instantiation is an empty object, specialized for Customer entities. This can be as simple as
this statement:
CustomerEntity customer = new CustomerEntity(); // (C#)
or
Dim customer As New CustomerEntity() ' (VB.NET)
The object 'customer' is now the instantiation of a new Customer Entity class, but it doesn't have
any values yet, and it is only known in memory, not in the persistent storage. Other users
or threads in the application can't see the object.
- Filling the Entity object with data. This action fills the entity object with its data.
In this case, you will specify values for the 'customer' object's properties, for example it's
CustomerID (if that isn't generated by the database) and CompanyName. When the properties are filled, the object no longer is an
empty bucket, but is the holder of the data of a new
entity: the entity which represents the customer who's data is stored inside the object.
- Persisting the Entity. Persisting means that the entity's data, which is held in memory
by an instance of an entity class, is written back to the persistent storage. When the entity
is new, like our customer in this example, persisting means that a new row will be added
to the table (or tables, through the view the entity is mapped on if the RDBMS supports updatable views) which definition represents the
entity definition (i.o.w. the target table / view of the entity definition, in this case the Customer entity definition, which targets the Customers
table). When an entity is not new, its row in the persistent storage is updated with changed
information held by the object in memory. Persisting entities in the LLBLGen Pro generated code
simply requires calling the entity object's Save() method.
The persistence logic will figure out if it has to insert the entity data as a new entity or update its row in the persistent storage (database).
Once an entity is persisted to the persistent storage, it is available in the database, and thus
can and will be seen by other threads and users targeting the same database. (Transactional, database
specific functionality can keep an entity hidden until a transaction is finished, however for
the example, we assume no transaction is issued, just a single entity is persisted)
- Modify an entity field. After the entity is persisted, other users can
read the entity's data via other threads, by instantiating the entity back into memory, their memory. This
is for example done on another machine. To read back the entity from the database into memory, a developer
can use the following statement:
CustomerEntity customer = new CustomerEntity("SOLDES"); // (C#)
or
Dim customer As New CustomerEntity("SOLDES") ' (VB.NET)
When these statements are completed, the Customer Entity represented by the CustomerID (which is
the primary key) with the value "SOLDES" is read into memory in a new instance of the Customer Entity
class, and that instance is called 'customer'. The developer can then change one or more of this entity's
values by altering the values of 'customer's properties. Also, the developer can traverse relations
the Customer Entity has with other entities, for example ask for a collection of all customer's orders
by simply referring to the 'Orders' property this entity object might have, because the entity definition has a relation with
the Order entity. The developer changes the value for CompanyName into "Solutions Design" and persists
the entity back to the persistent storage by using these statements:
customer.CompanyName = "Solutions Design"; // (C#)
customer.Save();
or
customer.CompanyName = "Solutions Design" ' (VB.NET)
customer.Save()
After the Save() method has been called, the entity's new data is available in the database and can be
seen and used by other users and threads.
- Deleting an entity. Everything ends, and so is the relationship with this customer. The developer
wants to remove the entity completely from the system. This is simply done by calling the Delete()
method exposed by the customer object. When the Delete() method is called, and the deletion was
successful, the entity itself is removed from the persistent storage, and the entity's data holding
object, the customer object which Delete() method was called, is cleared. Now the entity is not
available anymore to others. Also, using the in-memory object after a Delete() statement will
result in an exception. The only thing left is letting the 'customer' object go out of scope so
the .NET Garbage Collector can clean up the bits that once formed the object holding the SOLDES entity.