Table of Contents

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()
}

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/