C# Entity Framework CoreでLINQを使ったデータベース検索を完全解説!初心者向けガイド
生徒
「C#でデータベースからデータを検索する方法を教えてください。SQL文を書くのは難しそうで…」
先生
「それなら、Entity Framework CoreとLINQを使う方法がおすすめです。C#のコードだけでデータベース操作ができますよ。」
生徒
「LINQって何ですか?難しくないですか?」
先生
「LINQは、データを検索するための便利な機能です。日本語のような感覚でデータを取得できるので、初心者にも分かりやすいですよ。それでは、基本から見ていきましょう!」
1. Entity Framework CoreとLINQとは?
Entity Framework Core(EF Core)は、C#でデータベース操作を簡単にするための技術です。データベースというのは、たくさんの情報を整理して保管する場所のことです。例えば、図書館の本棚を思い浮かべてください。本棚には本が整理されて並んでいますね。データベースも同じように、商品情報や顧客情報などが整理されて保管されています。
LINQ(リンク)は、「Language Integrated Query」の略で、日本語では「統合言語クエリ」と呼ばれます。クエリとは「問い合わせ」という意味で、データベースに対して「このデータをください」とお願いすることです。LINQを使うと、C#のコードの中で自然な書き方でデータを検索できます。
従来はSQL(エスキューエル)という専用の言語でデータベースに問い合わせをしていましたが、LINQを使えばC#の文法だけでデータを取得できるため、初心者にも扱いやすくなっています。
2. EF CoreとLINQを使う準備
まず、EF Coreを使うための準備が必要です。Visual Studioなどの開発環境を使っている場合、NuGetパッケージマネージャーから必要なライブラリをインストールします。
必要なパッケージは以下の通りです:
Microsoft.EntityFrameworkCore- EF Coreの本体Microsoft.EntityFrameworkCore.SqlServer- SQL Serverを使う場合Microsoft.EntityFrameworkCore.Tools- 開発時に便利なツール
データベースには様々な種類がありますが、ここではSQL Serverを例に説明します。SQLiteやMySQLなど他のデータベースを使う場合は、対応するパッケージをインストールしてください。
DbContext(ディービーコンテキスト)というクラスを作成します。これは、データベースとの接続を管理する重要なクラスです。レストランで例えると、ウェイターのような役割を果たします。お客さん(プログラム)と厨房(データベース)の間でやり取りをしてくれるのです。
3. 基本的なデータ検索(全件取得)
まずは、データベースからすべてのデータを取得する方法を見てましょう。例として、商品情報を管理するProductテーブルからデータを取得します。
using (var context = new MyDbContext())
{
// すべての商品を取得
var products = context.Products.ToList();
// 取得した商品を表示
foreach (var product in products)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
}
このコードでは、context.ProductsでProductsテーブルにアクセスし、ToList()ですべてのデータをリスト形式で取得しています。ToList()は、データベースに実際に問い合わせを行うメソッドです。これを呼び出すまでは、データベースへの問い合わせは実行されません。
foreach文は、取得したデータを一つずつ取り出して処理するための構文です。リストの中身を順番に見ていくイメージですね。
4. 条件を指定したデータ検索(Where句)
特定の条件に合うデータだけを取得したい場合は、Whereメソッドを使います。これは、SQL文のWHERE句と同じ役割を果たします。
using (var context = new MyDbContext())
{
// 価格が1000円以上の商品を取得
var expensiveProducts = context.Products
.Where(p => p.Price >= 1000)
.ToList();
foreach (var product in expensiveProducts)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
}
Where(p => p.Price >= 1000)という部分が条件指定です。p => p.Price >= 1000はラムダ式と呼ばれる書き方で、「pという変数に対して、そのPriceが1000以上のもの」という意味になります。矢印(=>)の左側が変数、右側が条件です。
これは、図書館で「値段が1000円以上の本を探してください」とお願いするのと同じです。すべての本を調べて、条件に合う本だけを返してくれます。
5. 複数の条件を組み合わせたデータ検索
複数の条件を組み合わせてデータを検索することもできます。&&(かつ)や||(または)を使って条件を組み合わせます。
using (var context = new MyDbContext())
{
// 価格が500円以上1500円以下で、在庫がある商品を取得
var availableProducts = context.Products
.Where(p => p.Price >= 500 && p.Price <= 1500 && p.Stock > 0)
.ToList();
Console.WriteLine($"該当する商品数: {availableProducts.Count}件");
foreach (var product in availableProducts)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円, 在庫: {product.Stock}個");
}
}
&&は「かつ(AND)」を意味し、すべての条件を満たす必要があります。||は「または(OR)」を意味し、いずれかの条件を満たせば該当します。
この例では、「価格が500円以上」かつ「価格が1500円以下」かつ「在庫が0より多い」という3つの条件すべてを満たす商品を検索しています。買い物をするとき、「予算内で、在庫がある商品」を探すのと同じ感覚です。
6. データの並び替え(OrderBy、OrderByDescending)
取得したデータを特定の順番に並び替えるには、OrderBy(昇順)またはOrderByDescending(降順)を使います。昇順とは小さい順、降順とは大きい順のことです。
using (var context = new MyDbContext())
{
// 商品を価格の安い順に並び替え
var sortedProducts = context.Products
.OrderBy(p => p.Price)
.ToList();
Console.WriteLine("=== 価格の安い順 ===");
foreach (var product in sortedProducts.Take(5))
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
// 商品を価格の高い順に並び替え
var descendingProducts = context.Products
.OrderByDescending(p => p.Price)
.ToList();
Console.WriteLine("\n=== 価格の高い順 ===");
foreach (var product in descendingProducts.Take(5))
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
}
OrderBy(p => p.Price)で価格の安い順(昇順)に、OrderByDescending(p => p.Price)で価格の高い順(降順)に並び替えられます。Take(5)は、先頭から5件だけを取得するメソッドです。
本を並べるとき、背の低い順や高い順に整理するのと同じイメージです。検索結果を見やすく整理するために、並び替えはとても便利な機能です。
7. 特定の1件だけを取得する方法
条件に合う最初の1件だけを取得したい場合は、FirstOrDefaultやSingleOrDefaultを使います。両者の違いを理解することが大切です。
FirstOrDefaultは、条件に合う最初のデータを返します。該当するデータがない場合はnullを返します。複数のデータが該当しても、最初の1件だけを返します。
SingleOrDefaultは、条件に合うデータが1件だけの場合にそれを返します。該当するデータがない場合はnullを返し、2件以上ある場合はエラーになります。「絶対に1件しかないはず」というデータを取得するときに使います。
using (var context = new MyDbContext())
{
// IDが1の商品を取得(IDは通常1件のみなのでSingleOrDefaultを使用)
var product = context.Products
.SingleOrDefault(p => p.Id == 1);
if (product != null)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
else
{
Console.WriteLine("該当する商品が見つかりませんでした。");
}
// 最も安い商品を1件取得
var cheapestProduct = context.Products
.OrderBy(p => p.Price)
.FirstOrDefault();
if (cheapestProduct != null)
{
Console.WriteLine($"最安値商品: {cheapestProduct.Name}, 価格: {cheapestProduct.Price}円");
}
}
このコードでは、まずIDで商品を検索し、次に最も安い商品を取得しています。if文でnullチェックをすることで、データが見つからなかった場合のエラーを防いでいます。
8. 部分一致検索(文字列検索)
商品名などの文字列で部分一致検索をする場合は、Contains、StartsWith、EndsWithメソッドを使います。これは、Googleで検索するときのように、一部の文字だけで検索できる機能です。
Containsは「含む」、StartsWithは「で始まる」、EndsWithは「で終わる」という意味です。
using (var context = new MyDbContext())
{
string searchWord = "ノート";
// 商品名に「ノート」を含む商品を検索
var matchedProducts = context.Products
.Where(p => p.Name.Contains(searchWord))
.ToList();
Console.WriteLine($"「{searchWord}」を含む商品:");
foreach (var product in matchedProducts)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
// 「新」で始まる商品を検索
var newProducts = context.Products
.Where(p => p.Name.StartsWith("新"))
.ToList();
Console.WriteLine("\n「新」で始まる商品:");
foreach (var product in newProducts)
{
Console.WriteLine($"商品名: {product.Name}");
}
}
この検索方法は、ECサイトの商品検索機能などでよく使われます。ユーザーが入力したキーワードをsearchWord変数に入れて、Containsで部分一致検索をすることで、柔軟な検索機能を実装できます。
9. データの件数を取得する(Count)
条件に合うデータが何件あるかを知りたい場合は、Countメソッドを使います。すべてのデータを取得せずに件数だけを取得できるため、効率的です。
また、データが存在するかどうかだけを確認したい場合は、Anyメソッドが便利です。Anyは、条件に合うデータが1件でもあればtrue、なければfalseを返します。
using (var context = new MyDbContext())
{
// すべての商品数を取得
int totalCount = context.Products.Count();
Console.WriteLine($"商品の総数: {totalCount}件");
// 在庫がある商品の件数を取得
int inStockCount = context.Products.Count(p => p.Stock > 0);
Console.WriteLine($"在庫あり商品: {inStockCount}件");
// 1000円以上の商品が存在するか確認
bool hasExpensiveProduct = context.Products.Any(p => p.Price >= 1000);
if (hasExpensiveProduct)
{
Console.WriteLine("1000円以上の商品が存在します。");
}
else
{
Console.WriteLine("1000円以上の商品はありません。");
}
}
Count()メソッドは引数なしで使うとすべての件数を、引数に条件を指定すると条件に合う件数を返します。大量のデータを扱うシステムでは、Countを使うことでメモリの節約になります。
10. LINQのメソッドチェーンで複雑な検索
LINQの強力な点は、複数のメソッドを繋げて(メソッドチェーン)、複雑な検索を簡潔に書けることです。条件指定、並び替え、件数制限などを組み合わせることができます。
using (var context = new MyDbContext())
{
// 在庫があり、価格が500円以上の商品を、価格の安い順に並べて、上位10件を取得
var topProducts = context.Products
.Where(p => p.Stock > 0 && p.Price >= 500)
.OrderBy(p => p.Price)
.Take(10)
.ToList();
Console.WriteLine("おすすめ商品トップ10:");
int rank = 1;
foreach (var product in topProducts)
{
Console.WriteLine($"{rank}位: {product.Name} - {product.Price}円 (在庫: {product.Stock}個)");
rank++;
}
// 商品名に「ペン」を含む商品を価格の高い順に並べて、3件目から5件を取得
var penProducts = context.Products
.Where(p => p.Name.Contains("ペン"))
.OrderByDescending(p => p.Price)
.Skip(2)
.Take(5)
.ToList();
Console.WriteLine("\n「ペン」を含む商品(3位~7位):");
foreach (var product in penProducts)
{
Console.WriteLine($"商品名: {product.Name}, 価格: {product.Price}円");
}
}
Skipメソッドは、指定した件数だけデータを読み飛ばします。Skip(2).Take(5)とすることで、「最初の2件を飛ばして、次の5件を取得する」という処理ができます。これは、ページング処理(ページ分割)を実装するときに便利です。
メソッドチェーンは、上から順番に処理されます。まずWhereで条件に合うデータを絞り込み、次にOrderByで並び替え、最後にTakeで件数を制限する、という流れです。水道の蛇口に複数のフィルターを付けるようなイメージで、段階的にデータを絞り込んでいきます。
11. よくあるエラーと対処法
LINQを使ったデータベース検索で初心者がよく遭遇するエラーと、その対処法を紹介します。
エラー1: NullReferenceException
データが見つからないときにnullを参照してしまうエラーです。FirstOrDefaultやSingleOrDefaultを使った後は、必ずnullチェックをしましょう。
var product = context.Products.FirstOrDefault(p => p.Id == 999);
// productがnullの可能性があるので、チェックが必要
if (product != null)
{
Console.WriteLine(product.Name);
}
エラー2: InvalidOperationException(Sequenceに複数の要素が含まれています)
SingleやSingleOrDefaultを使ったとき、条件に合うデータが2件以上あるとこのエラーが発生します。データが1件だけと確信できない場合は、FirstOrDefaultを使いましょう。
エラー3: ToListを忘れる
LINQのクエリは遅延実行という特性があり、ToList()やCount()などを呼び出すまでデータベースへの問い合わせは実行されません。結果を確実に取得するには、ToList()を忘れずに付けましょう。