Technology

C# Entity Framework Agile Scrum Kanban

C# Entity Framework Agile Scrum Kanban | SiamCafe Blog
2025-10-01· อ. บอม — SiamCafe.net· 1,688 คำ

C# Entity Framework Agile Scrum Kanban คืออะไร

Entity Framework (EF Core) เป็น ORM (Object-Relational Mapper) สำหรับ .NET ที่ช่วยให้ทำงานกับฐานข้อมูลผ่าน C# objects โดยไม่ต้องเขียน SQL ตรงๆ Agile เป็นแนวคิดการพัฒนาซอฟต์แวร์ที่เน้น iterations สั้นๆ Scrum และ Kanban เป็น frameworks ยอดนิยมภายใต้ Agile บทความนี้รวมการใช้ EF Core ในโปรเจกต์ที่บริหารด้วย Scrum/Kanban รวมถึง database migrations ใน sprint cycles, Kanban board สำหรับ data model changes และ best practices สำหรับทีม agile

EF Core พื้นฐาน

# efcore_basics.py — EF Core concepts (Python equivalent demo)
import json

class EFCoreBasics:
    CONCEPTS = {
        "dbcontext": {
            "name": "DbContext",
            "description": "Entry point สำหรับทำงานกับ database (Unit of Work pattern)",
            "csharp": "public class AppDbContext : DbContext { public DbSet Users { get; set; } }",
        },
        "dbset": {
            "name": "DbSet",
            "description": "Collection ของ entities ที่ map กับ database table",
            "csharp": "DbSet Orders → maps to [Orders] table",
        },
        "migrations": {
            "name": "Migrations",
            "description": "Version control สำหรับ database schema changes",
            "csharp": "dotnet ef migrations add CreateUsersTable",
        },
        "linq": {
            "name": "LINQ Queries",
            "description": "Query database ด้วย C# syntax (translated to SQL)",
            "csharp": "var users = context.Users.Where(u => u.IsActive).ToList();",
        },
        "code_first": {
            "name": "Code-First Approach",
            "description": "ออกแบบ C# classes ก่อน → generate database schema",
            "csharp": "Model classes → Migrations → Database",
        },
    }

    CSHARP_EXAMPLE = """
// Models/User.cs
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public List Orders { get; set; }
}

// Data/AppDbContext.cs
public class AppDbContext : DbContext
{
    public DbSet Users { get; set; }
    public DbSet Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder mb)
    {
        mb.Entity().HasIndex(u => u.Email).IsUnique();
        mb.Entity().Property(u => u.Name).HasMaxLength(100);
    }
}

// Usage
using var context = new AppDbContext();
var activeUsers = await context.Users
    .Where(u => u.CreatedAt > DateTime.UtcNow.AddDays(-30))
    .Include(u => u.Orders)
    .OrderByDescending(u => u.CreatedAt)
    .ToListAsync();
"""

    def show_concepts(self):
        print("=== EF Core Concepts ===\n")
        for key, concept in self.CONCEPTS.items():
            print(f"[{concept['name']}]")
            print(f"  {concept['description']}")
            print(f"  C#: {concept['csharp']}")
            print()

    def show_example(self):
        print("=== C# Example ===")
        print(self.CSHARP_EXAMPLE[:500])

ef = EFCoreBasics()
ef.show_concepts()
ef.show_example()

Scrum สำหรับ EF Core Projects

# scrum_ef.py — Scrum workflow with EF Core
import json
import random

class ScrumEFCore:
    SPRINT_WORKFLOW = {
        "planning": {
            "phase": "Sprint Planning",
            "ef_tasks": [
                "Review pending migration requests",
                "Estimate data model changes (story points)",
                "Plan database migration order (dependencies)",
                "Assign EF Core tasks to developers",
            ],
        },
        "development": {
            "phase": "Sprint Development (2 weeks)",
            "ef_tasks": [
                "Create feature branch for data model changes",
                "Add EF Core migration: dotnet ef migrations add ",
                "Write LINQ queries for new features",
                "Unit test with InMemoryDatabase provider",
                "Integration test with test database",
            ],
        },
        "review": {
            "phase": "Sprint Review",
            "ef_tasks": [
                "Demo new database features",
                "Review migration scripts (peer review)",
                "Check query performance (SQL profiling)",
                "Validate data integrity",
            ],
        },
        "deployment": {
            "phase": "Sprint Deployment",
            "ef_tasks": [
                "Apply migrations to staging: dotnet ef database update",
                "Run integration tests on staging",
                "Apply migrations to production (with rollback plan)",
                "Monitor database performance post-deploy",
            ],
        },
    }

    STORY_EXAMPLES = [
        {"id": "US-101", "story": "As a user, I can update my profile", "ef_task": "Add ProfileImage column to Users table", "points": 3},
        {"id": "US-102", "story": "As an admin, I can view order analytics", "ef_task": "Create OrderAnalytics view + LINQ query", "points": 5},
        {"id": "US-103", "story": "As a user, I can save favorite products", "ef_task": "Create Favorites junction table + navigation properties", "points": 5},
        {"id": "US-104", "story": "As a system, soft-delete instead of hard-delete", "ef_task": "Add IsDeleted column + global query filter", "points": 8},
    ]

    def show_workflow(self):
        print("=== Scrum + EF Core Workflow ===\n")
        for key, phase in self.SPRINT_WORKFLOW.items():
            print(f"[{phase['phase']}]")
            for task in phase["ef_tasks"][:3]:
                print(f"  • {task}")
            print()

    def show_stories(self):
        print("=== User Stories with EF Tasks ===")
        for story in self.STORY_EXAMPLES:
            print(f"  [{story['id']}] ({story['points']}pts) {story['story']}")
            print(f"    EF: {story['ef_task']}")

scrum = ScrumEFCore()
scrum.show_workflow()
scrum.show_stories()

Kanban Board สำหรับ Database Changes

# kanban_ef.py — Kanban board for EF Core
import json
import random

class KanbanEFCore:
    COLUMNS = {
        "backlog": {
            "name": "Backlog",
            "wip_limit": None,
            "items": [
                {"task": "Add audit log table", "type": "Migration", "priority": "Medium"},
                {"task": "Optimize N+1 query in OrderService", "type": "Performance", "priority": "High"},
                {"task": "Add full-text search index", "type": "Migration", "priority": "Low"},
            ],
        },
        "analysis": {
            "name": "Analysis / Design",
            "wip_limit": 2,
            "items": [
                {"task": "Design multi-tenant schema", "type": "Architecture", "priority": "High"},
            ],
        },
        "development": {
            "name": "In Development",
            "wip_limit": 3,
            "items": [
                {"task": "Add SoftDelete global filter", "type": "Migration", "priority": "High"},
                {"task": "Implement repository pattern", "type": "Refactor", "priority": "Medium"},
            ],
        },
        "review": {
            "name": "Code Review",
            "wip_limit": 2,
            "items": [
                {"task": "Migration: Add UserPreferences table", "type": "Migration", "priority": "Medium"},
            ],
        },
        "testing": {
            "name": "Testing",
            "wip_limit": 2,
            "items": [],
        },
        "done": {
            "name": "Done",
            "wip_limit": None,
            "items": [
                {"task": "Add index on Orders.CreatedAt", "type": "Performance", "priority": "High"},
            ],
        },
    }

    def show_board(self):
        print("=== Kanban Board — EF Core Tasks ===\n")
        for key, col in self.COLUMNS.items():
            wip = f" (WIP: {col['wip_limit']})" if col["wip_limit"] else ""
            count = len(col["items"])
            print(f"[{col['name']}{wip}] — {count} items")
            for item in col["items"][:2]:
                print(f"  • [{item['priority']}] {item['task']} ({item['type']})")
            print()

    def metrics(self):
        print("=== Kanban Metrics ===")
        metrics = {
            "Lead Time (avg)": f"{random.randint(3, 10)} days",
            "Cycle Time (avg)": f"{random.randint(1, 5)} days",
            "Throughput": f"{random.randint(5, 15)} items/week",
            "WIP": f"{random.randint(3, 8)} items",
            "Blocked": f"{random.randint(0, 2)} items",
        }
        for m, v in metrics.items():
            print(f"  {m}: {v}")

kanban = KanbanEFCore()
kanban.show_board()
kanban.metrics()

Migration Management

# migrations.py — EF Core migration management
import json

class MigrationManagement:
    COMMANDS = {
        "add": "dotnet ef migrations add AddUserPreferences",
        "update": "dotnet ef database update",
        "rollback": "dotnet ef database update PreviousMigrationName",
        "remove": "dotnet ef migrations remove",
        "script": "dotnet ef migrations script --idempotent -o migration.sql",
        "list": "dotnet ef migrations list",
        "bundle": "dotnet ef migrations bundle -o efbundle",
    }

    BEST_PRACTICES = {
        "naming": {
            "name": "Migration Naming Convention",
            "convention": "YYYYMMDD_Description (เช่น 20250115_AddUserPreferences)",
            "bad": "Migration1, Update2, Fix3",
        },
        "small_migrations": {
            "name": "Small, Focused Migrations",
            "do": "1 migration = 1 logical change (add table, add column)",
            "dont": "1 migration = 10 different changes (hard to rollback)",
        },
        "data_migrations": {
            "name": "Data Migrations แยกจาก Schema Migrations",
            "do": "Schema migration ก่อน → Data migration แยก → Cleanup migration",
            "dont": "รวม schema + data + cleanup ใน migration เดียว",
        },
        "review": {
            "name": "Review Generated SQL",
            "do": "dotnet ef migrations script → review SQL ก่อน apply production",
            "dont": "Apply migrations โดยไม่ดู generated SQL",
        },
    }

    CSHARP_MIGRATION = """
// Migrations/20250115_AddUserPreferences.cs
public partial class AddUserPreferences : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "UserPreferences",
            columns: table => new
            {
                Id = table.Column(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                UserId = table.Column(nullable: false),
                Theme = table.Column(maxLength: 20, defaultValue: "light"),
                Language = table.Column(maxLength: 10, defaultValue: "th"),
                NotificationsEnabled = table.Column(defaultValue: true),
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_UserPreferences", x => x.Id);
                table.ForeignKey("FK_UserPreferences_Users",
                    x => x.UserId, "Users", "Id", onDelete: ReferentialAction.Cascade);
            });
        
        migrationBuilder.CreateIndex("IX_UserPreferences_UserId",
            "UserPreferences", "UserId", unique: true);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable("UserPreferences");
    }
}
"""

    def show_commands(self):
        print("=== EF Core Migration Commands ===\n")
        for cmd_name, cmd in self.COMMANDS.items():
            print(f"  [{cmd_name:<10}] {cmd}")

    def show_practices(self):
        print(f"\n=== Best Practices ===\n")
        for key, practice in self.BEST_PRACTICES.items():
            print(f"[{practice['name']}]")
            print(f"  Do: {practice.get('do', practice.get('convention', ''))}")
            print()

    def show_migration(self):
        print("=== Migration Example ===")
        print(self.CSHARP_MIGRATION[:500])

mig = MigrationManagement()
mig.show_commands()
mig.show_practices()
mig.show_migration()

Testing & Performance

# testing.py — EF Core testing in agile
import json
import random

class EFCoreTesting:
    TESTING_TYPES = {
        "unit": {
            "name": "Unit Tests (InMemory)",
            "provider": "Microsoft.EntityFrameworkCore.InMemory",
            "speed": "เร็วมาก",
            "accuracy": "ปานกลาง (ไม่ test SQL generation)",
        },
        "integration": {
            "name": "Integration Tests (Real DB)",
            "provider": "Testcontainers (Docker)",
            "speed": "ปานกลาง",
            "accuracy": "สูง (test จริงกับ database)",
        },
        "performance": {
            "name": "Performance Tests",
            "provider": "BenchmarkDotNet + SQL Profiler",
            "speed": "ช้า",
            "accuracy": "สูงมาก",
        },
    }

    CSHARP_TEST = """
// Tests/UserServiceTests.cs
[Fact]
public async Task CreateUser_ShouldAddToDatabase()
{
    // Arrange
    var options = new DbContextOptionsBuilder()
        .UseInMemoryDatabase("TestDb_" + Guid.NewGuid())
        .Options;

    using var context = new AppDbContext(options);
    var service = new UserService(context);

    // Act
    var user = await service.CreateUser("Alice", "alice@test.com");

    // Assert
    Assert.NotNull(user);
    Assert.Equal("Alice", user.Name);
    Assert.Equal(1, await context.Users.CountAsync());
}

[Fact]
public async Task GetActiveUsers_ShouldFilterInactive()
{
    // Arrange
    var options = new DbContextOptionsBuilder()
        .UseInMemoryDatabase("TestDb_" + Guid.NewGuid())
        .Options;

    using var context = new AppDbContext(options);
    context.Users.AddRange(
        new User { Name = "Active", IsActive = true },
        new User { Name = "Inactive", IsActive = false }
    );
    await context.SaveChangesAsync();

    // Act
    var service = new UserService(context);
    var users = await service.GetActiveUsers();

    // Assert
    Assert.Single(users);
    Assert.Equal("Active", users[0].Name);
}
"""

    def show_types(self):
        print("=== Testing Types ===\n")
        for key, t in self.TESTING_TYPES.items():
            print(f"[{t['name']}] Speed: {t['speed']} | Accuracy: {t['accuracy']}")

    def show_test(self):
        print(f"\n=== C# Test Example ===")
        print(self.CSHARP_TEST[:500])

    def perf_tips(self):
        print(f"\n=== Performance Tips ===")
        tips = [
            "ใช้ AsNoTracking() สำหรับ read-only queries",
            "หลีกเลี่ยง N+1: ใช้ Include() หรือ explicit loading",
            "ใช้ compiled queries สำหรับ hot paths",
            "Batch operations: AddRange() แทน Add() ทีละตัว",
            "Index columns ที่ใช้ใน WHERE, ORDER BY, JOIN",
        ]
        for tip in tips:
            print(f"  • {tip}")

test = EFCoreTesting()
test.show_types()
test.show_test()
test.perf_tips()

FAQ - คำถามที่พบบ่อย

Q: Scrum กับ Kanban อันไหนดีสำหรับ EF Core projects?

A: Scrum: ดีสำหรับ greenfield projects ที่ต้อง plan migrations ล่วงหน้า Kanban: ดีสำหรับ maintenance, bug fixes, continuous delivery ทีมใหญ่: Scrum (structure ชัดเจน) ทีมเล็ก: Kanban (flexible กว่า) หลายทีมใช้ Scrumban (ผสม Scrum ceremonies + Kanban board)

Q: Migration conflicts ใน team แก้ยังไง?

A: ป้องกัน: 1 migration ต่อ 1 feature branch, merge ก่อน create migration แก้ไข: dotnet ef migrations remove → resolve conflicts → สร้าง migration ใหม่ Rule: ห้าม edit migration ที่ apply แล้ว (สร้าง migration ใหม่แทน) ใช้ Kanban WIP limit: จำกัด migration tasks ที่ทำพร้อมกัน

Q: EF Core กับ Dapper อันไหนดี?

A: EF Core: productivity สูง, migrations, LINQ, complex relationships Dapper: performance สูงกว่า, control SQL ได้เต็มที่, lightweight ใช้ EF Core: CRUD operations, complex domain, rapid development ใช้ Dapper: read-heavy, performance critical, reporting queries หลายทีมใช้ทั้งคู่: EF Core สำหรับ writes, Dapper สำหรับ complex reads

Q: Production migration ทำอย่างไรให้ปลอดภัย?

A: 1. Generate SQL script: dotnet ef migrations script --idempotent 2. Review SQL ด้วย DBA หรือ senior dev 3. Test บน staging ที่ clone จาก production 4. Backup production database 5. Apply ใน maintenance window 6. มี rollback plan (Down migration) 7. Monitor หลัง apply (errors, performance)

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

Mintlify Docs Agile Scrum Kanbanอ่านบทความ → C# Entity Framework Stream Processingอ่านบทความ → C# Entity Framework Load Testing Strategyอ่านบทความ → C# Entity Framework IoT Gatewayอ่านบทความ → DNS over HTTPS Agile Scrum Kanbanอ่านบทความ →

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