はじめに
この記事は、
EF Core で安全に使える拡張メソッドの「型」だけを集めたテンプレ集です。
- どこまでなら SQL に変換されるか
- どこからメモリ処理になるか
を常に意識した形になっています。
※ 本記事は、EF Core を業務で使い始めた〜使い慣れてきた方向けです。
基本ルール(先に結論)
- 戻り値は IQueryable を維持する
- 中で ToList() / AsEnumerable() を呼ばない
- Where は Expression or IQueryable 合成
- Select は DB でできる射影まで
① Where 用テンプレ(条件追加)
最も基本形
public static IQueryable<User> WhereActive(
this IQueryable<User> query)
{
return query.Where(u => u.IsActive);
}
db.Users
.WhereActive()
.ToList();
条件付き Where(if 文で分岐)
public static IQueryable<User> WhereActiveIf(
this IQueryable<User> query,
bool onlyActive)
{
if (!onlyActive) { return query; }
return query.Where(u => u.IsActive);
}
👉 条件を外から渡しても SQL は 1 本
② Where(Expression 版・再利用向け)
public static class UserPredicates
{
public static Expression<Func<User, bool>> IsAdult()
=> u => u.Age >= 20;
}
db.Users
.Where(UserPredicates.IsAdult())
.ToList();
👉 EF Core が読める形
③ Select(射影)用テンプレ
DTO / Row への変換
public static IQueryable<UserRow> ToRow(
this IQueryable<User> query)
{
return query.Select(u => new UserRow
{
Id = u.Id,
Name = u.Name,
CreatedAt = u.CreatedAt
});
}
db.Users
.WhereActive()
.ToRow()
.ToList();
❌ NG:Select 内でメソッド呼び出し
// NG
.Select(u => new UserRow
{
DisplayName = FormatName(u.Name)
});
👉 SQL に変換できない
④ OrderBy 用テンプレ
public static IQueryable<User> OrderByNewest(
this IQueryable<User> query)
{
return query.OrderByDescending(u => u.CreatedAt);
}
db.Users
.WhereActive()
.OrderByNewest()
.ToList();
⑤ ページング用テンプレ
public static IQueryable<T> Page<T>(
this IQueryable<T> query,
int page,
int pageSize)
{
return query
.Skip((page - 1) * pageSize)
.Take(pageSize);
}
db.Users
.WhereActive()
.OrderByNewest()
.Page(1, 20)
.ToList();
⑥ ❌ やってはいけない拡張メソッド例
中で ToList
public static IEnumerable<User> Active(
this IQueryable<User> query)
{
return query.Where(u => u.IsActive).ToList();
}
- クエリ合成できない
- 呼び出し側が DB / メモリを判断できない
実装者向け最終チェック
- 戻り値は IQueryable のままか?
- この処理、SQL で書けるか?
- メモリ処理は ToList() 以降に閉じているか?
まとめ
良い拡張メソッドは
「LINQ を短くする」のではなく
「安全な形に固定する」ためにある
このテンプレをベースにすると、
EF Core の拡張メソッドはかなり事故りにくくなります。