C#Bot Timelines server-side overview
Codebots modeling
This article uses examples and code snippets from an application built using the model shown below on the Codebots platform.
It contains an entity called Book
with the Timeline extension
.

Server-side model changes
Each entity with a timeline extension
will have a paired (EntityName)TimelineEventsEntity.cs
model file written out into the server-side. This paired (EntityName)TimelineEventsEntity
is not visible in the Codebots platform model. The two entities are paired through one-to-many association (one (EntityName)Entity has many (EntityName)TimelineEventsEntity). The (EntityName)TimelineEventsEntity
is an entity that contains a timeline event.
Server-side model for entity with Timeline extension.
File:
/serverside/src/Models/(EntityName)/(EntityName)Entity.cs
Overview:
An Entity with a Timeline Extension will contain an ICollection
of (EntityName)TimelineEventsEntity
where the events are contained.
/// <summary>
/// Incoming one to many reference
/// </summary>
/// <see cref="Timelinesharp.Models.BookTimelineEventsEntity"/>
[EntityForeignKey("LoggedEvents", "Entity", false, typeof(BookTimelineEventsEntity))]
public ICollection<BookTimelineEventsEntity> LoggedEvents { get; set; }
Additionally, the Entity method BeforeSave()
will contain additional Timeline specific logic which is discussed later in the article.
Server-side model for (EntityName)TimelineEventsEntity
File:
/serverside/src/Models/(EntityName)/(EntityName)TimelineEventsEntity.cs
Overview:
The (EntityName)TimelineEventsEntity
implements the interface ITimelineEventEntity
. The interface forces the (EntityName)TimelineEventsEntity
to have the properties:
- Description: A description of the event.
- Action: The action that was performed i.e. Created.
- ActionTitle: Longer version of Action used for frontend display.
- EntityId: Foreign key to the entity the event belongs to.
- GroupId: Copy of the foreign key to use when the Entity that EntityId points to is deleted.
If you would like to store additional meta-data about a logged event, add your property to the
(EntityName)TimelineEventsEntity
in one of the protected regions.
public class BookTimelineEventsEntity : IOwnerAbstractModel, ITimelineEventEntity {
[Key]
public Guid Id { get; set; }
public Guid Owner { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
/// <summary>
/// The action taken
/// </summary>
// % protected region % [Customise Action here] off begin
[EntityAttribute]
public String Action { get; set; }
// % protected region % [Customise Action here] end
/// <summary>
/// The title of the action taken
/// </summary>
// % protected region % [Customise ActionTitle here] off begin
[EntityAttribute]
public String ActionTitle { get; set; }
// % protected region % [Customise ActionTitle here] end
/// <summary>
/// Decription of the event
/// </summary>
// % protected region % [Customise Description here] off begin
[EntityAttribute]
public String Description { get; set; }
// % protected region % [Customise Description here] end
/// <summary>
/// Id of the group the events belong to
/// </summary>
[Required]
// % protected region % [Customise GroupId here] off begin
[EntityAttribute]
public Guid? GroupId { get; set; }
// % protected region % [Customise GroupId here] end
// % protected region % [Add any further attributes here] off begin
// % protected region % [Add any further attributes here] end
/// <summary>
/// Outgoing one to many reference
/// </summary>
/// <see cref="Timelinesharp.Models.BookEntity"/>
public Guid? EntityId { get; set; }
[EntityForeignKey("Entity", "LoggedEvents", false, typeof(BookEntity))]
public BookEntity Entity { get; set; }
Server-side logic for creating Timeline events

Controllers and CRUD service
The controller logic and crud service (shown in the black and red blocks in the image above) is unchanged and works like normal. A controller (graphql or rest) receives a request to perform a CRUD action on an entity. The crud service will perform the action and call the BeforeSave()
method on each entity that has had a CRUD action performed. BeforeSave()
is called in each of the CrudService
CRUD methods Create()
Update()
and Delete()
.
BeforeSave()
File:
/serverside/src/Services/CrudService.cs
BeforeSave()
is a method on every modelled entity. You can find the method in the entity model file found at /serverside/src/Models/(EntityName)/EntityName.cs. The method is called be the CrudService
before a model is saved to the database.
If the entity has a timeline extension
, the BeforeSave()
method will contain timeline specific logic. Shown in the code below.
The code executes several if
statements to check what type of CRUD operation is being performed, and will call either:
-
CreateTimelineCreateEventsAsync()
If the entity is being created. -
CreateTimelineEventsAsync()
If the entity is being updated. -
CreateTimelineDeleteEventsAsync()
If the entity is being deleted.
public async Task BeforeSave(
EntityState operation,
TimelinesharpDBContext dbContext,
IServiceProvider serviceProvider,
CancellationToken cancellationToken = default)
{
// Create timeline event when an entity is added
if (operation == EntityState.Added)
{
await CreateTimelineCreateEventsAsync(dbContext, serviceProvider, cancellationToken);
}
// Create a timeline event when an entity is modified
if (operation == EntityState.Modified)
{
var original = await dbContext.BookEntity
.AsNoTracking()
.Where(x => x.Id == Id)
.FirstOrDefaultAsync(cancellationToken);
await CreateTimelineEventsAsync(original, dbContext, serviceProvider, cancellationToken);
}
// Create a timeline event when the entity is going to be deleted
if (operation == EntityState.Deleted)
{
await CreateTimelineDeleteEventsAsync(dbContext, serviceProvider, cancellationToken);
}
// % protected region % [Add any before save logic here] off begin
// % protected region % [Add any before save logic here] end
}
CreateTimelineCreateEventsAsync()
File:
/serverside/src/Models/(EntityName)Entity/(EntityName)Entity.cs**
This method will add a new (EntityName)TimelineEventsEntity
to the List<(EntityName)TimelineEventsEntity>
being added to the database to log timeline events by calling AddCreateEvent()
on the list and passing in parameters for the entity name and Id.
You can customise how
Create
timeline events are created here for a specific entity**.
public async Task CreateTimelineCreateEventsAsync(
TimelinesharpDBContext dbContext,
IServiceProvider serviceProvider,
CancellationToken cancellationToken = default)
{
var timelineEvents = new List<ITimelineEventEntity>();
timelineEvents.AddCreateEvent<BookTimelineEventsEntity>("BookEntity", Id);
await dbContext.AddRangeAsync(timelineEvents, cancellationToken);
}
AddCreateEvent()
File:
/serverside/src/Helpers/TimelineHelper.cs**
This is a generic method used to add a create event to a list of ITimelineEvents
.
You can customise how
Create
timeline events are created here for all entities.
public static void AddCreateEvent<T>(
this List<ITimelineEventEntity> descriptionList,
string entityName,
Guid id)
where T : ITimelineEventEntity, new()
{
var description = CreateCreateDescription(entityName);
descriptionList.AddEvent<T>("Created", $"Created {entityName}", description, id);
}
CreateTimelineEventsAsync()
File:
/serverside/src/Models/(EntityName)Entity/(EntityName)Entity.cs**
This method will be called when an entity is updated. It will run ConditionalAddUpdateEvent()
over each attribute on the entity, which will add a new (EntityName)TimelineEventsEntity
to the list of (EntityName)TimelineEventsEntity
which will be added to the database.
You can customise how
Update
timeline events are created here for a specific entity.
public async Task CreateTimelineEventsAsync<TEntity>(
TEntity original,
TimelinesharpDBContext dbContext,
IServiceProvider serviceProvider,
CancellationToken cancellationToken = default)
where TEntity : IOwnerAbstractModel
// % protected region % [Override CreateTimelineEventsAsync method here] end
{
// % protected region % [Override CreateTimelineEventsAsync type check here] off begin
if (!(original is BookEntity originalEntity))
{
return;
}
// % protected region % [Override CreateTimelineEventsAsync type check here] end
var timelineEvents = new List<ITimelineEventEntity>();
// % protected region % [Override CreateTimelineEventsAsync 'Title' case here] off begin
timelineEvents.ConditionalAddUpdateEvent<BookTimelineEventsEntity>(
"BookEntity",
"Title",
originalEntity.Title,
Title,
Id);
// % protected region % [Override CreateTimelineEventsAsync 'Title' case here] end
// % protected region % [Override CreateTimelineEventsAsync 'Price' case here] off begin
timelineEvents.ConditionalAddUpdateEvent<BookTimelineEventsEntity>(
"BookEntity",
"Price",
originalEntity.Price,
Price,
Id);
// % protected region % [Override CreateTimelineEventsAsync 'Price' case here] end
// % protected region % [Add any further timeline update events here] off begin
// % protected region % [Add any further timeline update events here] end
// % protected region % [Override CreateTimelineEventsAsync database call here] off begin
await dbContext.AddRangeAsync(timelineEvents, cancellationToken);
// % protected region % [Override CreateTimelineEventsAsync database call here] end
}
ConditionalAddUpdateEvent()
File:
/serverside/src/Helpers/TimelineHelper.cs**
This is a generic method to add a new update event to a list of ITimelineEventEntity
. It accepts old and new value of an attribute on an entity, and will only add a new ITimelineEventEntity
if the new value is different to the old value.
You can customise how
Update
timeline events are created here for all entities.
public static void ConditionalAddUpdateEvent<T>(
this List<ITimelineEventEntity> descriptionList,
string entityName,
string attributeName,
object originalValue,
object newValue,
Guid id)
where T : ITimelineEventEntity, new()
{
if (!Equals(newValue, originalValue))
{
var description = CreateUpdateDescription(
attributeName,
originalValue?.ToString() ?? "undefined",
newValue?.ToString() ?? "undefined");
descriptionList.AddEvent<T>("Updated", $"Updated {entityName}", description, id);
}
}
CreateTimelineDeleteEventsAsync()
File:
/serverside/src/Models/(EntityName)Entity/(EntityName)Entity.cs**
This will add Delete
timeline events are work the same way as CreateTimelineCreateEventsAsync()
except that AddDeleteEvent()
is called in place of AddCreateEvent()
You can customise how
Delete
timeline events are created here for a specific entity.
public async Task CreateTimelineDeleteEventsAsync(
TimelinesharpDBContext dbContext,
IServiceProvider serviceProvider,
CancellationToken cancellationToken = default)
{
var timelineEvents = new List<ITimelineEventEntity>();
timelineEvents.AddDeleteEvent<BookTimelineEventsEntity>("BookEntity", Id);
await dbContext.AddRangeAsync(timelineEvents, cancellationToken);
}
AddDeleteEvent()
File:
/serverside/src/Helpers/TimelineHelper.cs**
This is a generic method used to add a delete event to a list of ITimelineEvents
.
You can customise how
Delete
timeline events are created here for all entities.
public static void AddDeleteEvent<T>(
this List<ITimelineEventEntity> descriptionList,
string entityName,
Guid id)
where T : ITimelineEventEntity, new()
{
var description = CreateDeleteDescription(entityName, id);
descriptionList.AddEvent<T>("Deleted", $"Deleted {entityName}", description, id);
}
Was this article helpful?