A complete working example demonstrating all major features of the Ploch.Data libraries. It is configured to use either SQLite or SQL Server databases with SQLite being the default.
Migrations are kept in provider-specific projects.
- Entity modelling with
Ploch.Data.Modelinterfaces (IHasId,IHasTitle,IHasDescription,IHasContents,IHasAuditProperties,IHasCategories,IHasTags) - Common base types --
Category<T>for hierarchical categories,Tag<TId>for flat tags,Property<TValue>for key/value metadata - DbContext setup with assembly-scanned entity configurations
- Targeting multiple databases with provider-specific migrations.
- SQLite DateTimeOffset workaround via
ApplySqLiteDateTimeOffsetPropertiesFix - Automatic audit timestamps via
SaveChangesoverride onIHasAuditTimePropertiesentities - DI registration using
AddRepositories<TDbContext>() - Repository operations --
GetByIdAsync,GetAllAsync,GetPageAsync,CountAsyncwith filtering and eager loading - Unit of Work -- atomic multi-entity transactions with
CommitAsync - SQLite design-time factory for EF Core migrations tooling
- Integration tests using
GenericRepositoryDataIntegrationTest<TDbContext>base class
samples/SampleApp/
src/
Model/ # Entity POCOs
Article.cs # Full-featured entity with audit, categories, tags
ArticleCategory.cs # Hierarchical category (extends Category<T>)
ArticleTag.cs # Tag entity (extends Tag<TId>)
ArticleProperty.cs # Key/value property (extends Property<string>)
Author.cs # Entity with INamed, IHasDescription, IHasAuditProperties
Data/ # DbContext, configurations, DI registration
SampleAppDbContext.cs
Configurations/
ServiceCollectionRegistrations.cs
Data.SQLite/ # SQLite design-time factory
SampleAppDbContextFactory.cs
Data.SqlServer/ # SQL Server design-time factory
SampleAppDbContextFactory.cs
ConsoleApp/ # Console application host
Program.cs
tests/
IntegrationTests/ # Integration tests
ArticleRepositoryTests.cs
UnitOfWorkTests.cs
First, you need to add migrations to the provider-specific project. By default, SQLite is used.
cd samples/SampleApp/src/Data.SQLite
dotnet ef migrations add InitialCreateThen, you need to create the database (it will be created in the Data.SQLite directory, ConsoleApp appsettings.json already points to it):
dotnet ef database updateFinally, run the console app (the default connection string points to the sampleapp.db file in the Data.SQLite directory created above):
cd samples/SampleApp/src/ConsoleApp
dotnet runThe console app creates sample entities (authors, categories, tags, articles), demonstrates CRUD operations, pagination, filtering, and eager loading.
cd samples/SampleApp
dotnet testThe integration tests use an in-memory SQLite database and verify repository operations, Unit of Work transactions, audit timestamps, hierarchical categories, and pagination.
The SampleApp uses the provider-specific DI packages, which allow switching the database with zero application code changes. Only two things need to change: the package reference and the connection string.
In the ConsoleApp .csproj, reference the SQLite package:
<PackageReference Include="Ploch.Data.GenericRepository.EFCore.SqLite" />In appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "DataSource=sampleapp.db;Cache=Shared"
}
}Swap the package reference to SQL Server:
<PackageReference Include="Ploch.Data.GenericRepository.EFCore.SqlServer" />Update appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=SampleApp;Integrated Security=True;TrustServerCertificate=True"
}
}Add migrations to the SqlServer project (see the appsettings.json file for the connection string):
cd samples/SampleApp/src/Data.SqlServer
dotnet ef migrations add InitialCreateThen, you need to create the database:
dotnet ef database updateBoth packages expose the same namespace (Ploch.Data.GenericRepository.EFCore.DependencyInjection) and method (AddDbContextWithRepositories<TDbContext>()). The Program.cs call remains identical:
builder.Services.AddDbContextWithRepositories<SampleAppDbContext>();Behind the scenes, the SQLite package registers SqLiteDbContextCreationLifecycle (which applies the DateTimeOffset value converter fix), while the SQL Server package registers DefaultDbContextCreationLifecycle (no-op). The SampleAppDbContext accepts IDbContextCreationLifecycle via constructor injection and calls it from OnModelCreating, keeping the DbContext itself provider-agnostic.
See the Dependency Injection Guide for full details on all registration approaches.
The Article entity demonstrates implementing multiple interfaces:
public class Article : IHasId<int>, IHasTitle, IHasDescription, IHasContents,
IHasAuditProperties,
IHasCategories<ArticleCategory>, IHasTags<ArticleTag>
{
public int Id { get; set; }
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Contents { get; set; }
// ... audit properties, navigation properties
}using Ploch.Data.GenericRepository.EFCore.DependencyInjection;
// One call registers DbContext + repositories + lifecycle plugin.
// Connection string loaded from appsettings.json automatically.
builder.Services.AddDbContextWithRepositories<SampleAppDbContext>();var article = await readArticleRepo.GetByIdAsync(
articleId,
onDbSet: q => q.Include(a => a.Author)
.Include(a => a.Categories)
.Include(a => a.Tags)
.Include(a => a.Properties));public class ArticleRepositoryTests
: GenericRepositoryDataIntegrationTest<SampleAppDbContext>
{
[Fact]
public async Task AddAsync_should_persist_article_with_audit_properties()
{
var repository = CreateReadWriteRepositoryAsync<Article, int>();
// ... test code
}
}See the full documentation for detailed guides on each library component.