dotnet-dev
SKILL.md
.NET Core Development Expert Skill
คุณเป็น .NET Core Development Expert ที่เชี่ยวชาญในการพัฒนาระบบ Enterprise-grade ด้วย Microsoft Stack
🎯 Core Principles (จาก User Preferences)
1. Domain-First Approach
- เริ่มจาก Domain Model เสมอ - คิดจาก Business Requirements ก่อน
- ออกแบบ Entities และ Relationships ก่อนเขียน Code
- ใช้ Rich Domain Models แทน Anemic Models
2. Architecture Preferences
- Clean Architecture เป็นหลัก
- Repository Pattern + Unit of Work สำหรับ Data Access
- CQRS with MediatR สำหรับ Complex Applications
- Dependency Injection ทุกที่
3. Technology Stack
- .NET 8+ (Latest LTS)
- Entity Framework Core (Code First)
- PostgreSQL หรือ SQL Server เป็น Primary Database
- Redis สำหรับ Caching
- ASP.NET Core MVC / Web API / Minimal APIs
🗄️ Database Provider Selection
เมื่อไหร่ใช้ PostgreSQL
- Open source, ไม่มีค่า license
- ต้องการ JSONB columns
- Full-text search ภาษาไทย
- Array data types
- Linux/Container deployment
เมื่อไหร่ใช้ SQL Server
- Enterprise environment ที่มี license อยู่แล้ว
- ต้องการ Temporal Tables (System-Versioned)
- Row-Level Security (RLS)
- Always Encrypted
- Integration กับ Azure services
- Legacy systems ที่ใช้ SQL Server อยู่
📚 เมื่อต้องการข้อมูลล่าสุดจาก Microsoft Learn
ใช้ MCP Server microsoft-learn เพื่อค้นหา documentation ล่าสุด:
# ค้นหา documentation
npx mcporter call --stdio "streamable-http https://learn.microsoft.com/api/mcp" \
search query:"Entity Framework Core SQL Server"
# หรือใช้ผ่าน mcp tool โดยตรงถ้า configure ไว้แล้ว
# mcp__microsoft-learn__search query:"ASP.NET Core authentication"
เมื่อไหร่ควรใช้ Microsoft Learn MCP:
- ต้องการ syntax หรือ API ล่าสุด
- ไม่แน่ใจเกี่ยวกับ breaking changes ใน version ใหม่
- ต้องการ best practices จาก Microsoft
- ค้นหา configuration options ที่ถูกต้อง
🏗️ Project Structure (Clean Architecture)
Solution/
├── src/
│ ├── Domain/ # Core business logic
│ │ ├── Entities/
│ │ ├── ValueObjects/
│ │ ├── Enums/
│ │ ├── Events/
│ │ └── Exceptions/
│ │
│ ├── Application/ # Use cases & business rules
│ │ ├── Common/
│ │ │ ├── Interfaces/
│ │ │ ├── Behaviors/
│ │ │ └── Mappings/
│ │ ├── Features/
│ │ │ └── [Feature]/
│ │ │ ├── Commands/
│ │ │ └── Queries/
│ │ └── DTOs/
│ │
│ ├── Infrastructure/ # External concerns
│ │ ├── Data/
│ │ │ ├── Configurations/
│ │ │ ├── Repositories/
│ │ │ ├── Migrations/
│ │ │ └── ApplicationDbContext.cs
│ │ ├── Services/
│ │ └── DependencyInjection.cs
│ │
│ └── WebApi/ # Presentation layer
│ ├── Controllers/
│ ├── Middleware/
│ ├── Filters/
│ └── Program.cs
│
├── tests/
│ ├── Domain.Tests/
│ ├── Application.Tests/
│ └── Integration.Tests/
│
└── [AppName].AppHost/ # .NET Aspire (optional)
└── Program.cs
📋 Code Patterns & Templates
1. Base Entity
public abstract class BaseEntity
{
public long Id { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public string? CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
public bool IsDeleted { get; set; } = false;
}
public abstract class BaseEntity<TKey> : BaseEntity
{
public new TKey Id { get; set; } = default!;
}
2. Repository Interface
public interface IRepository<T> where T : BaseEntity
{
Task<T?> GetByIdAsync(long id, CancellationToken ct = default);
Task<IEnumerable<T>> GetAllAsync(CancellationToken ct = default);
Task<T> AddAsync(T entity, CancellationToken ct = default);
void Update(T entity);
void Delete(T entity);
Task<bool> ExistsAsync(long id, CancellationToken ct = default);
IQueryable<T> Query();
}
3. Unit of Work
public interface IUnitOfWork : IDisposable
{
// Repositories
IRepository<Customer> Customers { get; }
IRepository<Order> Orders { get; }
// Transaction management
Task<int> SaveChangesAsync(CancellationToken ct = default);
Task BeginTransactionAsync(CancellationToken ct = default);
Task CommitAsync(CancellationToken ct = default);
Task RollbackAsync(CancellationToken ct = default);
}
4. Generic Repository Implementation
public class Repository<T> : IRepository<T> where T : BaseEntity
{
protected readonly ApplicationDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(ApplicationDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public virtual async Task<T?> GetByIdAsync(long id, CancellationToken ct = default)
=> await _dbSet.FirstOrDefaultAsync(e => e.Id == id && !e.IsDeleted, ct);
public virtual async Task<IEnumerable<T>> GetAllAsync(CancellationToken ct = default)
=> await _dbSet.Where(e => !e.IsDeleted).ToListAsync(ct);
public virtual async Task<T> AddAsync(T entity, CancellationToken ct = default)
{
await _dbSet.AddAsync(entity, ct);
return entity;
}
public virtual void Update(T entity)
{
entity.UpdatedAt = DateTime.UtcNow;
_dbSet.Update(entity);
}
public virtual void Delete(T entity)
{
entity.IsDeleted = true;
entity.UpdatedAt = DateTime.UtcNow;
Update(entity);
}
public virtual async Task<bool> ExistsAsync(long id, CancellationToken ct = default)
=> await _dbSet.AnyAsync(e => e.Id == id && !e.IsDeleted, ct);
public virtual IQueryable<T> Query()
=> _dbSet.Where(e => !e.IsDeleted).AsQueryable();
}
5. DbContext - Multi-Database Support
public class ApplicationDbContext : DbContext
{
private readonly ICurrentUserService _currentUser;
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options,
ICurrentUserService currentUser) : base(options)
{
_currentUser = currentUser;
}
// DbSets
public DbSet<Customer> Customers => Set<Customer>();
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Apply all configurations from assembly
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(ApplicationDbContext).Assembly);
// Global query filter for soft delete
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(BaseEntity).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType)
.HasQueryFilter(
GenerateSoftDeleteFilter(entityType.ClrType));
}
}
}
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
foreach (var entry in ChangeTracker.Entries<BaseEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedAt = DateTime.UtcNow;
entry.Entity.CreatedBy = _currentUser.UserId;
break;
case EntityState.Modified:
entry.Entity.UpdatedAt = DateTime.UtcNow;
entry.Entity.UpdatedBy = _currentUser.UserId;
break;
}
}
return await base.SaveChangesAsync(ct);
}
private static LambdaExpression GenerateSoftDeleteFilter(Type type)
{
var parameter = Expression.Parameter(type, "e");
var property = Expression.Property(parameter, nameof(BaseEntity.IsDeleted));
var condition = Expression.Equal(property, Expression.Constant(false));
return Expression.Lambda(condition, parameter);
}
}
6. Dependency Injection - Database Provider
// Infrastructure/DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
var dbProvider = configuration.GetValue<string>("DatabaseProvider") ?? "PostgreSQL";
services.AddDbContext<ApplicationDbContext>(options =>
{
switch (dbProvider)
{
case "SqlServer":
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
sqlOptions =>
{
sqlOptions.MigrationsAssembly(
typeof(ApplicationDbContext).Assembly.FullName);
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
break;
case "PostgreSQL":
default:
options.UseNpgsql(
configuration.GetConnectionString("DefaultConnection"),
npgsqlOptions =>
{
npgsqlOptions.MigrationsAssembly(
typeof(ApplicationDbContext).Assembly.FullName);
npgsqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorCodesToAdd: null);
});
break;
}
});
// Repositories
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped<IUnitOfWork, UnitOfWork>();
// Caching
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration.GetConnectionString("Redis");
options.InstanceName = "App_";
});
return services;
}
}
7. appsettings.json - Database Configuration
{
"DatabaseProvider": "SqlServer",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyApp;User Id=sa;Password=YourPassword;TrustServerCertificate=True;",
"Redis": "localhost:6379"
}
}
{
"DatabaseProvider": "PostgreSQL",
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=myapp;Username=postgres;Password=YourPassword;",
"Redis": "localhost:6379"
}
}
8. .NET Aspire AppHost - Multi-Database
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// Choose database provider
var usePostgres = builder.Configuration.GetValue<bool>("UsePostgres", true);
IResourceBuilder<IResourceWithConnectionString> database;
if (usePostgres)
{
var postgres = builder.AddPostgres("postgres")
.WithPgAdmin()
.AddDatabase("appdb");
database = postgres;
}
else
{
var sqlserver = builder.AddSqlServer("sqlserver")
.AddDatabase("appdb");
database = sqlserver;
}
var redis = builder.AddRedis("redis")
.WithRedisCommander();
// API Project
var api = builder.AddProject<Projects.WebApi>("api")
.WithReference(database)
.WithReference(redis)
.WithExternalHttpEndpoints();
builder.Build().Run();
🔧 Common Tasks
Migration Commands
# Add migration
dotnet ef migrations add InitialCreate -p Infrastructure -s WebApi
# Update database
dotnet ef database update -p Infrastructure -s WebApi
# Generate SQL script
dotnet ef migrations script -p Infrastructure -s WebApi -o ./migrations.sql
# Remove last migration
dotnet ef migrations remove -p Infrastructure -s WebApi
NuGet Packages
Common Packages
<!-- Domain/Application -->
<PackageReference Include="MediatR" Version="12.*" />
<PackageReference Include="FluentValidation" Version="11.*" />
<PackageReference Include="AutoMapper" Version="13.*" />
<!-- Infrastructure - Core -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.*" />
<!-- WebApi -->
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
<PackageReference Include="Serilog.AspNetCore" Version="8.*" />
PostgreSQL Packages
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.*" />
<!-- Aspire -->
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.*" />
SQL Server Packages
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.*" />
<!-- Aspire -->
<PackageReference Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="8.*" />
📖 Reference Files
ดูไฟล์เพิ่มเติมใน:
references/ef-core-patterns.md- EF Core advanced patterns (PostgreSQL + SQL Server)references/aspire-setup.md- .NET Aspire configurationreferences/testing-patterns.md- Testing strategiesreferences/microsoft-learn-mcp.md- MCP usage guide
⚠️ Best Practices
- Always use async/await - ไม่ block threads
- Use CancellationToken - ทุก async method
- Validate inputs - FluentValidation ก่อน process
- Log appropriately - Structured logging with Serilog
- Handle exceptions - Global exception handler + Result pattern
- Write tests - Unit tests for business logic, Integration tests for APIs
- Use DTOs - ไม่ expose Entities ตรงๆ
- Soft delete - ใช้ IsDeleted flag แทน hard delete
- Audit trail - CreatedAt, UpdatedAt, CreatedBy, UpdatedBy
- Use transactions - สำหรับ operations ที่ต้อง atomic
- Enable retry on failure - สำหรับ database connections
- Use connection resiliency - ทั้ง PostgreSQL และ SQL Server
Weekly Installs
6
Repository
mounchons/agent…ketplaceFirst Seen
Mar 1, 2026
Security Audits
Installed on
opencode6
gemini-cli6
antigravity6
github-copilot6
codex6
kimi-cli6