Persistence Guide
Tlabs.Data
The Tlabs.Data module provides a series of interfaces and models with the goal of abstracting the persistence layer on a .Net application.
This facilitates the isolation of a given project from the actual underlying persistence technology. For more information about the flexibility refer to the configuration section.
The most important interfaces provided by this module are:
Interfacek |
Description |
---|---|
IDataStore |
Interface of an abstract data persistence store. |
ISerializer |
Interface of a serializer of objects of type T. |
IDynamicSerializer |
Interface of a serializer of objects of type only known in runtime. |
IDataTransaction |
Abstraction of a data transaction. |
IRepo |
Interface of a repository of objects of type T. |
Main concepts
Entities
A software application that works with data, especially if this data is persisted in some manner, will have an underlying data model. A data model is an abstract model that organizes elements of data and standardizes how they relate to one another and to the properties of real-world entities 1. This data model is defined by so called Entity types, which are classes defining the software representation of something that usually has an unique and separate existence.
These entities will get aligned with database structures via an ORM.
Which ORM will be used is to be decided depending on the particular
implementation of the TLabs.Data
interfaces. See below for the
description of the Tlabs.EfDataStore
which makes use of
[EntityFramework](Core
https://learn.microsoft.com/en-us/ef/core/
)
Entities just objects of a given type, which might get read and writen on the persistent storage. Depending on the underlying ORM library used to implement the data store logic, some configuration will be needed to map these entities into actual storage classes.
BaseEntity
The Tlabs.Data
package provides a BaseEntity
model class which
implements some common functionality for any entity, like equality, and
an Id field.
EditableEntity
Extends the BaseEntity
class with an Editor
identifier and a
Modified
DateTime
field. As well as the functionality of
automatically setting thosse when the model gets Inserted or deleted.
Tracked vs Untracked entities
Changes on entity objects can be tracked for potential commit onto the underlying persistence layer.
The IRepo
interface offers two ways of entity retrieval, All
to
retrieve tracked entities, and AllUntracked
which returns an
enumeration of untracked entities.
It is important to only use tracked entities in case potential changes are expected to be done (and commited) on them. As tracking is expensive and it will otherwise reduce the performance of operations performed on the entities.
Querying
Querying data is typically performed using the IRepo<T>
repository
abstraction.
This repo allows retrieving a queryable enumeration of all persisted entities, which can then be queried via the linq query language (see https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/ )
Example:
/* ... */
IRepo<MyEntityType> repo;
public ClassCostructor(IRepo<MyEntityType> repo) {
this.repo= repo;
}
IList<MyEntityType> ListMethod() {
repo.AllUntracked.Where()
}
Loading related entity data
The IDataStore
interface provides the LoadRelated<E>
and
ThenLoadRelated<E>
methods, which allow loading the data of a related
entity data associated with a given navigation property.
This method can be chained to load multiple navigation properties.
Example:
/* ... */
MyEntityType QueryMethod(string Name)
=> repo.All
.Where(r => r.Name == name)
.LoadRelated(Store, r => r.RelatedEntity1)
.LoadRelated(Store, r => r.RelatedEntity2)
.ThenLoadRelated(Store, re2 => re2.AnotherRelatedEntity) // This relation sits on the `RelatedEntity2`
.SingleOrDefault();
Saving data (commiting changes)
Entity modifications won't be automatically persisted per default. To
commit tracked changes on the storage, the CommitChanges()
method of
IRepo
must be called.
/* ... */
void ModifierMethod(string id) {
var ent= repo.All.Single(x=> x.Id == id); # Retrieval of a single (tracked) entity
ent.Name = "XXXX"; # Modification of some property
repo.CommitChanges(); # Committing (persistent saving) of the tracked changes
}
In case for some reason you are dealing with an untracked entity, it is possible to tell the store to track it on demand.
/* ... */
repo.Attach(untrackedEntity); # Mark the untrackedEntity object to be watched for changes
ent.Name = "XXXX"; # Modification of some property
repo.CommitChanges(); # Committing (persistent saving) of the tracked changes
If an entity object was just manually created, it won't be tracked. So the typical use case of creating and persisting an entity works like follows:
/* ... */
void CreateEntity(string name, string otherField) {
var ent= new MyEntity(); # Entity creation (not yet tracked)
ent.Name= name; # Modification of some properties
ent.OtherField= otherField;
repo.Insert(); # Mark ent for inserting in the store (will be tracked from now on)
repo.CommitChanges(); # Committing (persistent saving) of the tracked changes
}
It is also possible to force an update of an entity even if it's not tracked with the
repo.Update(entity)
. This will mark the entity to be updated on the next commit. And all of its field values will be overwritten.
Tlabs.EfDataStore
Tlabs.EfDataStore
offers a concrete implementation of the IDataStore
interface using [EntityFramework](Core
https://learn.microsoft.com/en-us/ef/core/
)
Configuring the model
In EF Core. After our DB Model has been defined (in form of entities),
the actual mapping to DB tables must be configured. That can be
performed implementing the IDstoreConfigModel
.
For more information about the way to configure a model on EF Core visit: https://learn.microsoft.com/en-us/ef/core/modeling/#use-fluent-api-to-configure-a-model
Configuring the DB server
It is needed to configure a suitable DB Server to act as a persistent storage. For that, a database provider must be setup, for more information on the available ef-core database providers see: https://learn.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli
For more information on configuration of the DB Server see the
Documentation of the DbServerConfigurator
class.
Handling model changes
Info: The dotnet-ef CLI tool must be installed in order to perform some of design-time development tasks like creating migrations, applying them, and so on. For more info: https://learn.microsoft.com/en-us/ef/core/cli/dotnet
dotnet tool install --global dotnet-ef
Model changes that happen during the application development must be reflected on the persistent DB structure. EF Core Migrations are there to handle these model changes.
After each change on the model, either when directly changing the
definition of the model classes, or its code-first mappings on the
IDStoreConfigModel
implementation, a new migration must be created,
which will be used to apply that changes onto the database tables.
For more information on that regard see: https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/