**C# の文字列が Dapper で SQL Server インデックスを静かに破壊する理由**
Dapper を使って SQL Server データベースへクエリを投げる際、文字列結合や文字列補間(string interpolation)でクエリを作成することはよくあります。
しかし、この一見無害な手法がインデックスの性能を黙って破壊してしまうケースがあります。
---
## なぜ起きるのか
1. **暗黙の型変換**
`string` と `int`・`bool` など非文字列型を結合すると、SQL Server は列値を `nvarchar` に変換せざるを得ません。
2. **インデックス回避**
この暗黙変換により最適化器は既存の数値や日付インデックスを利用できず、フルテーブルスキャンが発生します。
---
## 問題を引き起こす典型的なパターン
| パターン | 何をしているか | インデックスへの影響 |
|---------|-----------------|----------------------|
| `WHERE Id = " + id`(文字列結合) | `Id` 列を `nvarchar` に変換 | フルスキャン |
| `$"SELECT * FROM Users WHERE IsActive = {isActive}"`(補間) | ブール値も同様に `nvarchar` へ変換 | フルスキャン |
| `WHERE CreatedDate >= @date.ToString()` | 日付を文字列へ変換 | インデックスが失われる |
---
## 修正方法
1. **インライン値ではなくパラメータを使用する**
```csharp
var sql = "SELECT * FROM Users WHERE Id = @Id";
connection.Query<User>(sql, new { Id = id });
```
2. **型の一貫性を保つ**
列が期待する正確な型(`int`、`DateTime` など)で渡す。
3. **C# で暗黙変換を避ける**
必要なら明示的にキャストまたは変換し、安全かつ意図した変換のみ行う。
---
## 簡易チェックリスト
- [ ] Dapper に渡す値は文字列化せず、型付きである。
- [ ] 変数データと SQL フラグメントのインライン結合を行わない。
- [ ] すべてのクエリに `@ParameterName` プレースホルダーを使用する。
これらのガイドラインに従えば、インデックスの整合性を保ちつつクエリを高速かつ効率的に維持できます。
## Japanese Translation:
**概要:**
.NET/Dapper アプリケーションでは、C# の文字列を `nvarchar(4000)` として渡すと、SQL Server が `varchar` 列に対して暗黙の型変換(implicit conversions)を実行します。これにより、インデックス検索がスキャンに置き換わり、論理読み取り数が単桁から数万に膨らみ、CPU/I/O の使用率が急増します(例:`CONVERT_IMPLICIT(nvarchar(255), [Sales].[ProductCode], 0)`)。
正確性には影響しませんが、実行計画や Query Store の警告で明らかになります。特に `SQL_Latin1_General_CP1_CI_AS` などの照合順序では顕著です。
**修正:** パラメータを ANSI として明示的に宣言し、列サイズと一致させます。
```csharp
var p = new DynamicParameters();
p.Add("productCode", productCode, DbType.AnsiString, size: 100);
await conn.QueryFirstOrDefaultAsync<Product>(sql, p);
```
または匿名オブジェクトを使用する場合:
```csharp
new { productCode = new DbString { Value = productCode, IsAnsi = true, Length = 100 } }
```
スキーマ変更、インデックス更新、クエリ書き換えは不要です。パフォーマンスの改善は即座に実感できます。
**監査ヒント:** Query Store で `@nvarchar(4000)` を検索し、varchar 列へ文字列を渡す匿名オブジェクトをコード内でスキャンしてください。
**ベストプラクティス:** 将来のリグレッション防止のために、`DbType.AnsiString`(または `IsAnsi = true`)を使用した理由をコメントしておくことが推奨されます。
この簡単な調整でサーバー負荷を低減し、コスト削減とスケールアップが実現します。結果として開発者・ユーザー・広範な .NET/SQL Server コミュニティ全体に恩恵をもたらします。