Entity Framework Core

Entity Framework Core (EF Core) is developed as "a lightweight, extensible, and cross-platform Object-Relational Mapping (ORM) framework for .NET" and serves as Microsoft's official data access technology. Through complete integration with .NET 5/6/7/8, it provides intuitive query writing based on LINQ, automatic schema generation through Code First approach, and comprehensive migration functionality. Supporting major databases including SQL Server, MySQL, PostgreSQL, SQLite, it has established itself as a comprehensive data persistence solution for modern .NET development.

ORM.NETC#MigrationLINQCode First

GitHub Overview

dotnet/efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.

Stars14,213
Watchers887
Forks3,282
Created:January 23, 2014
Language:C#
License:MIT License

Topics

aspnet-productc-sharpdatabasedotnet-coredotnet-frameworkdotnet-standardentity-frameworkhacktoberfestorm

Star History

dotnet/efcore Star History
Data as of: 7/17/2025, 10:32 AM

Library

Entity Framework Core

Overview

Entity Framework Core (EF Core) is developed as "a lightweight, extensible, and cross-platform Object-Relational Mapping (ORM) framework for .NET" and serves as Microsoft's official data access technology. Through complete integration with .NET 5/6/7/8, it provides intuitive query writing based on LINQ, automatic schema generation through Code First approach, and comprehensive migration functionality. Supporting major databases including SQL Server, MySQL, PostgreSQL, SQLite, it has established itself as a comprehensive data persistence solution for modern .NET development.

Details

Entity Framework Core 2025 edition continues to mature as the core data access technology in the .NET ecosystem through over 10 years of evolution. Through a type-safe query system fully integrated with LINQ, it enables complex data operations without directly writing SQL. Automatic database schema generation from intuitive model definitions through Code First approach and rich migration functionality significantly improve development efficiency. Seamless integration with modern frameworks like ASP.NET Core, Blazor, and .NET MAUI provides comprehensive support for web, desktop, and mobile application development.

Key Features

  • LINQ Integration: Type-safe and intuitive query writing using C# LINQ
  • Code First Approach: Automatic database schema generation from C# classes
  • Comprehensive Migration: Professional schema change management supporting both automatic and manual approaches
  • Multi-Database Support: Unified API for SQL Server, MySQL, PostgreSQL, SQLite, etc.
  • .NET Integration: Complete integration with ASP.NET Core, Blazor, and cross-platform support
  • Advanced Features: Change tracking, lazy loading, optimistic concurrency control support

Pros and Cons

Pros

  • High reliability and long-term maintenance system through official Microsoft support
  • Type-safe and intuitive data operations through complete LINQ integration
  • High development efficiency and model-centric design through Code First approach
  • Professional schema management through comprehensive migration functionality
  • Excellent integration with the entire .NET ecosystem and cross-platform support
  • Rich documentation and learning resources, active community support

Cons

  • Specialized for .NET environment, cannot be used on other platforms
  • Performance may be inferior to raw SQL for complex queries
  • Abstraction layer may limit detailed SQL control in some cases
  • May be heavier than other specialized ORMs for large-scale data processing
  • Difficulty in advanced optimization without SQL knowledge due to LINQ dependency
  • Black-boxing due to dependency on auto-generated code

Reference Pages

Code Examples

Basic Setup

# Create new .NET project
dotnet new web -n BlogApp
cd BlogApp

# Add Entity Framework Core packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools

# For SQLite usage
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
// Program.cs - .NET 6+ style
using Microsoft.EntityFrameworkCore;
using BlogApp.Data;

var builder = WebApplication.CreateBuilder(args);

// Entity Framework Core configuration
builder.Services.AddDbContext<BlogContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Add development services
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

// Auto-apply migrations in development environment
if (app.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<BlogContext>();
    await context.Database.MigrateAsync();
}

app.Run();
// appsettings.json - Connection string configuration
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BlogAppDb;Trusted_Connection=true;MultipleActiveResultSets=true",
    "SQLiteConnection": "Data Source=blog.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Information"
    }
  }
}

Model Definition and Basic Operations

// Models/Blog.cs - Blog entity
using System.ComponentModel.DataAnnotations;

namespace BlogApp.Models;

public class Blog
{
    public int BlogId { get; set; }
    
    [Required]
    [StringLength(200)]
    public string Title { get; set; } = string.Empty;
    
    [StringLength(1000)]
    public string? Description { get; set; }
    
    public string Url { get; set; } = string.Empty;
    
    public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
    
    public DateTime? UpdatedDate { get; set; }
    
    // Navigation properties
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
    
    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

// Models/Post.cs - Post entity
public class Post
{
    public int PostId { get; set; }
    
    [Required]
    [StringLength(300)]
    public string Title { get; set; } = string.Empty;
    
    public string Content { get; set; } = string.Empty;
    
    public DateTime PublishedDate { get; set; } = DateTime.UtcNow;
    
    public DateTime? UpdatedDate { get; set; }
    
    public bool IsPublished { get; set; } = false;
    
    // Foreign key
    public int BlogId { get; set; }
    
    // Navigation properties
    public virtual Blog Blog { get; set; } = null!;
    
    public virtual ICollection<Comment> Comments { get; set; } = new List<Comment>();
    
    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

// Models/Comment.cs - Comment entity
public class Comment
{
    public int CommentId { get; set; }
    
    [Required]
    public string Author { get; set; } = string.Empty;
    
    [Required]
    [EmailAddress]
    public string Email { get; set; } = string.Empty;
    
    public string Content { get; set; } = string.Empty;
    
    public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
    
    public bool IsApproved { get; set; } = false;
    
    // Foreign key
    public int PostId { get; set; }
    
    // Navigation property
    public virtual Post Post { get; set; } = null!;
}

// Models/Tag.cs - Tag entity
public class Tag
{
    public int TagId { get; set; }
    
    [Required]
    [StringLength(50)]
    public string Name { get; set; } = string.Empty;
    
    [StringLength(200)]
    public string? Description { get; set; }
    
    // Many-to-many navigation properties
    public virtual ICollection<Blog> Blogs { get; set; } = new List<Blog>();
    
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

// Data/BlogContext.cs - Database context
using Microsoft.EntityFrameworkCore;
using BlogApp.Models;

namespace BlogApp.Data;

public class BlogContext : DbContext
{
    public BlogContext(DbContextOptions<BlogContext> options) : base(options)
    {
    }
    
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<Tag> Tags { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // Blog configuration
        modelBuilder.Entity<Blog>(entity =>
        {
            entity.HasKey(e => e.BlogId);
            entity.Property(e => e.Title).IsRequired().HasMaxLength(200);
            entity.Property(e => e.Url).IsRequired();
            entity.HasIndex(e => e.Url).IsUnique();
        });
        
        // Post configuration
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasKey(e => e.PostId);
            entity.Property(e => e.Title).IsRequired().HasMaxLength(300);
            entity.Property(e => e.Content).HasColumnType("text");
            
            // One-to-many relationship with Blog
            entity.HasOne(p => p.Blog)
                  .WithMany(b => b.Posts)
                  .HasForeignKey(p => p.BlogId)
                  .OnDelete(DeleteBehavior.Cascade);
        });
        
        // Comment configuration
        modelBuilder.Entity<Comment>(entity =>
        {
            entity.HasKey(e => e.CommentId);
            entity.Property(e => e.Author).IsRequired();
            entity.Property(e => e.Email).IsRequired();
            
            // One-to-many relationship with Post
            entity.HasOne(c => c.Post)
                  .WithMany(p => p.Comments)
                  .HasForeignKey(c => c.PostId)
                  .OnDelete(DeleteBehavior.Cascade);
        });
        
        // Tag configuration
        modelBuilder.Entity<Tag>(entity =>
        {
            entity.HasKey(e => e.TagId);
            entity.Property(e => e.Name).IsRequired().HasMaxLength(50);
            entity.HasIndex(e => e.Name).IsUnique();
        });
        
        // Many-to-many relationship configuration (Blog-Tag)
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Tags)
            .WithMany(t => t.Blogs)
            .UsingEntity<Dictionary<string, object>>(
                "BlogTag",
                j => j.HasOne<Tag>().WithMany().HasForeignKey("TagId"),
                j => j.HasOne<Blog>().WithMany().HasForeignKey("BlogId"));
        
        // Many-to-many relationship configuration (Post-Tag)
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(t => t.Posts)
            .UsingEntity<Dictionary<string, object>>(
                "PostTag",
                j => j.HasOne<Tag>().WithMany().HasForeignKey("TagId"),
                j => j.HasOne<Post>().WithMany().HasForeignKey("PostId"));
    }
}

// Services/BlogService.cs - Business logic
public class BlogService
{
    private readonly BlogContext _context;
    
    public BlogService(BlogContext context)
    {
        _context = context;
    }
    
    // Create blog
    public async Task<Blog> CreateBlogAsync(string title, string description, string url)
    {
        var blog = new Blog
        {
            Title = title,
            Description = description,
            Url = url,
            CreatedDate = DateTime.UtcNow
        };
        
        _context.Blogs.Add(blog);
        await _context.SaveChangesAsync();
        
        return blog;
    }
    
    // Get blog with posts
    public async Task<Blog?> GetBlogWithPostsAsync(int blogId)
    {
        return await _context.Blogs
            .Include(b => b.Posts)
                .ThenInclude(p => p.Comments)
            .Include(b => b.Tags)
            .FirstOrDefaultAsync(b => b.BlogId == blogId);
    }
    
    // Get all blogs
    public async Task<List<Blog>> GetAllBlogsAsync()
    {
        return await _context.Blogs
            .Include(b => b.Posts)
            .OrderBy(b => b.Title)
            .ToListAsync();
    }
    
    // Update blog
    public async Task<Blog?> UpdateBlogAsync(int blogId, string title, string description)
    {
        var blog = await _context.Blogs.FindAsync(blogId);
        if (blog == null) return null;
        
        blog.Title = title;
        blog.Description = description;
        blog.UpdatedDate = DateTime.UtcNow;
        
        await _context.SaveChangesAsync();
        return blog;
    }
    
    // Delete blog
    public async Task<bool> DeleteBlogAsync(int blogId)
    {
        var blog = await _context.Blogs.FindAsync(blogId);
        if (blog == null) return false;
        
        _context.Blogs.Remove(blog);
        await _context.SaveChangesAsync();
        return true;
    }
}

Advanced Query Operations

// Services/PostQueryService.cs - Advanced query examples
public class PostQueryService
{
    private readonly BlogContext _context;
    
    public PostQueryService(BlogContext context)
    {
        _context = context;
    }
    
    // Complex conditional search
    public async Task<List<Post>> SearchPostsAsync(string? keyword, int? blogId, bool? isPublished, DateTime? fromDate, DateTime? toDate)
    {
        var query = _context.Posts
            .Include(p => p.Blog)
            .Include(p => p.Tags)
            .AsQueryable();
        
        // Add dynamic conditions
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            query = query.Where(p => p.Title.Contains(keyword) || p.Content.Contains(keyword));
        }
        
        if (blogId.HasValue)
        {
            query = query.Where(p => p.BlogId == blogId.Value);
        }
        
        if (isPublished.HasValue)
        {
            query = query.Where(p => p.IsPublished == isPublished.Value);
        }
        
        if (fromDate.HasValue)
        {
            query = query.Where(p => p.PublishedDate >= fromDate.Value);
        }
        
        if (toDate.HasValue)
        {
            query = query.Where(p => p.PublishedDate <= toDate.Value);
        }
        
        return await query
            .OrderByDescending(p => p.PublishedDate)
            .ToListAsync();
    }
    
    // Aggregation queries
    public async Task<PostStatistics> GetPostStatisticsAsync()
    {
        var stats = await _context.Posts
            .GroupBy(p => 1) // Group entire dataset
            .Select(g => new PostStatistics
            {
                TotalPosts = g.Count(),
                PublishedPosts = g.Count(p => p.IsPublished),
                DraftPosts = g.Count(p => !p.IsPublished),
                TotalComments = g.Sum(p => p.Comments.Count),
                AverageCommentsPerPost = g.Average(p => p.Comments.Count),
                LastPostDate = g.Max(p => p.PublishedDate)
            })
            .FirstOrDefaultAsync();
        
        return stats ?? new PostStatistics();
    }
    
    // Get popular posts (ordered by comment count)
    public async Task<List<Post>> GetPopularPostsAsync(int count = 10)
    {
        return await _context.Posts
            .Include(p => p.Blog)
            .Include(p => p.Comments)
            .Where(p => p.IsPublished)
            .OrderByDescending(p => p.Comments.Count)
            .ThenByDescending(p => p.PublishedDate)
            .Take(count)
            .ToListAsync();
    }
    
    // Get posts by tag
    public async Task<List<Post>> GetPostsByTagAsync(string tagName)
    {
        return await _context.Posts
            .Include(p => p.Blog)
            .Include(p => p.Tags)
            .Where(p => p.Tags.Any(t => t.Name == tagName) && p.IsPublished)
            .OrderByDescending(p => p.PublishedDate)
            .ToListAsync();
    }
    
    // Get posts with pagination
    public async Task<PagedResult<Post>> GetPagedPostsAsync(int pageNumber, int pageSize, int? blogId = null)
    {
        var query = _context.Posts
            .Include(p => p.Blog)
            .Include(p => p.Tags)
            .AsQueryable();
        
        if (blogId.HasValue)
        {
            query = query.Where(p => p.BlogId == blogId.Value);
        }
        
        var totalCount = await query.CountAsync();
        
        var posts = await query
            .Where(p => p.IsPublished)
            .OrderByDescending(p => p.PublishedDate)
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
        
        return new PagedResult<Post>
        {
            Items = posts,
            TotalCount = totalCount,
            PageNumber = pageNumber,
            PageSize = pageSize,
            TotalPages = (int)Math.Ceiling((double)totalCount / pageSize)
        };
    }
    
    // Raw SQL query example
    public async Task<List<BlogStatistics>> GetBlogStatisticsRawSqlAsync()
    {
        return await _context.Database
            .SqlQueryRaw<BlogStatistics>(@"
                SELECT 
                    b.BlogId,
                    b.Title,
                    COUNT(p.PostId) as PostCount,
                    COUNT(CASE WHEN p.IsPublished = 1 THEN 1 END) as PublishedPostCount,
                    COALESCE(SUM(c.CommentCount), 0) as TotalComments
                FROM Blogs b
                LEFT JOIN Posts p ON b.BlogId = p.BlogId
                LEFT JOIN (
                    SELECT PostId, COUNT(*) as CommentCount 
                    FROM Comments 
                    GROUP BY PostId
                ) c ON p.PostId = c.PostId
                GROUP BY b.BlogId, b.Title
                ORDER BY PostCount DESC")
            .ToListAsync();
    }
}

// DTOs/PagedResult.cs - Pagination result
public class PagedResult<T>
{
    public List<T> Items { get; set; } = new();
    public int TotalCount { get; set; }
    public int PageNumber { get; set; }
    public int PageSize { get; set; }
    public int TotalPages { get; set; }
    public bool HasPreviousPage => PageNumber > 1;
    public bool HasNextPage => PageNumber < TotalPages;
}

// DTOs/PostStatistics.cs - Post statistics
public class PostStatistics
{
    public int TotalPosts { get; set; }
    public int PublishedPosts { get; set; }
    public int DraftPosts { get; set; }
    public int TotalComments { get; set; }
    public double AverageCommentsPerPost { get; set; }
    public DateTime LastPostDate { get; set; }
}

// DTOs/BlogStatistics.cs - Blog statistics
public class BlogStatistics
{
    public int BlogId { get; set; }
    public string Title { get; set; } = string.Empty;
    public int PostCount { get; set; }
    public int PublishedPostCount { get; set; }
    public int TotalComments { get; set; }
}

Relationship Operations

// Services/RelationshipService.cs - Relationship operation examples
public class RelationshipService
{
    private readonly BlogContext _context;
    
    public RelationshipService(BlogContext context)
    {
        _context = context;
    }
    
    // Create post with tag associations
    public async Task<Post> CreatePostWithTagsAsync(int blogId, string title, string content, List<string> tagNames)
    {
        using var transaction = await _context.Database.BeginTransactionAsync();
        
        try
        {
            // Check blog existence
            var blog = await _context.Blogs.FindAsync(blogId);
            if (blog == null)
                throw new ArgumentException("Blog not found", nameof(blogId));
            
            // Create post
            var post = new Post
            {
                Title = title,
                Content = content,
                BlogId = blogId,
                PublishedDate = DateTime.UtcNow,
                IsPublished = true
            };
            
            _context.Posts.Add(post);
            await _context.SaveChangesAsync(); // Save to get ID
            
            // Process tags
            foreach (var tagName in tagNames.Distinct())
            {
                var tag = await _context.Tags
                    .FirstOrDefaultAsync(t => t.Name == tagName);
                
                if (tag == null)
                {
                    // Create new tag
                    tag = new Tag { Name = tagName };
                    _context.Tags.Add(tag);
                    await _context.SaveChangesAsync();
                }
                
                // Associate post with tag
                post.Tags.Add(tag);
            }
            
            await _context.SaveChangesAsync();
            await transaction.CommitAsync();
            
            return post;
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
    
    // Add comment
    public async Task<Comment> AddCommentToPostAsync(int postId, string author, string email, string content)
    {
        var post = await _context.Posts.FindAsync(postId);
        if (post == null)
            throw new ArgumentException("Post not found", nameof(postId));
        
        var comment = new Comment
        {
            PostId = postId,
            Author = author,
            Email = email,
            Content = content,
            CreatedDate = DateTime.UtcNow,
            IsApproved = false // Awaiting admin approval
        };
        
        _context.Comments.Add(comment);
        await _context.SaveChangesAsync();
        
        return comment;
    }
    
    // Move post between blogs
    public async Task<bool> MovePostToBlogAsync(int postId, int newBlogId)
    {
        var post = await _context.Posts.FindAsync(postId);
        var newBlog = await _context.Blogs.FindAsync(newBlogId);
        
        if (post == null || newBlog == null)
            return false;
        
        post.BlogId = newBlogId;
        post.UpdatedDate = DateTime.UtcNow;
        
        await _context.SaveChangesAsync();
        return true;
    }
    
    // Remove tag from post
    public async Task<bool> RemoveTagFromPostAsync(int postId, int tagId)
    {
        var post = await _context.Posts
            .Include(p => p.Tags)
            .FirstOrDefaultAsync(p => p.PostId == postId);
        
        if (post == null) return false;
        
        var tag = post.Tags.FirstOrDefault(t => t.TagId == tagId);
        if (tag == null) return false;
        
        post.Tags.Remove(tag);
        await _context.SaveChangesAsync();
        
        return true;
    }
    
    // Add multiple posts to blog in batch
    public async Task<List<Post>> AddMultiplePostsToBlogAsync(int blogId, List<(string Title, string Content)> posts)
    {
        var blog = await _context.Blogs.FindAsync(blogId);
        if (blog == null)
            throw new ArgumentException("Blog not found", nameof(blogId));
        
        var newPosts = posts.Select(p => new Post
        {
            Title = p.Title,
            Content = p.Content,
            BlogId = blogId,
            PublishedDate = DateTime.UtcNow,
            IsPublished = false // Create as draft
        }).ToList();
        
        _context.Posts.AddRange(newPosts);
        await _context.SaveChangesAsync();
        
        return newPosts;
    }
    
    // Delete blog with all related data
    public async Task<bool> DeleteBlogWithAllRelatedDataAsync(int blogId)
    {
        using var transaction = await _context.Database.BeginTransactionAsync();
        
        try
        {
            var blog = await _context.Blogs
                .Include(b => b.Posts)
                    .ThenInclude(p => p.Comments)
                .Include(b => b.Posts)
                    .ThenInclude(p => p.Tags)
                .FirstOrDefaultAsync(b => b.BlogId == blogId);
            
            if (blog == null) return false;
            
            // Cascade delete is configured, so deleting blog
            // will automatically delete related posts and comments
            _context.Blogs.Remove(blog);
            await _context.SaveChangesAsync();
            
            await transaction.CommitAsync();
            return true;
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

Practical Examples

// Controllers/BlogController.cs - ASP.NET Core controller example
using Microsoft.AspNetCore.Mvc;
using BlogApp.Services;
using BlogApp.Models;

[ApiController]
[Route("api/[controller]")]
public class BlogController : ControllerBase
{
    private readonly BlogService _blogService;
    private readonly PostQueryService _postQueryService;
    private readonly RelationshipService _relationshipService;
    
    public BlogController(
        BlogService blogService, 
        PostQueryService postQueryService,
        RelationshipService relationshipService)
    {
        _blogService = blogService;
        _postQueryService = postQueryService;
        _relationshipService = relationshipService;
    }
    
    // Get all blogs
    [HttpGet]
    public async Task<ActionResult<List<Blog>>> GetBlogs()
    {
        var blogs = await _blogService.GetAllBlogsAsync();
        return Ok(blogs);
    }
    
    // Get blog details
    [HttpGet("{id}")]
    public async Task<ActionResult<Blog>> GetBlog(int id)
    {
        var blog = await _blogService.GetBlogWithPostsAsync(id);
        if (blog == null)
            return NotFound();
        
        return Ok(blog);
    }
    
    // Create blog
    [HttpPost]
    public async Task<ActionResult<Blog>> CreateBlog([FromBody] CreateBlogRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);
        
        try
        {
            var blog = await _blogService.CreateBlogAsync(
                request.Title, 
                request.Description, 
                request.Url);
            
            return CreatedAtAction(nameof(GetBlog), new { id = blog.BlogId }, blog);
        }
        catch (Exception ex)
        {
            return BadRequest(new { message = ex.Message });
        }
    }
    
    // Update blog
    [HttpPut("{id}")]
    public async Task<ActionResult<Blog>> UpdateBlog(int id, [FromBody] UpdateBlogRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);
        
        var blog = await _blogService.UpdateBlogAsync(id, request.Title, request.Description);
        if (blog == null)
            return NotFound();
        
        return Ok(blog);
    }
    
    // Delete blog
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteBlog(int id)
    {
        var result = await _relationshipService.DeleteBlogWithAllRelatedDataAsync(id);
        if (!result)
            return NotFound();
        
        return NoContent();
    }
    
    // Search blog posts
    [HttpGet("{id}/posts")]
    public async Task<ActionResult<PagedResult<Post>>> GetBlogPosts(
        int id,
        [FromQuery] int page = 1,
        [FromQuery] int size = 10,
        [FromQuery] string? keyword = null)
    {
        var result = await _postQueryService.GetPagedPostsAsync(page, size, id);
        return Ok(result);
    }
    
    // Create post
    [HttpPost("{id}/posts")]
    public async Task<ActionResult<Post>> CreatePost(int id, [FromBody] CreatePostRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);
        
        try
        {
            var post = await _relationshipService.CreatePostWithTagsAsync(
                id, 
                request.Title, 
                request.Content, 
                request.Tags ?? new List<string>());
            
            return CreatedAtAction("GetPost", "Post", new { id = post.PostId }, post);
        }
        catch (ArgumentException ex)
        {
            return BadRequest(new { message = ex.Message });
        }
    }
}

// DTOs/Requests.cs - Request DTOs
public class CreateBlogRequest
{
    [Required]
    [StringLength(200)]
    public string Title { get; set; } = string.Empty;
    
    [StringLength(1000)]
    public string? Description { get; set; }
    
    [Required]
    [Url]
    public string Url { get; set; } = string.Empty;
}

public class UpdateBlogRequest
{
    [Required]
    [StringLength(200)]
    public string Title { get; set; } = string.Empty;
    
    [StringLength(1000)]
    public string? Description { get; set; }
}

public class CreatePostRequest
{
    [Required]
    [StringLength(300)]
    public string Title { get; set; } = string.Empty;
    
    [Required]
    public string Content { get; set; } = string.Empty;
    
    public List<string>? Tags { get; set; }
}

// Migration management command examples
/*
# Create initial migration
dotnet ef migrations add InitialCreate

# Update database
dotnet ef database update

# Add new migration
dotnet ef migrations add AddTagsAndComments

# List migrations
dotnet ef migrations list

# Remove migration (latest only)
dotnet ef migrations remove

# Revert to specific migration
dotnet ef database update AddTagsAndComments

# Generate script (for production)
dotnet ef migrations script --output migration.sql

# Create bundle (executable file)
dotnet ef migrations bundle --output efbundle.exe
*/

// Advanced configuration example in Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
    // Entity Framework Core configuration
    services.AddDbContext<BlogContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), 
            sqlOptions =>
            {
                // Connection timeout setting
                sqlOptions.CommandTimeout(30);
                
                // Specify migrations assembly
                sqlOptions.MigrationsAssembly("BlogApp.Migrations");
                
                // Retry configuration
                sqlOptions.EnableRetryOnFailure(
                    maxRetryCount: 3,
                    maxRetryDelay: TimeSpan.FromSeconds(5),
                    errorNumbersToAdd: null);
            });
        
        // Detailed logging in development environment
        if (Environment.IsDevelopment())
        {
            options.EnableSensitiveDataLogging();
            options.EnableDetailedErrors();
        }
        
        // Change tracking optimization
        options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    });
    
    // Service registration
    services.AddScoped<BlogService>();
    services.AddScoped<PostQueryService>();
    services.AddScoped<RelationshipService>();
}