Persistence
Overview
Independent (NO-SQL, SQL) interface for applying the storage pattern to domain logic. Dependent (NO-SQL, SQL) implementation for specific storage servers.
Install
- ASOL.Core.Persistence.Abstractions - contains persistence interfaces like IRepository, IReadOnlyRepository. Use this package when using persistence.
- ASOL.Core.Persistence.MongoDB - contains MongoDB persistence providers like MongoRepository, MongoPagedRepository, MongoPolymorphicRepository. Use this package when implementing persistence.
- ASOL.Core.Persistence.EFCore - contains EntityFrameworkCore persistence providers like BaseDynamicDbContextConfigurationProvider. Use this package when implementing persistence.
Usage third party libraries (NuGets)
ASOL.Core.Persistence.MongoDB
Registration of persistence layer
Differences of tenant, master and hybrid database contexts:
- Tenant - means that domain entity and repository is used in tenant database context only
- Master - means that domain entity and repository is used in master database context only
- Hybrid - means that domain entity and repository is used in both (tenant and master) database contexts
Required dependency injection for persistence data.
MongoDB dependency injection
Tenant, master and hybrid database context
Example for registration of repositories for tenant, master and hybrid database contexts.
services.AddMongoDb(configuration.GetSection(nameof(MongoDbConnectionOptions)));
public static IServiceCollection AddMongoDb(this IServiceCollection services, IConfigurationSection configurationSection)
{
new DbMapping().RegisterClassMaps();
services.AddDefaultTenantDbContext(configurationSection); //use tenant db context by default
services.AddRepoFactory();
// Register repositories implicitly in default context (i.e. in tenant db context only)
services.AddRepository<IBookRepository, IBookReadOnlyRepository, BookRepository>();
// Register repositories using tenant accessor (i.e. in tenant db context only)
services.AddTenantRepositoryAccessor<IBookRepository, IBookReadOnlyRepository, BookRepository>();
// Register repositories using master accessor (i.e. in master db context only)
services.AddMasterRepositoryAccessor<IBookRepository, IBookReadOnlyRepository, BookRepository>();
// Register repositories using hybrid accessor (i.e. tenant and master db contexts)
services.AddHybridRepositoryAccessor<IBookRepository, IBookReadOnlyRepository, BookRepository>();
//(ocassionally) register repositories implicitly in non-default context (i.e. in master db context only)
services.AddTransient<IBookRepository>(sp =>
{
var repoFactory = sp.GetRequiredService<IRepoFactory>();
return repoFactory.CreateMasterRepositoryByType<BookRepository>();
});
services.AddTransient<IBookReadOnlyRepository>(sp => sp.GetRequiredService<IBookRepository>());
// Data, index and seed migrations
services.AddMultitenancyMongoDbMigration();
// tenant migrations
.AddTenantDataMigration<BookDataMigration>()
// master migrations
.AddMasterDataMigration<BookDataMigration>()
// hybrid migrations
.AddHybridDataMigration<BookDataMigration>()
;
return services;
}
public class DbMapping : DbMappingBase
{
...
/// <inheritdoc/>
protected override void MapDomainEntities()
{
TryMapEntity<Book>(map =>
{
map.AutoMap();
map.SetIgnoreExtraElements(true);
});
}
...
}
Master database context
using ASxOL.MyOwnProject.Domain.Repositories;
using ASxOL.MyOwnProject.Persistence.Migrations;
using ASxOL.MyOwnProject.Persistence.Repositories;
using ASOL.Core.Multitenancy.MongoDb.Extensions;
using ASOL.Core.Persistence.MongoDb.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ASxOL.MyOwnProject.Persistence;
public static class MongoDbServiceCollectionExtensions
{
public static IServiceCollection AddMongoDb(this IServiceCollection services, IConfigurationSection configurationSection)
{
new DbMapping().RegisterClassMaps();
services.TryAddDefaultMasterDbContext();
services.TryAddMasterRepoFactory();
services.AddMasterRepositoryAccessor<ITestItemRepository, ITestItemReadOnlyRepository, TestItemRepository>();
return services;
}
}
Sticky primary reads after write
When reads go to secondaries (e.g. readPreference=secondaryPreferred), a read right after a write can miss the written data because of replication lag.
With sticky primary reads, once a scope (HTTP request, message, job) modifies data, its following reads go to the primary.
Other reads keep the configured read preference.
services.AddMongoDbStickyPrimaryReads();
- Standard repository methods (
Add*,Update*,Delete*) mark the scope dirty, also when they fail. - Mark other modifications yourself:
MarkDirty()in a repository,IDbModificationState.MarkDirty()elsewhere. - Each scope starts clean. Tenant and master contexts of one scope share the flag.
- An explicit read preference wins, e.g.
WithReadPreference(ReadPreference.Primary)for a read without a previous write.
A nested scope (e.g. IRuntimeContextScope) starts clean.
When it reads data written by the outer scope or by another nested scope, or the other way round, share the flag right after creating it:
using var scope = serviceProvider.GetRequiredService<IRuntimeContextScope>();
scope.ScopeProvider.ShareDbModificationState(serviceProvider); // serviceProvider of the HTTP request or message scope
Limitations:
- Collections and queries obtained before the write keep their read preference.
- A context created from the root service provider shares one flag for the whole process.
EntityFrameworkCore (EfCore) dependency injection
Tenant database context
services.AddEfCorePersistence<DatabaseContextConfigurationProvider>();
public static IServiceCollection AddEfCorePersistence<TTenantDbContextConfigurationProvider>(this IServiceCollection services)
where TTenantDbContextConfigurationProvider : class, ITenantDbContextConfigurationProvider
{
services.AddTenantDbContext<DatabaseContext, TTenantDbContextConfigurationProvider>();
services.AddTenantRepositoryAccessor<ITestItemRepository, ITestItemReadOnlyRepository, TestItemRepository, DatabaseContext>();
return services;
}
Master database context
services.AddEfCorePersistence<DatabaseContextConfigurationProvider>();
public static IServiceCollection AddEfCorePersistence<TMasterDbContextConfigurationProvider>(this IServiceCollection services)
where TMasterDbContextConfigurationProvider : class, IMasterDbContextConfigurationProvider
{
services.AddMasterDbContext<DatabaseContext, TMasterDbContextConfigurationProvider>();
services.AddMasterRepositoryAccessor<ITestItemRepository, ITestItemReadOnlyRepository, TestItemRepository, DatabaseContext>();
return services;
}
Hybrid database context
services.AddEfCorePersistence<TenantDatabaseContextConfigurationProvider, MasterDatabaseContextConfigurationProvider>();
public static IServiceCollection AddEfCorePersistence<TTenantDbContextConfigurationProvider, TMasterDbContextConfigurationProvider>(this IServiceCollection services)
where TTenantDbContextConfigurationProvider : class, ITenantDbContextConfigurationProvider
where TMasterDbContextConfigurationProvider : class, IMasterDbContextConfigurationProvider
{
services.AddHybridDbContext<DatabaseContext, TTenantDbContextConfigurationProvider, TMasterDbContextConfigurationProvider>();
services.AddHybridRepositoryAccessor<ITestItemRepository, ITestItemReadOnlyRepository, TestItemRepository, DatabaseContext>();
return services;
}