it

C# Entity Framework กับ Distributed System —

C# Entity Framework กับ Distributed System —

EF Core ใน Distributed System

C# Entity Framework กับ 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

C# Entity Framework กับ Distributed System —
// === 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

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง