Entity Framework Core
Entity Framework Core(EF Core)は、.NET向けの軽量・拡張可能・クロスプラットフォームORMです。Code First、Database First両方のアプローチをサポートし、SQL Server、Azure SQL、SQLite、PostgreSQL、MySQL等を支援し、LINQ統合による強力なクエリ機能を提供します。
ライブラリ
Entity Framework Core
概要
Entity Framework Core(EF Core)は、.NET向けの軽量・拡張可能・クロスプラットフォームORMです。Code First、Database First両方のアプローチをサポートし、SQL Server、Azure SQL、SQLite、PostgreSQL、MySQL等を支援し、LINQ統合による強力なクエリ機能を提供します。
詳細
Entity Framework CoreはMicrosoftが開発した.NETエコシステムの標準ORMです。従来のEntity Frameworkを軽量化・モダン化し、クロスプラットフォーム対応を実現しました。LINQによる型安全なクエリ、強力なマイグレーション機能、Change Trackingによる効率的な更新処理を特徴とし、エンタープライズからクラウドネイティブアプリケーションまで幅広く利用されています。
主な特徴
- LINQ統合: 型安全で直感的なクエリ構文
- Code First/Database First: 柔軟な開発アプローチ
- Change Tracking: 効率的なエンティティ状態管理
- マイグレーション: 自動的なスキーマ変更管理
- クロスプラットフォーム: Windows、Linux、macOSサポート
メリット・デメリット
メリット
- .NET開発者にとって自然で統一された開発体験
- LINQ による強力で型安全なクエリ機能
- Visual Studioとの優れた統合とIntelliSense支援
- Azureクラウドサービスとの深い統合
- マイクロサービスアーキテクチャでの実績豊富
デメリット
- .NET以外の環境では使用できない
- 大量データ処理でのパフォーマンス課題
- 複雑なオブジェクトグラフでのメモリ使用量増大
- 学習コストが比較的高い(特にEntity関係の設定)
参考ページ
書き方の例
インストールと基本セットアップ
# .NET CLI での EF Core インストール
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
# PostgreSQL を使用する場合
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
// Program.cs (.NET 6+)
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// EF Core サービス登録
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyAppDb;Trusted_Connection=true"
}
}
基本的なCRUD操作(エンティティ定義、作成、読み取り、更新、削除)
// Models/User.cs
using System.ComponentModel.DataAnnotations;
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// ナビゲーションプロパティ
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
// Models/Post.cs
public class Post
{
public int Id { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; } = string.Empty;
public string? Content { get; set; }
public int AuthorId { get; set; }
public User Author { get; set; } = null!;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API 設定
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => e.Email).IsUnique();
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
});
modelBuilder.Entity<Post>(entity =>
{
entity.HasOne(p => p.Author)
.WithMany(u => u.Posts)
.HasForeignKey(p => p.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
});
}
}
// 基本的なCRUD操作
using Microsoft.EntityFrameworkCore;
public class UserService
{
private readonly ApplicationDbContext _context;
public UserService(ApplicationDbContext context)
{
_context = context;
}
// 作成
public async Task<User> CreateUserAsync(string name, string email)
{
var user = new User { Name = name, Email = email };
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user;
}
// 読み取り
public async Task<List<User>> GetAllUsersAsync()
{
return await _context.Users.ToListAsync();
}
public async Task<User?> GetUserByIdAsync(int id)
{
return await _context.Users.FindAsync(id);
}
// 更新
public async Task<User?> UpdateUserAsync(int id, string name)
{
var user = await _context.Users.FindAsync(id);
if (user != null)
{
user.Name = name;
await _context.SaveChangesAsync();
}
return user;
}
// 削除
public async Task<bool> DeleteUserAsync(int id)
{
var user = await _context.Users.FindAsync(id);
if (user != null)
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return true;
}
return false;
}
}
高度なクエリとリレーションシップ
// 複雑なLINQクエリ
public class AdvancedQueryService
{
private readonly ApplicationDbContext _context;
public AdvancedQueryService(ApplicationDbContext context)
{
_context = context;
}
// 複合条件クエリ
public async Task<List<User>> GetActiveUsersAsync()
{
return await _context.Users
.Where(u => u.Posts.Any(p => p.CreatedAt >= DateTime.UtcNow.AddDays(-30)))
.Where(u => u.Email.EndsWith("@company.com"))
.OrderByDescending(u => u.CreatedAt)
.Take(10)
.ToListAsync();
}
// JOIN クエリ(Include)
public async Task<List<User>> GetUsersWithPostsAsync()
{
return await _context.Users
.Include(u => u.Posts)
.ToListAsync();
}
// Select による射影
public async Task<List<object>> GetUserSummaryAsync()
{
return await _context.Users
.Select(u => new
{
u.Id,
u.Name,
u.Email,
PostCount = u.Posts.Count(),
LatestPost = u.Posts.OrderByDescending(p => p.CreatedAt).FirstOrDefault()!.Title
})
.ToListAsync();
}
// グループ化と集約
public async Task<List<object>> GetPostCountsByUserAsync()
{
return await _context.Posts
.GroupBy(p => p.Author.Name)
.Select(g => new
{
AuthorName = g.Key,
PostCount = g.Count(),
LatestPostDate = g.Max(p => p.CreatedAt)
})
.OrderByDescending(x => x.PostCount)
.ToListAsync();
}
// Raw SQL クエリ
public async Task<List<User>> GetUsersByRawSqlAsync(string emailDomain)
{
return await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Email LIKE {0}", $"%@{emailDomain}")
.ToListAsync();
}
// ストアドプロシージャ呼び出し
public async Task<List<User>> GetUsersFromStoredProcAsync(int minPosts)
{
return await _context.Users
.FromSqlRaw("EXEC GetActiveUsers @MinPosts = {0}", minPosts)
.ToListAsync();
}
}
マイグレーションとスキーマ管理
# 初回マイグレーション作成
dotnet ef migrations add InitialCreate
# マイグレーション適用
dotnet ef database update
# 新しいマイグレーション追加
dotnet ef migrations add AddPostsTable
# マイグレーション取り消し
dotnet ef migrations remove
# データベース削除
dotnet ef database drop
// カスタムマイグレーション
public partial class AddUserIndexes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email",
unique: true);
migrationBuilder.Sql("CREATE INDEX IX_Users_Name_Email ON Users (Name, Email)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_Email",
table: "Users");
migrationBuilder.Sql("DROP INDEX IX_Users_Name_Email ON Users");
}
}
// プログラムでのマイグレーション実行
public class DatabaseManager
{
public static async Task MigrateDatabaseAsync(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.MigrateAsync();
}
}
パフォーマンス最適化と高度な機能
// トランザクション管理
public class TransactionService
{
private readonly ApplicationDbContext _context;
public TransactionService(ApplicationDbContext context)
{
_context = context;
}
public async Task<User> CreateUserWithPostsAsync(string name, string email, List<string> postTitles)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var user = new User { Name = name, Email = email };
_context.Users.Add(user);
await _context.SaveChangesAsync();
foreach (var title in postTitles)
{
_context.Posts.Add(new Post
{
Title = title,
Content = $"Content for {title}",
AuthorId = user.Id
});
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return user;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
// バッチ操作
public class BatchOperationService
{
private readonly ApplicationDbContext _context;
public BatchOperationService(ApplicationDbContext context)
{
_context = context;
}
public async Task BulkInsertUsersAsync(List<(string Name, string Email)> userData)
{
var users = userData.Select(u => new User
{
Name = u.Name,
Email = u.Email
}).ToList();
_context.Users.AddRange(users);
await _context.SaveChangesAsync();
}
public async Task BulkUpdateUsersAsync(List<int> userIds, string newNamePrefix)
{
await _context.Users
.Where(u => userIds.Contains(u.Id))
.ExecuteUpdateAsync(u => u.SetProperty(x => x.Name, x => newNamePrefix + x.Name));
}
}
// Change Tracking 最適化
public class OptimizedQueryService
{
private readonly ApplicationDbContext _context;
public OptimizedQueryService(ApplicationDbContext context)
{
_context = context;
}
// 読み取り専用クエリ(Change Tracking無効)
public async Task<List<User>> GetUsersReadOnlyAsync()
{
return await _context.Users
.AsNoTracking()
.ToListAsync();
}
// 分割クエリ(Split Query)
public async Task<List<User>> GetUsersWithPostsSplitAsync()
{
return await _context.Users
.AsSplitQuery()
.Include(u => u.Posts)
.ToListAsync();
}
// コンパイル済みクエリ
private static readonly Func<ApplicationDbContext, int, Task<User?>> GetUserByIdCompiled =
EF.CompileAsyncQuery((ApplicationDbContext context, int id) =>
context.Users.FirstOrDefault(u => u.Id == id));
public async Task<User?> GetUserByIdOptimizedAsync(int id)
{
return await GetUserByIdCompiled(_context, id);
}
}
フレームワーク統合と実用例
// ASP.NET Core Web API統合
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserService _userService;
public UsersController(UserService userService)
{
_userService = userService;
}
[HttpGet]
public async Task<ActionResult<List<User>>> GetUsers()
{
var users = await _userService.GetAllUsersAsync();
return Ok(users);
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
[HttpPost]
public async Task<ActionResult<User>> CreateUser(CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request.Name, request.Email);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
}
// Minimal API 統合 (.NET 6+)
app.MapGet("/api/users", async (ApplicationDbContext db) =>
await db.Users.ToListAsync());
app.MapGet("/api/users/{id}", async (int id, ApplicationDbContext db) =>
await db.Users.FindAsync(id)
is User user
? Results.Ok(user)
: Results.NotFound());
app.MapPost("/api/users", async (CreateUserRequest request, ApplicationDbContext db) =>
{
var user = new User { Name = request.Name, Email = request.Email };
db.Users.Add(user);
await db.SaveChangesAsync();
return Results.Created($"/api/users/{user.Id}", user);
});
// Blazor Server統合
@page "/users"
@inject ApplicationDbContext DbContext
<h3>Users</h3>
@if (users == null)
{
<p>Loading...</p>
}
else
{
<table class="table">
@foreach (var user in users)
{
<tr>
<td>@user.Name</td>
<td>@user.Email</td>
<td>@user.Posts.Count posts</td>
</tr>
}
</table>
}
@code {
private List<User>? users;
protected override async Task OnInitializedAsync()
{
users = await DbContext.Users
.Include(u => u.Posts)
.ToListAsync();
}
}
// Background Service統合
public class DataProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public DataProcessingService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// 定期的なデータ処理
var unprocessedUsers = await context.Users
.Where(u => !u.IsProcessed)
.Take(100)
.ToListAsync(stoppingToken);
foreach (var user in unprocessedUsers)
{
user.IsProcessed = true;
// 処理ロジック
}
await context.SaveChangesAsync(stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}