Skip to content
bitzorcas
EN

Reference

Persistence building block

Multi-adapter persistence layer — SqlSugar (primary ORM with trim-clean query API), optional EF Core adapter, Dapper for read-only queries, all behind a unified IUnitOfWork abstraction.

Last updated

BitzOrcas takes a multi-adapter persistence strategy — SqlSugar is the primary ORM, EF Core is available as an optional drop-in adapter, and Dapper serves read-only query scenarios. All three share a common IUnitOfWork contract and the same CAP Outbox for transactional event publishing.

Architecture

┌──────────────────┐
│ IUnitOfWork │
└────────┬─────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
SqlSugar UoW CapSqlSugar UoW EfCore UoW
(query-only) (CAP Outbox) (CAP Outbox)
│ │ │
▼ ▼ ▼
SqlSugar ORM SqlSugar ORM EF Core ORM
+ Audit Store + CAP + Audit + CAP
│ │ │
└──────────────┴──────────────┘
┌──────┴──────┐
│ SQL Server │
└─────────────┘

SqlSugar (Primary ORM)

BitzOrcas.Infrastructure.SqlSugar is the default persistence adapter, providing:

Trim-clean query API

SqlSugar uses a fluent query API that avoids expression-tree bloat:

// Simple query
var notes = await db.Queryable<NoteEntity>()
.Where(n => n.TenantId == tenantId)
.OrderByDescending(n => n.CreatedAt)
.ToListAsync();
// Pagination
var page = await db.Queryable<NoteEntity>()
.Where(n => n.TenantId == tenantId)
.ToPageListAsync(pageNumber, pageSize, total);

Global filters (multi-tenancy)

SqlSugar applies global query filters automatically — no per-query opt-in:

// Registered once in DI — applies to every query automatically
db.QueryFilter.AddTableFilter<TenantEntityBase>(e => e.TenantId == currentTenantId);
db.QueryFilter.AddTableFilter<TenantSoftDeleteEntityBase>(e => e.IsDeleted == false);

Schema initialization

Instead of migration files, SqlSugar uses CodeFirst auto-mapping via PersistenceModelRegistry:

// Entities declare their metadata via attributes
[SugarTable("biz_notes")]
public class NoteEntity : TenantSoftDeleteEntityBase
{
[SugarColumn(Length = 200)]
public string Title { get; set; }
[SugarColumn(ColumnDataType = "nvarchar(max)")]
public string Content { get; set; }
}

Schema is initialized via --init-schema flag at startup — creates tables, indexes, and seed data in one pass.

Audit store

SqlSugar ships the production audit storage backend (SqlSugarAuditLogStore) with 7-category sharded tables:

  • SysAuditLog — general audit
  • SysActivityLog — user activity
  • SysEntityPropertyChangesLog — property change tracking
  • SysCommunicationLog — API communication
  • SysExternalRequestLogRecord — outbound HTTP calls
  • SysSpecialLog — special events

CAP Outbox integration

CapSqlSugarUnitOfWork bridges SqlSugar and CAP’s Outbox pattern — domain events and integration events are published atomically within the same database transaction:

// Events are queued in Outbox table, published by CAP after commit
await unitOfWork.PublishAsync(new NoteCreatedIntegrationEvent(noteId));
await unitOfWork.CommitAsync(); // commits DB + Outbox atomically

EF Core (Optional Adapter)

BitzOrcas.Infrastructure.EfCore provides a drop-in replacement:

  • Same IUnitOfWork contract
  • Same CAP Outbox bridge (DotNetCore.CAP.SqlServer)
  • Mirrors every SqlSugar repository with an EF Core equivalent
  • Set via Persistence:Provider=EfCore in configuration

Dapper (Read-only queries)

BitzOrcas.Infrastructure.Dapper provides lightweight read-only queries for scenarios where you need maximum performance:

// Dapper for read-heavy reporting queries
const string sql = "SELECT * FROM biz_notes WHERE TenantId = @TenantId";
var notes = await connection.QueryAsync<NoteEntity>(sql, new { TenantId = tenantId });

Entity hierarchy

EntityBase (snowflake Id)
├── TenantEntityBase (+ TenantId, global filter)
│ ├── TenantSoftDeleteEntityBase (+ IsDeleted)
│ │ └── All business entities (biz_*)
│ └── Identity entities (sys_*)
└── BizEntityBase (business entity marker)
AuditEntityBase (separate hierarchy for sharded audit tables)
├── AuditTenantEntityBase (+ TenantId)
└── AuditTenantSoftDeleteEntityBase (+ IsDeleted)

Configuration

{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BitzOrcas;User Id=sa;Password=..."
},
"Persistence": {
"Provider": "SqlSugar" // or "EfCore"
},
"RabbitMq": {
"Host": "localhost",
"Port": 5672
}
}

Assembly reference

BitzOrcas.Infrastructure.SqlSugar
├── DependencyInjection.cs → AddBitzOrcasSqlSugarWithCap()
├── SqlSugarUnitOfWork.cs → IUnitOfWork implementation
├── Cap/ → CapSqlSugarUnitOfWork, Outbox extensions
├── Auditing/ → Audit store + entities + retention
├── Seeders/ → CsvSeedReader, CsvSeedStepBase
├── Mapping/ → PersistenceModelRegistry, SchemaInitializer
└── {Module}/ → Per-module SqlSugar repositories
BitzOrcas.Infrastructure.EfCore
├── DependencyInjection.cs → AddBitzOrcasEfCoreWithCap()
└── {Module}/ → Per-module EF Core repositories
BitzOrcas.Infrastructure.Dapper
└── Dapper query extensions
BitzOrcas.Infrastructure.Persistence.Models
├── Entities/ → Base entity hierarchy
├── Identity/ → Sys* platform entities
├── MasterData/ → Master data entities
└── {Module}/ → Per-module entities