SiamCafe · Blog
C# Entity Framework Career Development IT —
บทความ

C# Entity Framework Career Development IT —

เผยแพร่ 28 พฤษภาคม 2569

EF Core

C# Entity Framework Core ORM DbContext Migration LINQ Repository Pattern Clean Architecture Performance SQL Server PostgreSQL Career Development

FeatureEF CoreDapperADO.NETNHibernate
TypeFull ORMMicro ORMRaw ADOFull ORM
Performanceดีดีมากดีที่สุดปานกลาง
Productivityสูงมากสูงต่ำสูง
MigrationBuilt-inไม่มีไม่มีFluentMigrator
LINQFull supportไม่มีไม่มีPartial
Learningปานกลางง่ายง่ายสูง
เหมาะกับMost projectsHigh perf queryLegacy/controlComplex domain

DbContext and Models

=== EF Core DbContext Configuration ===

Models/Product.cs

public class Product

{

public int Id { get; set; }

public string Name { get; set; } = string.Empty;

public decimal Price { get; set; }

public int CategoryId { get; set; }

public Category Category { get; set; } = null!;

public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();

public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

public bool IsActive { get; set; } = true;

}

Models/Category.cs

public class Category

{

public int Id { get; set; }

public string Name { get; set; } = string.Empty;

public ICollection<Product> Products { get; set; } = new List<Product>();

}

Data/AppDbContext.cs

public class AppDbContext : DbContext

{

public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

public DbSet<Product> Products => Set<Product>();

public DbSet<Category> Categories => Set<Category>();

public DbSet<Order> Orders => Set<Order>();

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.Entity<Product>(entity =>

{

entity.HasIndex(e => e.Name);

entity.Property(e => e.Price).HasPrecision(18, 2);

entity.HasOne(e => e.Category)

.WithMany(c => c.Products)

.HasForeignKey(e => e.CategoryId);

});

}

}

from dataclasses import dataclass

@dataclass

class EFCommand:

command: str

description: str

example: str

commands = [

EFCommand("dotnet ef migrations add", "สร้าง Migration ใหม่", "dotnet ef migrations add AddProductTable"),

EFCommand("dotnet ef database update", "Apply Migration ล่าสุด", "dotnet ef database update"),

EFCommand("dotnet ef migrations remove", "ลบ Migration ล่าสุด", "dotnet ef migrations remove"),

EFCommand("dotnet ef migrations list", "แสดง Migration ทั้งหมด", "dotnet ef migrations list"),

EFCommand("dotnet ef migrations script", "สร้าง SQL Script", "dotnet ef migrations script -o migration.sql"),

EFCommand("dotnet ef database drop", "ลบ Database", "dotnet ef database drop --force"),

EFCommand("dotnet ef dbcontext scaffold", "Scaffold จาก DB", 'dotnet ef dbcontext scaffold "ConnStr" Npgsql.EntityFrameworkCore.PostgreSQL'),

]

print("=== EF Core CLI Commands ===")

for c in commands:

print(f" [{c.command}]")

print(f" {c.description}")

print(f" Example: {c.example}")

LINQ Queries and Performance

=== LINQ Query Patterns ===

Basic Query

var products = await _context.Products

.Where(p => p.IsActive && p.Price > 100)

.OrderByDescending(p => p.CreatedAt)

.Take(10)

.ToListAsync();

Eager Loading (fix N+1)

var productsWithCategory = await _context.Products

.Include(p => p.Category)

.Where(p => p.IsActive)

.ToListAsync();

Projection (select only needed fields)

var productDtos = await _context.Products

.Where(p => p.IsActive)

.Select(p => new ProductDto

{

Id = p.Id,

Name = p.Name,

Price = p.Price,

CategoryName = p.Category.Name

})

.ToListAsync();

AsNoTracking (read-only performance)

var readOnlyProducts = await _context.Products

.AsNoTracking()

.Where(p => p.CategoryId == categoryId)

.ToListAsync();

Compiled Query (frequently called)

private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =

EF.CompileAsyncQuery((AppDbContext ctx, int id) =>

ctx.Products.FirstOrDefault(p => p.Id == id));

@dataclass

class PerfTip:

problem: str

solution: str

impact: str

code_change: str

tips = [

PerfTip("N+1 Query", "Use Include() for Eager Loading", "10x faster for related data",

".Include(p => p.Category)"),

PerfTip("Tracking overhead", "Use AsNoTracking() for reads", "2-3x faster read queries",

".AsNoTracking()"),

PerfTip("Over-fetching", "Use Select() Projection", "Reduce data transfer 50-80%",

".Select(p => new { p.Id, p.Name })"),

PerfTip("Repeated queries", "Use Compiled Query", "Eliminate query compilation cost",

"EF.CompileAsyncQuery(...)"),

PerfTip("Slow inserts", "Use AddRange() + batch", "10-100x faster bulk insert",

"_context.Products.AddRange(list)"),

PerfTip("Missing index", "Add HasIndex in OnModelCreating", "100x faster filtered queries",

"entity.HasIndex(e => e.Name)"),

PerfTip("Complex query", "Use Raw SQL via FromSqlRaw", "Optimal for complex joins/CTEs",

'_context.Products.FromSqlRaw("SELECT ...")'),

]

print("\n=== Performance Tips ===")

for t in tips:

print(f" [{t.problem}] → {t.solution}")

print(f" Impact: {t.impact}")

print(f" Code: {t.code_change}")

Repository Pattern

=== Repository Pattern with EF Core ===

IRepository.cs

public interface IRepository<T> where T : class

{

Task<T?> GetByIdAsync(int id);

Task<IEnumerable<T>> GetAllAsync();

Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);

Task AddAsync(T entity);

void Update(T entity);

void Remove(T entity);

}

Repository.cs

public class Repository<T> : IRepository<T> where T : class

{

protected readonly AppDbContext _context;

protected readonly DbSet<T> _dbSet;

public Repository(AppDbContext context)

{

_context = context;

_dbSet = context.Set<T>();

}

public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);

public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();

public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);

public void Update(T entity) => _dbSet.Update(entity);

public void Remove(T entity) => _dbSet.Remove(entity);

}

IUnitOfWork.cs

public interface IUnitOfWork : IDisposable

{

IRepository<Product> Products { get; }

IRepository<Category> Categories { get; }

Task<int> SaveChangesAsync();

}

@dataclass

class CareerLevel:

level: str

ef_skills: str

salary_range: str

project_type: str

levels = [

CareerLevel("Junior .NET Dev", "Basic CRUD, Migration, LINQ basics",

"25-45K THB", "Simple CRUD apps, internal tools"),

CareerLevel("Mid .NET Dev", "Repository Pattern, Performance tuning, Complex LINQ",

"45-80K THB", "Web API, microservices, e-commerce"),

CareerLevel("Senior .NET Dev", "Clean Architecture, CQRS, Advanced EF, Raw SQL",

"80-150K THB", "Enterprise apps, system design"),

CareerLevel(".NET Architect", "Multi-DB strategy, Sharding, Event Sourcing",

"120-200K+ THB", "Platform design, team leadership"),

]

print("Career Path (.NET + EF Core):")

for l in levels:

print(f" [{l.level}] Salary: {l.salary_range}")

print(f" EF Skills: {l.ef_skills}")

print(f" Projects: {l.project_type}")

เคล็ดลับ

  • AsNoTracking: ใช้ AsNoTracking() สำหรับ Read-only Query เสมอ
  • Include: ใช้ Include() แก้ N+1 Problem ทุกครั้ง
  • Migration: สร้าง SQL Script จาก Migration ตรวจสอบก่อน Apply Production
  • Index: ตั้ง Index บน Column ที่ใช้ Where/OrderBy บ่อย
  • Logging: เปิด EF Core Logging ดู Generated SQL ตรวจ Performance

Entity Framework Core คืออะไร

ORM .NET C# Class Database SQL Server PostgreSQL MySQL Code-First Database-First Migration LINQ Query Schema Change