用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-efcore-architecture命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
Routes .NET/C# work to domain skills. Loads coding-standards for code paths.
基于 SOC 职业分类
| name | dotnet-efcore-architecture |
| category | data |
| subcategory | ef-core |
| description | Designs EF Core data layer architecture. Read/write split, aggregate boundaries, N+1 governance. |
| license | MIT |
| targets | ["*"] |
| tags | ["architecture","dotnet","skill"] |
| version | 0.0.1 |
| author | dotnet-agent-harness |
| invocable | true |
| claudecode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| codexcli | {"short-description":".NET skill guidance for architecture tasks"} |
| opencode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| copilot | {} |
| geminicli | {} |
| antigravity | {} |
Strategic architectural patterns for EF Core data layers. Covers read/write model separation, aggregate boundary design, repository vs direct DbContext policy, N+1 query governance, row limit enforcement, and projection patterns. These patterns guide how to structure a data layer -- not how to write individual queries (see [skill:dotnet-efcore-patterns] for tactical usage).
Cross-references: [skill:dotnet-efcore-patterns] for tactical DbContext usage and migrations, [skill:dotnet-data-access-strategy] for technology selection, [skill:dotnet-csharp-dependency-injection] for service registration, [skill:dotnet-csharp-async-patterns] for async query patterns.
Examples in this skill use PostgreSQL (UseNpgsql). Substitute the provider package for your database:
| Database | Provider Package |
|---|---|
| PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL |
| SQL Server | Microsoft.EntityFrameworkCore.SqlServer |
| SQLite | Microsoft.EntityFrameworkCore.Sqlite |
All examples also require the core Microsoft.EntityFrameworkCore package (pulled in transitively by provider
packages).
Separate read models (queries) from write models (commands) to optimize each path independently. This is not full CQRS -- it is a practical separation using EF Core features.
// Write context: full change tracking, navigation properties, interceptors
public :
{
DbSet<Order> Orders => Set<Order>();
DbSet<Product> Products => Set<Product>();
{
modelBuilder.ApplyConfigurationsFromAssembly((WriteDbContext).Assembly);
}
}
:
{
DbSet<Order> Orders => Set<Order>();
DbSet<Product> Products => Set<Product>();
{
modelBuilder.ApplyConfigurationsFromAssembly((ReadDbContext).Assembly);
}
{
}
}
```text
```csharp
builder.Services.AddDbContext<WriteDbContext>(options =>
options.UseNpgsql(connectionString, npgsql =>
npgsql.EnableRetryOnFailure(maxRetryCount: )));
builder.Services.AddDbContext<ReadDbContext>(options =>
options.UseNpgsql(readReplicaConnectionString ?? connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
```text
| Scenario | Recommendation |
| ----------------------------------- | --------------------------------------------------- |
| Simple CRUD app | Single `DbContext` per-query `AsNoTracking()` |
| Read-heavy API complex queries | Separate read/write contexts |
| Read replica database | Separate contexts different connection strings |
| CQRS architecture | Separate contexts, possibly separate models |
**Start simple.** Use a single `DbContext` per-query `AsNoTracking()`
{
Id { ; ; }
CustomerId { ; ; } = !;
OrderStatus Status { ; ; }
DateTimeOffset CreatedAt { ; ; }
List<OrderItem> _items = [];
IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
{
(Status != OrderStatus.Draft)
InvalidOperationException();
_items.Add( OrderItem(productId, quantity, unitPrice));
}
}
{
Id { ; ; }
ProductId { ; ; }
Quantity { ; ; }
UnitPrice { ; ; }
{
ProductId = productId;
Quantity = quantity;
UnitPrice = unitPrice;
}
{ }
}
```text
```csharp
: <>
{
{
builder.HasKey(o => o.Id);
builder.Property(o => o.CustomerId).IsRequired().HasMaxLength();
builder.Property(o => o.Status).HasConversion<>();
builder.OwnsMany(o => o.Items, items =>
{
items.WithOwner().HasForeignKey();
items.Property(i => i.ProductId).IsRequired();
});
}
}
```text
**Load the entire aggregate** -- load aggregates. Use `Include()` the owned collections.
**Save through the aggregate root** -- call `SaveChangesAsync()` the root, child entities independently.
**Reference other aggregates ID** -- create navigation properties between aggregate roots. Use `CustomerId`
(foreign key ), `Customer` (navigation property).
**Keep aggregates small** -- large aggregates cause contention slow loads.
{
{
order = Order(command.CustomerId);
( item command.Items)
{
order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
}
db.Orders.Add(order);
db.SaveChangesAsync(ct);
order.Id;
}
}
```text
**Pros:** Simple, no abstraction overhead, full LINQ power, easy to debug. **Cons:** Business logic can leak query
methods, harder to unit test without a database.
```csharp
{
Task<Order?> GetByIdAsync( id, CancellationToken ct);
;
;
}
{
Task<Order?> GetByIdAsync( id, CancellationToken ct)
{
db.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
}
{
db.Orders.AddAsync(order, ct);
}
{
db.SaveChangesAsync(ct);
}
}
```text
**Pros:** Testable without a database, encapsulates query logic, enforces aggregate loading rules. **Cons:** Extra
abstraction layer, can become a leaky abstraction LINQ exposed, repository per aggregate can proliferate.
| Factor | Direct DbContext | Repository |
| -------------------- | ------------------------------ | ----------------------------- |
| Team size | Small, aligned | Large, varied experience |
| Test strategy | Integration tests real DB | Unit tests mocked repos |
| Query complexity | High (reports, projections) | Low-medium (CRUD, aggregates) |
| Aggregate discipline | Enforced convention | Enforced |
** ** (`<>`). --
- ( , ).
.
---
## +1
+1 .
, .
###
:
```
<>( =>
options.UseNpgsql(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging()
.EnableDetailedErrors());
```text
**Pattern : Lazy loading a loop**
```csharp
orders = db.Orders.ToListAsync(ct);
( order orders)
{
total = order.Items.Sum(i => i.Quantity * i.UnitPrice);
}
orders = db.Orders
.Include(o => o.Items)
.ToListAsync(ct);
```text
**Pattern : Querying inside a loop**
```csharp
( customerId customerIds)
{
orders = db.Orders
.Where(o => o.CustomerId == customerId)
.ToListAsync(ct);
}
orders = db.Orders
.Where(o => customerIds.Contains(o.CustomerId))
.ToListAsync(ct);
```text
**Pattern : Missing projection**
```csharp
orders = db.Orders
.Include(o => o.Items)
.Include(o => o.Customer)
.ToListAsync(ct);
dtos = orders.Select(o => OrderDto(...));
dtos = db.Orders
.Select(o => OrderDto
{
Id = o.Id,
CustomerName = o.Customer.Name,
ItemCount = o.Items.Count,
Total = o.Items.Sum(i => i.Quantity * i.UnitPrice)
})
.ToListAsync(ct);
```text
- **Disable lazy loading** -- install `Microsoft.EntityFrameworkCore.Proxies` configure
`UseLazyLoadingProxies()`. Eager loading via `Include()` projection via `Select()` makes data access .
- **Review queries code review** -- look loops that access navigation properties call `FindAsync` /
`FirstOrDefaultAsync` per element.
- **Use query tags** -- `db.Orders.TagWith()` makes queries identifiable logs profiling tools.
- **Set up EF Core logging development** -- every lazy load unexpected query visible the console output.
---
Unbounded queries are a production risk. Always limit the number of rows returned.
{
maxPageSize = ;
pageSize = Math.Min(pageSize, maxPageSize);
query = db.Orders
.AsNoTracking()
.Where(o => o.CustomerId == customerId);
(afterId.HasValue)
{
query = query.Where(o => o.Id > afterId.Value);
}
items = query
.OrderBy(o => o.Id)
.Take(pageSize + )
.Select(o => OrderSummary
{
Id = o.Id,
Status = o.Status,
CreatedAt = o.CreatedAt,
Total = o.Items.Sum(i => i.Quantity * i.UnitPrice)
})
.ToListAsync(ct);
hasNext = items.Count > pageSize;
(hasNext)
{
items.RemoveAt(items.Count - );
}
PagedResult<OrderSummary>
{
Items = items,
HasNextPage = hasNext,
NextCursor = hasNext ? items[^].Id :
};
}
```text
For admin UIs small datasets exact page numbers matter:
```csharp
page = db.Orders
.AsNoTracking()
.OrderBy(o => o.CreatedAt)
.Skip((pageNumber - ) * pageSize)
.Take(pageSize)
.ToListAsync(ct);
```text
**Warning:** Offset pagination degrades at scale -- `OFFSET ` forces the database to scan discard , rows.
Prefer keyset pagination user-facing APIs.
Set a hard upper bound all queries to prevent accidental full-table scans:
```csharp
:
{
MaxRows = ;
{
queryExpression;
}
}
```text
**Practical approach:** Rather than a runtime interceptor, enforce row limits through:
**Code review convention** -- every `ToListAsync()` must have `Take(N)` be a `Select()` projection `Take(N)`.
**API-level page size caps** -- validate `pageSize` the request pipeline before it reaches the query.
**Query tags** -- annotate queries `TagWith()` to identify unbounded queries monitoring.
---
Projections (`Select()`) are the most effective optimization read queries. They reduce data transfer, skip change
tracking, eliminate N+ risks.
```csharp
{
Id { ; ; }
CustomerName { ; ; } = !;
ItemCount { ; ; }
Total { ; ; }
DateTimeOffset CreatedAt { ; ; }
}
summaries = db.Orders
.Select(o => OrderSummary
{
Id = o.Id,
CustomerName = o.Customer.Name,
ItemCount = o.Items.Count,
Total = o.Items.Sum(i => i.Quantity * i.UnitPrice),
CreatedAt = o.CreatedAt
})
.OrderByDescending(o => o.CreatedAt)
.Take()
.ToListAsync(ct);
```text
| Concern | Entity + Include | Projection (Select) |
| ------------------- | ------------------------- | --------------------- |
| Change tracking | Yes (unless AsNoTracking) | No |
| Data transferred | All columns | Only selected columns |
| N+ risk | Yes (lazy nav props) | No (computed SQL) |
| Cartesian explosion | Yes (multiple Includes) | No (single query) |
| Type safety | Entity types | DTO/ |
**:** - .
.
---
##
- ** ** --
- ** ** --
- ** ** --
- ** ** -- `()` `()`
- ** ** --
- ** ** -- `()`
- ** **
---
##
1. ** ** -- (., ``)
(., ``). -
.
2. ** ** (`<>`) -- -
. .
3. ** `()`** -- +1 .
`()` `()` .
4. ** `<>` ** --
(., , - ). (`<>`,
`?`).
5. ** `()` `()` ** --
. .
6. ** ** --
. .
---
##
- [ ](:
- [ ](:
- [ ](:
- [- ](:
- [ ](:
- [ ](:
Primary approach: Use Serena symbol operations for efficient code navigation:
serena_find_symbol instead of text searchserena_get_symbols_overview for file organizationserena_find_referencing_symbols for impact analysisserena_replace_symbol_body for clean modificationsWhen to use Serena vs traditional tools:
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"