data-dotnet
# EF Core Data Persistence Layer - Hexagonal Architecture Adapter
What this skill does
# EF Core Data Persistence Layer - Hexagonal Architecture Adapter
## Overview
This skill provides a comprehensive guide to implementing the data persistence layer using Entity Framework Core as an adapter in a hexagonal architecture (ports and adapters pattern). The data layer is a **library that encapsulates its implementation internally** and **exposes DI registration publicly** for wiring into applications.
This skill complements the `ddd-dotnet-basics.md` skill and assumes familiarity with Domain layer patterns.
### Key Architecture Principles
```
┌─────────────────────────────────────────────┐
│ Domain (Ports) │
│ - IRepository<TAggregate, TKey> │
│ - IQueryRepository<TAggregate> │
│ - IUnitOfWork │
│ - Aggregates & Entities │
└─────────────────────────────────────────────┘
▲
│ (implements)
│
┌─────────────────────────────────────────────┐
│ Data (Adapter - Infrastructure) │
│ - DbContext │
│ - Entity Configurations │
│ - Repository Implementations │
│ - UnitOfWork Implementation │
│ - Service Registrations (DI) │
└─────────────────────────────────────────────┘
```
**Key Benefits:**
- **Testability**: In-memory database for unit/integration tests
- **Separation of Concerns**: Domain knows nothing about EF Core
- **CQRS Support**: Separate read/write repository base classes
- **Schema Organization**: Multiple database schemas for bounded contexts
- **Convention-based Registration**: Automatic repository discovery and registration
---
## Project Structure
```
Pikot.LMP.Adapters.Data/
├── DataContext.cs # Main DbContext
├── UnitOfWork.cs # Transaction coordinator
├── ServiceRegistrations.cs # DI setup (public API)
├── EntityConfigurations/ # Fluent configurations
│ ├── ConfigHelper.cs # Reusable config utilities
│ ├── ShipperOrderConfiguration.cs
│ ├── PackageConfiguration.cs
│ └── ...
├── Repositories/ # Repository implementations
│ ├── RepositoryBase.cs # Write repository base
│ ├── QueryRepositoryBase.cs # Read repository base
│ ├── ShipperOrderRepository.cs
│ ├── ShipperOrderQueryRepository.cs
│ └── ...
└── Migrations/ # EF Core migrations
└── ...
```
---
## DbContext Configuration
### Main DbContext
The `DataContext` is the central EF Core context with specific patterns for production and testing.
```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Pikot.LMP.Adapters.Data;
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options) { }
public DataContext() { } // Parameterless for design-time tools
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Fallback configuration for design-time tools (migrations)
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseNpgsql("Server=127.0.0.1;Database=lmp-nia;User Id=admin;Password=Password1!");
}
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Auto-discover and apply all IEntityTypeConfiguration<T> from assembly
modelBuilder.ApplyConfigurationsFromAssembly(typeof(DataContext).Assembly);
base.OnModelCreating(modelBuilder);
}
// Migration runner with retry logic (for application startup)
public static async Task RunDbMigrationsAsync(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<DataContext>>();
const int maxAttempts = 5;
var attempt = 1;
var success = false;
Exception? startupException = null;
while (attempt < maxAttempts && !success)
{
try
{
logger.LogInformation("Attempting db migration run on startup. Attempt [{StartupMigrationAttempt}]",
attempt);
using var dbScope = scope.ServiceProvider.CreateScope();
var db = dbScope.ServiceProvider.GetRequiredService<DataContext>();
await db.Database.MigrateAsync();
logger.LogInformation("Db migration run successful");
success = true;
startupException = null;
}
catch (Exception ex)
{
startupException = ex;
logger.LogError(ex, "Error accessing db for startup migration run on attempt [{StartupMigrationAttempt}]",
attempt);
attempt++;
await ExponentialBackoffAsync(attempt);
}
}
if (attempt < maxAttempts || startupException == null)
{
return;
}
logger.LogCritical("Db migration run failed. Service run FAILED");
throw new Exception("Critical stop", startupException);
}
private static Task ExponentialBackoffAsync(int attempt)
{
var waitInterval = TimeSpan.FromSeconds(Fibonacci(attempt));
return Task.Delay(waitInterval);
}
private static int Fibonacci(int n)
{
if (n is 0 or 1)
{
return n;
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
```
**Key Patterns:**
- **Two constructors**: Parameterized for runtime, parameterless for design-time
- **Conditional configuration**: Fallback for migration tools
- **Assembly scanning**: Auto-discovers entity configurations
- **Migration runner**: Resilient startup with exponential backoff
- **Logging**: Track migration attempts and failures
---
## Entity Configurations
Entity configurations use the Fluent API to map domain objects to database tables. Each entity gets its own configuration class.
### Configuration Helper
Reusable utilities for common configuration patterns:
```csharp
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Pikot.LMP.Adapters.Data.EntityConfigurations;
internal static class ConfigHelper
{
// Bulk set multiple properties as required
public static void SetRequired<TEntityType>(this EntityTypeBuilder<TEntityType> builder,
params Expression<Func<TEntityType, object>>[] setters) where TEntityType : class
{
foreach (var setter in setters)
{
var member = setter.Body;
if (member == null)
{
throw new ArgumentNullException(nameof(setter.Body));
}
var propInfo = member is UnaryExpression expression
? expression.Operand as MemberExpression
: member as MemberExpression;
builder.Property(propInfo.Member.Name).IsRequired();
}
}
public static void SetMaxLength<TEntityType, TProperty>(this EntityTypeBuilder<TEntityType> builder,
Expression<Func<TEntityType, TProperty>> property, int maxLength)
where TEntityType : class
{
builder.Property(property).HasMaxLength(maxLength);
}
// Database schemas for bounded contexts
public const string DispatcherScheme = "Dispatcher";
}
```
### Aggregate Configuration Example
Configuration for an aggregate root with child entities:
```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Pikot.LMP.Domain.Shipment;
using Pikot.LMP.Domain.Shipment.ShipperOrders;
namespace Pikot.LMP.Adapters.Data.EntityConfigurations;
internal class ShipperOrderConfiguration : IEntityTypeConfiguration<ShipperORelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.