0Pricing
C# Academy · レッスン

Raw SQL、ストアドプロシージャと補間

FromSqlRaw、FromSqlInterpolated、ストアドプロシージャでRaw SQLを安全に実行し、インジェクションを防ぎます。

「Raw SQL、ストアドプロシージャと補間」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

EF CoreでRaw SQLを使うタイミング

ほとんどのクエリはLINQで問題なく処理できますが、複雑な分析、ベンダー固有の関数、ストアドプロシージャ、またはLINQではうまく変換できないパフォーマンス重視のクエリなど、Raw SQLが必要になるシナリオもあります。

EF Coreには、セキュリティを損なわずにRaw SQLを安全に使用する方法が用意されています。

FromSqlRaw:安全なパラメーター化クエリ

FromSqlRawはRaw SQLを実行し、結果をエンティティにマッピングします。SQLインジェクションを防ぐため、必ず{0}プレースホルダーを使用し、文字列連結は決して行わないでください。

// SAFE: parameterized
var products = await _db.Products
    .FromSqlRaw("SELECT * FROM Products WHERE Price > {0}", 100m)
    .ToListAsync();

// DANGEROUS: never do this
// FromSqlRaw("SELECT * FROM Products WHERE Price > " + price)

FromSqlInterpolated:補間文字列の安全性

FromSqlInterpolatedはC#の補間文字列を受け取ります。EF Coreが補間された値を自動的にSQLパラメーターへ変換するため、安全で読みやすいコードになります。

decimal minPrice = 100m;
string category = "Electronics";

var products = await _db.Products
    .FromSqlInterpolated(
        $"SELECT * FROM Products WHERE Price > {minPrice} AND Category = {category}")
    .OrderBy(p => p.Name)
    .ToListAsync();
// Values become @p0, @p1 parameters automatically

FromSql後のLINQ合成

FromSqlの後にLINQ演算子を連結できます。EF CoreはRaw SQLをサブクエリでラップし、その上にLINQを適用します。フィルタリングやページングの追加に便利です。

decimal min = 50m;
var page = await _db.Products
    .FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {min}")
    .Where(p => p.IsActive)        // additional LINQ filter
    .OrderBy(p => p.Price)
    .Skip(0).Take(20)              // pagination
    .ToListAsync();
// Generates: SELECT ... FROM (raw SQL) AS p WHERE ...

ストアドプロシージャの実行

エンティティの行を返すストアドプロシージャはFromSqlRawで呼び出します。行を返さないプロシージャ(INSERTやUPDATEなど)には、ExecuteSqlRawAsyncを使用してください。

// Stored proc returning Products
var products = await _db.Products
    .FromSqlRaw("EXEC GetProductsByCategory {0}", "Books")
    .ToListAsync();

// Stored proc with no return rows
await _db.Database.ExecuteSqlRawAsync(
    "EXEC ArchiveOldOrders {0}",
    DateTime.UtcNow.AddYears(-1));

DMLに対するExecuteSqlInterpolated

ExecuteSqlInterpolatedは、補間文字列の安全性を確保しながらDML文(INSERT、UPDATE、DELETE)を実行します。影響を受けた行数を返します。

int days = 30;
int affected = await _db.Database.ExecuteSqlInterpolatedAsync(
    $"UPDATE Orders SET Status = 'Expired' WHERE PlacedAt < {DateTime.UtcNow.AddDays(-days)}");

Console.WriteLine($"{affected} orders expired");

エンティティ以外の型をクエリする

SqlQuery<T>(.NET 7以降)を使うと、EF Coreのエンティティではない任意のスカラー型やDTO型をクエリできます。DbSetは必要ありません。

// Query a DTO directly — no entity needed
var stats = await _db.Database
    .SqlQuery<ProductStats>(
        $"SELECT Category, COUNT(*) AS Count, AVG(Price) AS AvgPrice FROM Products GROUP BY Category")
    .ToListAsync();

public record ProductStats(string Category, int Count, decimal AvgPrice);

ビュー向けのキーなしエンティティ型

HasNoKey()とToView()を使って、データベースビューやRaw SQLの結果をキーなしエンティティ型にマッピングできます。主キーがなくてもDbSetと同じようにクエリできます。

public class ProductSalesSummary
{
    public string Category { get; set; } = "";
    public int TotalSold { get; set; }
    public decimal Revenue { get; set; }
}

// In OnModelCreating:
modelBuilder.Entity<ProductSalesSummary>()
    .HasNoKey()
    .ToView("vw_ProductSalesSummary");

// Query:
var summary = await _db.Set<ProductSalesSummary>().ToListAsync();

SQLインジェクションの落とし穴を避ける

鉄則は、ユーザー入力をSQL文字列に決して連結しないことです。FromSqlRawのプレースホルダー、FromSqlInterpolated、またはEF CoreのLINQを使って、必ずパラメーター化クエリを使用してください。

// SAFE
var name = userInput;
var result = await _db.Products
    .FromSqlInterpolated($"SELECT * FROM Products WHERE Name = {name}")
    .ToListAsync();

// INJECTION RISK (never do this)
// FromSqlRaw("SELECT * FROM Products WHERE Name = '" + name + "'");

ADO.NETへのフォールバックによる出力パラメーター

EF Coreはストアドプロシージャの出力パラメーターをネイティブにはサポートしていません。その場合は、_db.Database.GetDbConnection()を使ってADO.NETに切り替えます。

await _db.Database.OpenConnectionAsync();
var conn = _db.Database.GetDbConnection();
using var cmd = conn.CreateCommand();
cmd.CommandText = "EXEC GetOrderCount @CustomerId, @Count OUTPUT";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@CustomerId", 5));
var outParam = new SqlParameter("@Count", SqlDbType.Int) { Direction = ParameterDirection.Output };
cmd.Parameters.Add(outParam);
await cmd.ExecuteNonQueryAsync();
int count = (int)outParam.Value;

実践例:全文検索のフォールバック

LINQではデータベース固有の全文検索述語を表現できません。FromSqlInterpolatedを使って、ネイティブのFREETEXTやtsvectorの機能を利用してください。

string query = "keyboard mechanical";
var results = await _db.Products
    .FromSqlInterpolated(
        $"SELECT * FROM Products WHERE FREETEXT(Name, {query})")
    .AsNoTracking()
    .Take(20)
    .ToListAsync();

確認問題

パラメーター化クエリでは、文字列連結よりもFromSqlInterpolatedのほうが安全なのはなぜですか?

まとめ:EF CoreでのRaw SQL

要点:

  • FromSqlRaw:{0}プレースホルダーを使用し、ユーザー入力を決して連結しない
  • FromSqlInterpolated:安全な補間文字列 → ADO.NETパラメーター
  • FromSqlの後にLINQ演算子を連結して、フィルタリング、並べ替え、ページングを追加する
  • ExecuteSqlInterpolatedAsync:安全なDML実行
  • SqlQuery<T>:エンティティ以外のDTOをクエリする(.NET 7以降)
  • 出力パラメーターや複雑なストアドプロシージャには、ADO.NETを直接使用する

よくある質問

「Raw SQL、ストアドプロシージャと補間」レッスンは無料ですか?

はい。「Raw SQL、ストアドプロシージャと補間」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「Raw SQL、ストアドプロシージャと補間」で何を学びますか?

FromSqlRaw、FromSqlInterpolated、ストアドプロシージャでRaw SQLを安全に実行し、インジェクションを防ぎます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「Raw SQL、ストアドプロシージャと補間」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Eager、Lazy、Explicit Loading
  2. Raw SQL、ストアドプロシージャと補間
  3. コンパイル済みクエリとパフォーマンス
  4. グローバルクエリフィルターと所有エンティティ
← C# Academyに戻る