Start modelling your app today.

Get started for free

What's this?

C#Bot Timelines server-side overview

When a CRUD action is performed on an entity with the Timelines extension a timeline event is logged using it’s paired timeline event entity called (EntityName)TimelineEventsEntity.

This article gives an overview of the model changes that happen when the Timelines extension is added to an entity, and how timeline events are logged in the server-side.


Codebots modeling.

This article uses examples and code snippets from an application built using the model shown below on the Codebots platform.

It conatins an entity called Book with the Timeline extension.

C#Bot Timelines Entity

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.

C#Bot Server-side Logic

Controllers and CRUD service

The controller logic and crud service (shown in the black and red blocks in the image above) is unchanged an 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);
        }

Ready to start building?

Sign up to Codebots today to see how much faster you can build apps with us.