it
C# Entity Framework กับ Distributed System —
EF Core ใน Distributed System

Entity Framework Core เป็น ORM สำหรับ .NET เขียน Database Operations ด้วย C# Objects Distributed System หลาย Services หลาย Servers สื่อสารผ่าน Network
เนื้อหาเกี่ยวข้อง — NFS v4 Kerberos Multi-cloud Strategy
ใช้ EF Core กับ Microservices แต่ละ Service มี DbContext แยก Database per Service Domain Events สื่อสารระหว่าง Services
เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ colocation hosting คือ
EF Core Setup และ Patterns
// === EF Core สำหรับ Distributed System ===
// dotnet add package Microsoft.EntityFrameworkCore
// dotnet add package Microsoft.EntityFrameworkCore.SqlServer
// dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
// 1. Domain Models
public class Order
{
public Guid Id { get; set; } = Guid.NewGuid();
public string CustomerId { get; set; } = "";
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; } = OrderStatus.Pending;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public List<OrderItem> Items { get; set; } = new();
public List<OutboxMessage> OutboxMessages { get; set; } = new();
}
public class OrderItem
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid OrderId { get; set; }
public string ProductId { get; set; } = "";
public string ProductName { get; set; } = "";
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}
// Outbox Pattern — ส่ง Events อย่างน่าเชื่อถือ
public class OutboxMessage
{
public Guid Id { get; set; } = Guid.NewGuid();
public string EventType { get; set; } = "";
public string Payload { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? ProcessedAt { get; set; }
public bool IsProcessed { get; set; }
}
public enum OrderStatus { Pending, Confirmed, Shipped, Delivered, Canceled }
// 2. DbContext
public class OrderDbContext : DbContext
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
public DbSet<OutboxMessage> OutboxMessages => Set<OutboxMessage>();
public OrderDbContext(DbContextOptions<OrderDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(e =>
{
e.HasKey(o => o.Id);
e.Property(o => o.TotalAmount).HasPrecision(18, 2);
e.HasMany(o => o.Items).WithOne().HasForeignKey(i => i.OrderId);
e.HasIndex(o => o.CustomerId);
e.HasIndex(o => o.Status);
// Concurrency Token
e.Property(o => o.UpdatedAt).IsConcurrencyToken();
});
modelBuilder.Entity<OutboxMessage>(e =>
{
e.HasIndex(m => m.IsProcessed);
});
}
// SaveChanges with Outbox
public override async Task<int> SaveChangesAsync(
CancellationToken ct = default)
{
// Auto-set UpdatedAt
foreach (var entry in ChangeTracker.Entries<Order>()
.Where(e => e.State == EntityState.Modified))
{
entry.Entity.UpdatedAt = DateTime.UtcNow;
}
return await base.SaveChangesAsync(ct);
}
}
// 3. Registration (Program.cs)
// builder.Services.AddDbContext<OrderDbContext>(options =>
// options.UseNpgsql(builder.Configuration
// .GetConnectionString("OrderDb")));
// 4. Migration Commands
// dotnet ef migrations add InitialCreate
// dotnet ef database update
Console.WriteLine("EF Core Distributed Setup:");
Console.WriteLine(" DbContext: OrderDbContext");
Console.WriteLine(" Models: Order, OrderItem, OutboxMessage");
Console.WriteLine(" Patterns: Outbox, Concurrency Token");
CQRS Pattern

// === CQRS Pattern with EF Core ===
using MediatR;
// 1. Command — Write Side
public record CreateOrderCommand(
string CustomerId,
List<OrderItemDto> Items
) : IRequest<Guid>;
public record OrderItemDto(string ProductId, string Name, int Qty, decimal Price);
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
private readonly OrderDbContext _db;
public CreateOrderHandler(OrderDbContext db) => _db = db;
public async Task<Guid> Handle(
CreateOrderCommand cmd, CancellationToken ct)
{
var order = new Order
{
CustomerId = cmd.CustomerId,
Items = cmd.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
ProductName = i.Name,
Quantity = i.Qty,
UnitPrice = i.Price,
}).ToList(),
};
order.TotalAmount = order.Items.Sum(i => i.Total);
// Outbox — Domain Event
order.OutboxMessages.Add(new OutboxMessage
{
EventType = "OrderCreated",
Payload = System.Text.Json.JsonSerializer.Serialize(new
{
OrderId = order.Id,
order.CustomerId,
order.TotalAmount,
ItemCount = order.Items.Count,
}),
});
_db.Orders.Add(order);
await _db.SaveChangesAsync(ct);
return order.Id;
}
}
// 2. Query — Read Side (ใช้ Dapper สำหรับ Performance)
public record GetOrderQuery(Guid OrderId) : IRequest<OrderDetailDto?>;
public record OrderDetailDto(
Guid Id, string CustomerId, decimal Total,
string Status, DateTime CreatedAt, List<OrderItemDto> Items);
// public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDetailDto?>
// {
// private readonly IDbConnection _connection;
//
// public async Task<OrderDetailDto?> Handle(
// GetOrderQuery query, CancellationToken ct)
// {
// var sql = @"
// SELECT o.Id, o.CustomerId, o.TotalAmount as Total,
// o.Status, o.CreatedAt
// FROM Orders o WHERE o.Id = @OrderId;
//
// SELECT i.ProductId, i.ProductName as Name,
// i.Quantity as Qty, i.UnitPrice as Price
// FROM OrderItems i WHERE i.OrderId = @OrderId;";
//
// using var multi = await _connection.QueryMultipleAsync(
// sql, new { query.OrderId });
//
// var order = await multi.ReadSingleOrDefaultAsync<OrderDetailDto>();
// if (order == null) return null;
//
// var items = (await multi.ReadAsync<OrderItemDto>()).ToList();
// return order with { Items = items };
// }
// }
// 3. API Endpoints
// app.MapPost("/orders", async (CreateOrderCommand cmd, IMediator mediator) =>
// {
// var orderId = await mediator.Send(cmd);
// return Results.Created($"/orders/{orderId}", new { orderId });
// });
//
// app.MapGet("/orders/{id}", async (Guid id, IMediator mediator) =>
// {
// var order = await mediator.Send(new GetOrderQuery(id));
// return order is null ? Results.NotFound() : Results.Ok(order);
// });
Console.WriteLine("CQRS Pattern:");
Console.WriteLine(" Write: EF Core + MediatR Commands");
Console.WriteLine(" Read: Dapper + Raw SQL Queries");
Console.WriteLine(" Events: Outbox Pattern for reliability");
Distributed Patterns
// === Distributed Patterns ===
// 1. Outbox Message Processor (Background Service)
// public class OutboxProcessor : BackgroundService
// {
// protected override async Task ExecuteAsync(CancellationToken ct)
// {
// while (!ct.IsCancellationRequested)
// {
// using var scope = _services.CreateScope();
// var db = scope.ServiceProvider
// .GetRequiredService<OrderDbContext>();
//
// var messages = await db.OutboxMessages
// .Where(m => !m.IsProcessed)
// .OrderBy(m => m.CreatedAt)
// .Take(10)
// .ToListAsync(ct);
//
// foreach (var msg in messages)
// {
// await _messageBus.PublishAsync(msg.EventType, msg.Payload);
// msg.IsProcessed = true;
// msg.ProcessedAt = DateTime.UtcNow;
// }
//
// await db.SaveChangesAsync(ct);
// await Task.Delay(1000, ct);
// }
// }
// }
// 2. Saga Pattern — Distributed Transaction
// public class OrderSaga
// {
// public async Task Execute(CreateOrderCommand cmd)
// {
// // Step 1: Create Order (Order Service)
// var orderId = await _orderService.CreateOrder(cmd);
//
// try
// {
// // Step 2: Reserve Inventory (Inventory Service)
// await _inventoryService.ReserveItems(orderId, cmd.Items);
//
// // Step 3: Process Payment (Payment Service)
// await _paymentService.Charge(orderId, cmd.TotalAmount);
//
// // Step 4: Confirm Order
// await _orderService.ConfirmOrder(orderId);
// }
// catch (Exception)
// {
// // Compensating Transactions
// await _inventoryService.ReleaseItems(orderId);
// await _orderService.CancelOrder(orderId);
// throw;
// }
// }
// }
// 3. Distributed Caching with Redis
// builder.Services.AddStackExchangeRedisCache(options =>
// {
// options.Configuration = "localhost:6379";
// options.InstanceName = "OrderService_";
// });
//
// public class CachedOrderQuery
// {
// private readonly IDistributedCache _cache;
// private readonly OrderDbContext _db;
//
// public async Task<Order?> GetOrderAsync(Guid id)
// {
// var cacheKey = $"order:{id}";
// var cached = await _cache.GetStringAsync(cacheKey);
//
// if (cached != null)
// return JsonSerializer.Deserialize<Order>(cached);
//
// var order = await _db.Orders
// .Include(o => o.Items)
// .FirstOrDefaultAsync(o => o.Id == id);
//
// if (order != null)
// {
// await _cache.SetStringAsync(cacheKey,
// JsonSerializer.Serialize(order),
// new DistributedCacheEntryOptions
// {
// AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
// SlidingExpiration = TimeSpan.FromMinutes(2),
// });
// }
// return order;
// }
// }
// 4. Health Check
// builder.Services.AddHealthChecks()
// .AddDbContextCheck<OrderDbContext>("database")
// .AddRedis("localhost:6379", name: "redis")
// .AddRabbitMQ("amqp://localhost", name: "rabbitmq");
var patterns = new Dictionary<string, string>
{
["Outbox Pattern"] = "ส่ง Events อย่างน่าเชื่อถือ Atomically กับ DB",
["Saga Pattern"] = "Distributed Transactions ด้วย Compensating Actions",
["CQRS"] = "แยก Read/Write สำหรับ Scale แยกกัน",
["Distributed Cache"] = "Redis Cache ลด Database Load",
["Health Checks"] = "ตรวจสอบสถานะ Database, Redis, RabbitMQ",
};
Console.WriteLine("Distributed Patterns:");
foreach (var (pattern, desc) in patterns)
Console.WriteLine($" {pattern}: {desc}");
Best Practices
- Database per Service: แต่ละ Service มี Database แยก ไม่แชร์ข้าม Services
- Outbox Pattern: ใช้ Outbox ส่ง Events อย่างน่าเชื่อถือ Atomic กับ DB Write
- Concurrency: ใช้ Concurrency Token ป้องกัน Lost Updates
- Connection Pooling: ตั้ง MaxPoolSize เหมาะสม ไม่เปิด Connection ค้าง
- Retry Policy: ใช้ Polly สำหรับ Transient Fault Handling
- Migration: ใช้ EF Migrations จัดการ Schema Changes อัตโนมัติ
Entity Framework Core คืออะไร
ORM สำหรับ .NET เขียน Database Operations ด้วย C# Objects แทน SQL รองรับ SQL Server PostgreSQL MySQL SQLite Cosmos DB Migrations LINQ Change Tracking
แนะนำเพิ่มเติม — ติดตาม XM Signal
เนื้อหาเกี่ยวข้อง — บทความที่เกี่ยวข้อง: Strapi CMS Serverless Architecture





