MasLazu.AspNet.Verification.EfCore
1.0.0-preview.6
dotnet add package MasLazu.AspNet.Verification.EfCore --version 1.0.0-preview.6
NuGet\Install-Package MasLazu.AspNet.Verification.EfCore -Version 1.0.0-preview.6
<PackageReference Include="MasLazu.AspNet.Verification.EfCore" Version="1.0.0-preview.6" />
<PackageVersion Include="MasLazu.AspNet.Verification.EfCore" Version="1.0.0-preview.6" />
<PackageReference Include="MasLazu.AspNet.Verification.EfCore" />
paket add MasLazu.AspNet.Verification.EfCore --version 1.0.0-preview.6
#r "nuget: MasLazu.AspNet.Verification.EfCore, 1.0.0-preview.6"
#:package MasLazu.AspNet.Verification.EfCore@1.0.0-preview.6
#addin nuget:?package=MasLazu.AspNet.Verification.EfCore&version=1.0.0-preview.6&prerelease
#tool nuget:?package=MasLazu.AspNet.Verification.EfCore&version=1.0.0-preview.6&prerelease
MasLazu.AspNet.Verification.EfCore
The Infrastructure layer of the MasLazu ASP.NET Verification system. This project provides Entity Framework Core implementation for data persistence using Clean Architecture and CQRS patterns.
๐ Overview
This is the infrastructure layer that implements data access using Entity Framework Core. It provides database contexts, entity configurations, and repository implementations following CQRS (Command Query Responsibility Segregation) pattern.
๐๏ธ Architecture
This project represents the Infrastructure Layer in Clean Architecture:
- Data Contexts: EF Core DbContexts for write and read operations
- Configurations: Entity type configurations and relationships
- Extensions: Dependency injection utilities for EF Core setup
- CQRS Support: Separate contexts for commands and queries
๐ฆ Dependencies
Project References
MasLazu.AspNet.Verification.Abstraction- Core interfacesMasLazu.AspNet.Verification.Domain- Domain entities
Package References
MasLazu.AspNet.Framework.EfCore- Base EF Core frameworkMicrosoft.EntityFrameworkCore- EF Core ORM
๐ Core Components
Database Contexts
VerificationDbContext (Write Context)
Main context for write operations (commands):
public class VerificationDbContext : BaseDbContext
{
public VerificationDbContext(DbContextOptions<VerificationDbContext> options) : base(options)
{
}
public DbSet<Domain.Entities.Verification> Verifications { get; set; }
public DbSet<VerificationPurpose> VerificationPurposes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}
VerificationReadDbContext (Read Context)
Optimized context for read operations (queries):
public class VerificationReadDbContext : BaseReadDbContext
{
public VerificationReadDbContext(DbContextOptions<VerificationReadDbContext> options) : base(options)
{
}
public DbSet<Domain.Entities.Verification> Verifications { get; set; }
public DbSet<VerificationPurpose> VerificationPurposes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}
๐ง Entity Configurations
VerificationConfiguration
Comprehensive configuration for the Verification entity:
public class VerificationConfiguration : IEntityTypeConfiguration<Domain.Entities.Verification>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Verification> builder)
{
// Primary Key
builder.HasKey(v => v.Id);
// Required Properties
builder.Property(v => v.UserId).IsRequired();
builder.Property(v => v.Channel).IsRequired().HasConversion<string>();
builder.Property(v => v.Destination).IsRequired().HasMaxLength(255);
builder.Property(v => v.VerificationCode).IsRequired().HasMaxLength(10);
builder.Property(v => v.Status).IsRequired().HasConversion<string>()
.HasDefaultValue(VerificationStatus.Pending);
builder.Property(v => v.ExpiresAt).IsRequired();
// Optional Properties
builder.Property(v => v.VerifiedAt).IsRequired(false);
builder.Property(v => v.AttemptCount).HasDefaultValue(0);
// Relationships
builder.HasOne(v => v.VerificationPurpose)
.WithMany(vp => vp.Verifications)
.HasForeignKey(v => v.VerificationPurposeCode)
.HasPrincipalKey(vp => vp.Code)
.OnDelete(DeleteBehavior.Restrict);
// Indexes
builder.HasIndex(v => new { v.UserId, v.VerificationPurposeCode, v.Status });
builder.HasIndex(v => v.VerificationCode);
builder.HasIndex(v => v.ExpiresAt);
}
}
VerificationPurposeConfiguration
Configuration for verification purposes:
public class VerificationPurposeConfiguration : IEntityTypeConfiguration<VerificationPurpose>
{
public void Configure(EntityTypeBuilder<VerificationPurpose> builder)
{
builder.HasKey(vp => vp.Id);
builder.Property(vp => vp.Code).IsRequired().HasMaxLength(50);
builder.Property(vp => vp.Name).IsRequired().HasMaxLength(100);
builder.Property(vp => vp.Description).HasMaxLength(500);
builder.Property(vp => vp.IsActive).HasDefaultValue(true);
builder.HasIndex(vp => vp.Code).IsUnique();
builder.HasIndex(vp => vp.IsActive);
}
}
๐ Database Schema
Tables
Verifications
CREATE TABLE Verifications (
Id UNIQUEIDENTIFIER PRIMARY KEY,
UserId UNIQUEIDENTIFIER NOT NULL,
Channel NVARCHAR(50) NOT NULL, -- Enum stored as string
Destination NVARCHAR(255) NOT NULL,
VerificationCode NVARCHAR(10) NOT NULL,
VerificationPurposeCode NVARCHAR(50) NOT NULL,
Status NVARCHAR(50) NOT NULL DEFAULT 'Pending',
AttemptCount INT DEFAULT 0,
ExpiresAt DATETIMEOFFSET NOT NULL,
VerifiedAt DATETIMEOFFSET NULL,
CreatedAt DATETIMEOFFSET NOT NULL,
UpdatedAt DATETIMEOFFSET NULL,
FOREIGN KEY (VerificationPurposeCode) REFERENCES VerificationPurposes(Code)
);
VerificationPurposes
CREATE TABLE VerificationPurposes (
Id UNIQUEIDENTIFIER PRIMARY KEY,
Code NVARCHAR(50) NOT NULL UNIQUE,
Name NVARCHAR(100) NOT NULL,
Description NVARCHAR(500) NULL,
IsActive BIT DEFAULT 1,
CreatedAt DATETIMEOFFSET NOT NULL,
UpdatedAt DATETIMEOFFSET NULL
);
Indexes
IX_Verifications_UserId_VerificationPurposeCode_StatusIX_Verifications_VerificationCodeIX_Verifications_ExpiresAtIX_VerificationPurposes_Code(Unique)IX_VerificationPurposes_IsActive
๐ง Configuration
Service Registration
// Register EF Core contexts
services.AddDbContext<VerificationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddDbContext<VerificationReadDbContext>(options =>
options.UseSqlServer(connectionString));
// Register repositories
services.AddScoped<IRepository<Domain.Entities.Verification>, EfRepository<Domain.Entities.Verification, VerificationDbContext>>();
services.AddScoped<IReadRepository<Domain.Entities.Verification>, EfReadRepository<Domain.Entities.Verification, VerificationReadDbContext>>();
// Register unit of work
services.AddScoped<IUnitOfWork, EfUnitOfWork<VerificationDbContext>>();
Connection String
{
"ConnectionStrings": {
"VerificationDb": "Server=.;Database=VerificationDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"VerificationReadDb": "Server=.;Database=VerificationDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Migration Setup
// In Program.cs or Startup.cs
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<VerificationDbContext>();
await dbContext.Database.MigrateAsync();
}
๐ Usage Examples
Repository Pattern Implementation
public class VerificationService : IVerificationService
{
private readonly IRepository<Domain.Entities.Verification> _repository;
private readonly IReadRepository<Domain.Entities.Verification> _readRepository;
private readonly IUnitOfWork _unitOfWork;
public VerificationService(
IRepository<Domain.Entities.Verification> repository,
IReadRepository<Domain.Entities.Verification> readRepository,
IUnitOfWork unitOfWork)
{
_repository = repository;
_readRepository = readRepository;
_unitOfWork = unitOfWork;
}
public async Task<VerificationDto> CreateVerificationAsync(CreateVerificationRequest request)
{
var verification = new Domain.Entities.Verification
{
// ... set properties
};
await _repository.AddAsync(verification);
await _unitOfWork.SaveChangesAsync();
return verification.Adapt<VerificationDto>();
}
}
Query Optimization
public async Task<VerificationDto?> GetByCodeAsync(string code)
{
return await _readRepository.FirstOrDefaultAsync(
v => v.VerificationCode == code &&
v.Status == VerificationStatus.Pending &&
v.ExpiresAt > DateTimeOffset.UtcNow);
}
Bulk Operations
public async Task ExpireOldVerificationsAsync()
{
var expiredVerifications = await _repository.FindAsync(
v => v.ExpiresAt < DateTimeOffset.UtcNow &&
v.Status == VerificationStatus.Pending);
foreach (var verification in expiredVerifications)
{
verification.Status = VerificationStatus.Failed;
await _repository.UpdateAsync(verification);
}
await _unitOfWork.SaveChangesAsync();
}
๐งช Testing
Unit Testing with In-Memory Database
[Fact]
public async Task CreateVerificationAsync_ShouldCreateVerification()
{
// Arrange
var options = new DbContextOptionsBuilder<VerificationDbContext>()
.UseInMemoryDatabase(databaseName: "TestDb")
.Options;
using var context = new VerificationDbContext(options);
var repository = new EfRepository<Domain.Entities.Verification, VerificationDbContext>(context);
// Act
var verification = new Domain.Entities.Verification { /* ... */ };
await repository.AddAsync(verification);
await context.SaveChangesAsync();
// Assert
var saved = await context.Verifications.FindAsync(verification.Id);
Assert.NotNull(saved);
}
Integration Testing
[Fact]
public async Task VerificationRepository_ShouldHandleConcurrency()
{
// Arrange
using var factory = new SqliteConnectionFactory();
var connection = factory.CreateConnection();
var options = new DbContextOptionsBuilder<VerificationDbContext>()
.UseSqlite(connection)
.Options;
// Act & Assert
// Test concurrent operations
}
๐ Performance Optimization
Query Optimization
// Use AsNoTracking for read-only queries
public async Task<List<VerificationDto>> GetPendingVerificationsAsync()
{
return await _readRepository.FindAsync(
v => v.Status == VerificationStatus.Pending,
query => query.AsNoTracking()
.OrderBy(v => v.ExpiresAt));
}
Indexing Strategy
- Composite Indexes: For common query patterns
- Single Column: For unique constraints and lookups
- Filtered Indexes: For active records only
Connection Pooling
// Configure connection pooling
services.AddDbContextPool<VerificationDbContext>(options =>
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.MaxBatchSize(100);
sqlOptions.CommandTimeout(30);
}));
๐ Migrations
Creating Migrations
# Add migration
dotnet ef migrations add InitialCreate --project src/MasLazu.AspNet.Verification.EfCore --startup-project src/MasLazu.AspNet.Verification.Endpoint
# Update database
dotnet ef database update --project src/MasLazu.AspNet.Verification.EfCore --startup-project src/MasLazu.AspNet.Verification.Endpoint
Migration Files
[Migration("20230918000000_InitialCreate")]
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Create tables and indexes
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Rollback logic
}
}
๐ Monitoring & Health Checks
Health Check Configuration
services.AddHealthChecks()
.AddDbContextCheck<VerificationDbContext>("VerificationDb")
.AddDbContextCheck<VerificationReadDbContext>("VerificationReadDb");
Metrics Collection
// Configure EF Core metrics
services.AddOpenTelemetry()
.WithMetrics(builder =>
{
builder.AddMeter("Microsoft.EntityFrameworkCore");
});
๐ ๏ธ Development Guidelines
Code Structure
src/MasLazu.AspNet.Verification.EfCore/
โโโ Configurations/ # Entity type configurations
โโโ Data/ # DbContext classes
โโโ Extensions/ # DI utilities
โโโ Migrations/ # EF Core migrations (generated)
Best Practices
- Use separate contexts for read/write operations
- Configure indexes for common query patterns
- Use appropriate data types and constraints
- Implement proper foreign key relationships
- Use migrations for schema changes
- Test with realistic data volumes
Naming Conventions
- Configurations: Entity name + "Configuration"
- Contexts: Domain name + "DbContext" / "ReadDbContext"
- Migrations: Descriptive names with timestamps
๐ค Contributing
- Schema Changes: Use EF Core migrations for database changes
- Performance: Consider query optimization and indexing
- Testing: Add integration tests for data operations
- Documentation: Update entity configurations documentation
- Code Review: Ensure proper separation of read/write contexts
๐ License
Part of the MasLazu ASP.NET framework ecosystem.</content> <parameter name="filePath">/home/mfaziz/projects/cs/MasLazu.AspNet.Verification/src/MasLazu.AspNet.Verification.EfCore/README.md
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- MasLazu.AspNet.Framework.EfCore (>= 1.0.0-preview.15)
- MasLazu.AspNet.Verification.Abstraction (>= 1.0.0-preview.6)
- MasLazu.AspNet.Verification.Domain (>= 1.0.0-preview.6)
- Microsoft.EntityFrameworkCore (>= 9.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview.6 | 148 | 10/2/2025 |
| 1.0.0-preview.5 | 147 | 10/1/2025 |
| 1.0.0-preview.4 | 148 | 9/29/2025 |
| 1.0.0-preview.3 | 147 | 9/29/2025 |
| 1.0.0-preview.2 | 113 | 9/28/2025 |
| 1.0.0-preview.1 | 268 | 9/18/2025 |