elsa-workflows / elsa-workflows/elsa-core

[ENH] Add Naming Convention Option for Database Entity Naming

Open
#6,337 9 comments 0 reactions 0 assignees View on GitHub
triaged
Dominant language
C#
Stars
7.9k
Forks
1.5k
Avg merge
15h 22m
Merged PRs (30d)
114

Description

## Enhancement Request

### Enhancement Overview
Database table names and column names default to however they were specified in the migration scripts. On SQL Server this leads to the Quartz tables being all uppercase while Elsa specific tables are all pascal case. On MySQL provider all names are lower case without an underscore making it more difficult to read (i.e. `workflowdefinitions`).

### Proposed Enhancement
Integrate the [EFCore.NamingConventions](https://www.nuget.org/packages/EFCore.NamingConventions) package and expose the various naming conventions offered by the library as configuration chained options on each Elsa DbContext configuration to modify the table and column names:

```
UseSnakeCaseNamingConvention()
UseLowerCaseNamingConvention()
UseCamelCaseNamingConvention()
UseUpperCaseNamingConvention()
UseUpperSnakeCaseNamingConvention()
```

The resulting startup would look similar to this:

```csharp
services.AddElsa(elsa =>
{
var elsaConnectionString = configuration.GetConnectionString(nameof(Elsa))!;
var dbContextOptions = new ElsaDbContextOptions() { MigrationsHistoryTableName = "__ef_migrations_history" };

elsa.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef
.UseMySql(elsaConnectionString, dbContextOptions)
.UseSnakeCaseNamingConvention()));

elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef
.UseMySql(elsaConnectionString, dbContextOptions)
.UseSnakeCaseNamingConvention()));

elsa.UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef
.UseMySql(elsaConnectionString, dbContextOptions)
.UseSnakeCaseNamingConvention()));

elsa.UseAlterations(alterations => alterations.UseEntityFrameworkCore(ef => ef
.UseMySql(elsaConnectionString, dbContextOptions)
.UseSnakeCaseNamingConvention()));

elsa.UseIdentity(identity => identity.UseEntityFrameworkCore(ef => ef
.UseMySql(elsaConnectionString, dbContextOptions)
.UseSnakeCaseNamingConvention()));

elsa.UseIdentity(identity => identity.UseEntityFrameworkCore(ef => ef.UseMySql(elsaConnectionString, dbContextOptions).UseSnakeCaseNamingConvention()));

elsa.UseQuartz(quartz => quartz.UseMySql(elsaConnectionString).UseSnakeCaseNamingConvention());
}
```

### Use Cases
This gives users flexibility to specify the naming convention that should be used throughout their database to match naming conventions of the platform they are using. MySQL for example is snake case but the default behavior for Elsa's MySQL provider is to just make everything lower case and all run together making it more difficult to read.

### Impact of Enhancement
Naming things in the standard the database ensures coding standards and improves the developer experience.

### Visuals and Mockups
Here is an initial implementation that _seems_ close. I got Quartz migration history to respect snake casing of the migration history table by extending `CustomHistoryRepository` and replacing it in DI.

As for the Elsa configurations, neither `feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();` or `feature.Services.ConfigureDbContext(options => options.UseSnakeCaseNamingConvention());` worked. The tables always came out in lowercase and all running together:

![Image](https://github.com/user-attachments/assets/f7ba3a09-9ef9-4a6f-96d4-53e3046c8ab1)

```csharp
///
/// Provides extensions to configure EF Core to use specific naming conventions.
///
public static class EFCoreProvidersNamingConventionExtensions
{
///
/// Configures the to use MySql.
///
public static EFCoreIdentityPersistenceFeature UseSnakeCaseNamingConvention(this EFCoreIdentityPersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static EFCoreAlterationsPersistenceFeature UseSnakeCaseNamingConvention(this EFCoreAlterationsPersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static EFCoreLabelPersistenceFeature UseSnakeCaseNamingConvention(this EFCoreLabelPersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static EFCoreWorkflowDefinitionPersistenceFeature UseSnakeCaseNamingConvention(this EFCoreWorkflowDefinitionPersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static EFCoreWorkflowInstancePersistenceFeature UseSnakeCaseNamingConvention(this EFCoreWorkflowInstancePersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static WorkflowManagementPersistenceFeature UseSnakeCaseNamingConvention(this WorkflowManagementPersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
// feature.Services.ConfigureDbContext(options => options.UseSnakeCaseNamingConvention());
return feature;
}

///
/// Configures the to use MySql.
///
public static EFCoreWorkflowRuntimePersistenceFeature UseSnakeCaseNamingConvention(this EFCoreWorkflowRuntimePersistenceFeature feature)
{
feature.DbContextOptionsBuilder += (_, db) => db.UseSnakeCaseNamingConvention();
return feature;
}

///
/// Configures the to use MySql.
///
public static QuartzFeature UseSnakeCaseNamingConvention(this QuartzFeature feature)
{
feature.Services.ConfigureDbContext(options =>
{
options.UseSnakeCaseNamingConvention()
.ReplaceService();
});

return feature;
}
}

public class CustomHistoryRepository(HistoryRepositoryDependencies dependencies) : MySqlHistoryRepository(dependencies)
{
protected override string TableName => "__ef_migrations_history";
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.