This document explains the Entity Framework Core configuration system in FlexBase applications, using the EBusiness sample as a reference. The EF Core configuration provides a flexible, multi-database, multi-tenant data access layer that supports SQL Server, MySQL, and PostgreSQL with both single-tenant and multi-tenant architectures.
Architecture Overview
The EF Core configuration in FlexBase follows a layered architecture with separate projects for different database providers and tenant configurations. This allows for seamless transitions between different database systems and tenant models.
Each project references specific FlexBase EF Core packages:
Configuration Selection Logic
DbEndPointConfig.cs - Database and Tenant Configuration
The DbEndPointConfig.cs file contains the logic for selecting the appropriate database configuration:
Single Tenant vs Multi-Tenant Architecture
Single Tenant Architecture
Definition: A single tenant architecture means that each customer (tenant) has their own dedicated database instance. All data for a tenant is completely isolated from other tenants.
Characteristics:
Data Isolation: Complete physical separation of tenant data
Performance: No cross-tenant performance impact
Security: Highest level of data security and isolation
Scalability: Each tenant can scale independently
Cost: Higher cost due to dedicated resources per tenant
Configuration:
Connection Providers:
AppSettingsWriteDbConnectionProvider: Reads write database connection from appsettings
AppSettingsReadDbConnectionProvider: Reads read database connection from appsettings
Use Cases:
Enterprise Applications: Large organizations with strict data isolation requirements
Compliance Requirements: Industries requiring complete data separation
Definition: A multi-tenant architecture means that multiple customers (tenants) have their own independent database instances, with complete data isolation and independent scaling capabilities. FlexBase's default implementation provides each tenant with their own dedicated database.
Characteristics:
Independent Databases: Each tenant has their own dedicated database instance
Complete Data Isolation: Physical separation ensures maximum security and confidentiality
Independent Scaling: Each tenant can scale based on their specific usage patterns
Cost Optimization: Cloud databases can be scaled independently per tenant usage
Security: Highest level of data security with no cross-tenant data access
Performance: No cross-tenant performance impact or resource contention
Configuration:
Connection Providers:
NativeWriteDbTenantConnectionProvider: Dynamically determines write database based on tenant
NativeReadDbTenantConnectionProvider: Dynamically determines read database based on tenant
FlexBase Multi-Tenant Advantages:
1. Independent Database Scaling 🚀
Benefits:
Usage-Based Scaling: Each tenant scales based on their actual usage
Cost Optimization: Tenants pay only for resources they consume
Performance Isolation: Heavy usage by one tenant doesn't affect others
Cloud-Native: Leverages cloud database auto-scaling features
2. Enhanced Security & Compliance 🔒
Benefits:
Physical Data Separation: No shared tables or cross-tenant data access
Production Ready: Battle-tested for enterprise applications
This Entity Framework Core configuration system provides a robust, scalable, and maintainable data access layer that adapts to different database systems and tenant models! 🚀
public static class DbEndPointConfig
{
public static void AddFlexBaseDbServices(this IServiceCollection services)
{
//Configuration for Single Tenant Database
EnableSingleTenant(services);
//Configuration for Multi Tenant Database
//EnableMultitenant(services);
}
private static void EnableSingleTenant(IServiceCollection services)
{
ConfigureSingleTenantDb(services);
ConfigureFlexDb.ForRepositoryForSqlServer(services);
}
private static void EnableMultitenant(IServiceCollection services)
{
ConfigureMultiTenantMasterDb(services);
//Either
//If you want to use the same sql server for all instances for multitenant application
//You may do this if you want MySql Or PostgreSql respectively
ConfigureFlexDb.ForMultitenantMasterDbForSqlServer(services);
ConfigureFlexDb.ForRepositoryForSqlServer(services);
//Or
//ConfigureFlexDb.ForRepositoryForMultiDb(services); //If you want to use different database type for each tenant
}
}
private static void EnableSingleTenant(IServiceCollection services)
{
ConfigureSingleTenantDb(services);
ConfigureFlexDb.ForRepositoryForSqlServer(services);
}
private static void ConfigureSingleTenantDb(IServiceCollection services)
{
//Default Settings for connection provider which gives you a simple single tenant application
services.AddTransient<IWriteDbConnectionProviderBridge, AppSettingsWriteDbConnectionProvider>();
services.AddTransient<IReadDbConnectionProviderBridge, AppSettingsReadDbConnectionProvider>();
}
private static void EnableMultitenant(IServiceCollection services)
{
ConfigureMultiTenantMasterDb(services);
//Option 1: Same database type for all tenants
ConfigureFlexDb.ForMultitenantMasterDbForSqlServer(services);
ConfigureFlexDb.ForRepositoryForSqlServer(services);
//Option 2: Different database types for different tenants
//ConfigureFlexDb.ForRepositoryForMultiDb(services);
}
private static void ConfigureMultiTenantMasterDb(IServiceCollection services)
{
//Default Settings for connection provider which gives you a simple multi tenant application
services.AddTransient<IWriteDbConnectionProviderBridge, NativeWriteDbTenantConnectionProvider>();
services.AddTransient<IReadDbConnectionProviderBridge, NativeReadDbTenantConnectionProvider>();
}
// Complete data isolation per tenant
Tenant A Data: Completely isolated in Database_A
Tenant B Data: Completely isolated in Database_B
Tenant C Data: Completely isolated in Database_C
// Different tenants can use different database types
Tenant A: SQL Server (Enterprise features)
Tenant B: MySQL (Cost-effective)
Tenant C: PostgreSQL (Advanced features)
Tenant D: Oracle (High-performance analytics)
// Cloud-native database scaling per tenant
Azure SQL Database: Auto-scaling based on tenant usage
AWS RDS: Independent scaling per tenant
Google Cloud SQL: Per-tenant resource allocation
public class ApplicationEFSqlServerDbContext : ApplicationEFDbContext
{
public ApplicationEFSqlServerDbContext()
{
//this constructor goes only with the migrations
}
public ApplicationEFSqlServerDbContext(DbContextOptions<ApplicationEFSqlServerDbContext> options) : base(options)
{
//this constructor is being used by the repos to initialize the context with various options including multitenancy
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class FlexEFTenantMasterSqlServerContext : ApplicationTenantEFDbContext
{
public FlexEFTenantMasterSqlServerContext() : base()
{
}
public FlexEFTenantMasterSqlServerContext(DbContextOptions options): base(options)
{
}
}
public class ApplicationEFOracleDbContext : ApplicationEFDbContext
{
public ApplicationEFOracleDbContext()
{
//this constructor goes only with the migrations
}
public ApplicationEFOracleDbContext(DbContextOptions<ApplicationEFOracleDbContext> options) : base(options)
{
//this constructor is being used by the repos to initialize the context with various options including multitenancy
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class FlexEFTenantMasterOracleContext : ApplicationTenantEFDbContext
{
public FlexEFTenantMasterOracleContext() : base()
{
}
public FlexEFTenantMasterOracleContext(DbContextOptions options): base(options)
{
}
}
public class ApplicationEFDbContext : FlexEFDbContext
{
public ApplicationEFDbContext()
{
//this constructor goes only with the migrations
}
public ApplicationEFDbContext(DbContextOptions options) : base(options)
{
//this constructor is being used by the repos to initialize the context with various options including multitenancy
//Uncomment below code to enable audit trail for your CUD transactions
//this.EnableAuditTrail();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Use this for adding indexes or other customizations to tables pertaining to EF implementation
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
ILogger _logger = FlexContainer.ServiceProvider.GetRequiredService<ILogger<ApplicationEFDbContext>>();
base.OnConfiguring(optionsBuilder);
optionsBuilder
.EnableSensitiveDataLogging()
.LogTo(message => _logger.LogDebug(message)) //This is to log the EF queries to the logger
.AddInterceptors(new DefaultCreationTimeInterceptor()); //This is to set the creation time for the entities
}
}
public class ApplicationTenantEFDbContext : FlexEFTenantContext<FlexTenantBridge>
{
public ApplicationTenantEFDbContext() : base()
{
}
public ApplicationTenantEFDbContext(DbContextOptions options) :base(options)
{
}
}
public class AppSettingsWriteDbConnectionProvider : FlexAppSettingsWriteDbConnectionProvider<FlexAppContextBridge>, IWriteDbConnectionProviderBridge
{
public AppSettingsWriteDbConnectionProvider(IConfiguration configuration) : base(configuration)
{
}
}
public class AppSettingsReadDbConnectionProvider : FlexAppSettingsReadDbConnectionProvider<FlexAppContextBridge>, IReadDbConnectionProviderBridge
{
public AppSettingsReadDbConnectionProvider(IConfiguration configuration) : base(configuration)
{
}
}
public class NativeWriteDbTenantConnectionProvider : FlexNativeWriteDbTenantConnectionProvider<FlexTenantBridge, FlexAppContextBridge>, IWriteDbConnectionProviderBridge
{
public NativeWriteDbTenantConnectionProvider(IFlexNativeHostTenantProviderBridge flexTenantProvider) : base(flexTenantProvider)
{
}
}
public class NativeReadDbTenantConnectionProvider : FlexNativeReadDbTenantConnectionProvider<FlexTenantBridge, FlexAppContextBridge>, IReadDbConnectionProviderBridge
{
public NativeReadDbTenantConnectionProvider(IFlexNativeHostTenantProviderBridge flexTenantProvider) : base(flexTenantProvider)
{
}
}
// Development
if (env.IsDevelopment())
{
EnableSingleTenant(services);
}
// Production
else if (env.IsProduction())
{
EnableMultitenant(services);
}