Technology

C# Entity Framework Distributed System

c entity framework distributed system
C# Entity Framework Distributed System | SiamCafe Blog
2026-01-25· อ. บอม — SiamCafe.net· 11,151 คำ

EF Core ใน Distributed System

Entity Framework Core เป็น ORM สำหรับ .NET เขียน Database Operations ด้วย C# Objects Distributed System หลาย Services หลาย Servers สื่อสารผ่าน Network

ใช้ EF Core กับ Microservices แต่ละ Service มี DbContext แยก Database per Service Domain Events สื่อสารระหว่าง Services

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

Entity Framework Core คืออะไร

ORM สำหรับ .NET เขียน Database Operations ด้วย C# Objects แทน SQL รองรับ SQL Server PostgreSQL MySQL SQLite Cosmos DB Migrations LINQ Change Tracking

Distributed System คืออะไร

ระบบหลาย Services หลาย Servers สื่อสาร Network Microservices Architecture Database แยก Message Queue CAP Theorem Consistency Availability Partition Tolerance

EF Core ใช้กับ Microservices อย่างไร

แต่ละ Service DbContext แยก Database per Service ไม่แชร์ Database Domain Events สื่อสาร Outbox Pattern ส่ง Events น่าเชื่อถือ Saga Pattern Distributed Transactions

CQRS คืออะไร

Command Query Responsibility Segregation แยก Read Write Model Write EF Core SQL Database Read Dapper Redis Elasticsearch Scale แยกกัน Read มากกว่า Write

สรุป

EF Core ใช้กับ Distributed System ด้วย Database per Service Outbox Pattern ส่ง Events น่าเชื่อถือ CQRS แยก Read Write Saga Pattern สำหรับ Distributed Transactions Redis Cache ลด Load Concurrency Token ป้องกัน Lost Updates

📖 บทความที่เกี่ยวข้อง

XDR Platform Distributed Systemอ่านบทความ → C# Entity Framework Blue Green Canary Deployอ่านบทความ → C# Entity Framework Stream Processingอ่านบทความ → C# Entity Framework Clean Architectureอ่านบทความ → HTTP/3 QUIC Distributed Systemอ่านบทความ →

📚 ดูบทความทั้งหมด →