かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Entity Framework 5.0個人的メモ

ちょっと更新処理とかのことが気になったので試した結果をメモメモ。
こんなデータコンテキストを用意しておきます。

// 簡単データコンテキスト
public class SampleContext : DbContext
{
    public DbSet<Person> People { get; set; }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

適当にデータを突っ込む場合

// 適当にデータ突っ込む
using (var ctx = new SampleContext())
{
    ctx.People.Add(new Person { Name = "田中1" });
    ctx.People.Add(new Person { Name = "田中2" });
    ctx.People.Add(new Person { Name = "田中3" });
    ctx.People.Add(new Person { Name = "田中4" });
    ctx.People.Add(new Person { Name = "田中5" });
    ctx.People.Add(new Person { Name = "田中6" });
    ctx.SaveChanges();
}

このデータがあるという前提で、DBのデータを書き換える一番一般的な方法

Console.WriteLine("DB更新------------------");
using (var ctx = new SampleContext())
{
    // DB書き換え
    foreach (var p in ctx.People)
    {
        p.Name += " * ";
    }
    Console.WriteLine("{0}件更新", ctx.SaveChanges());
    // ローカルのデータを確認
    foreach (var p in ctx.People.Local)
    {
        Console.WriteLine("{0} {1}", p.Id, p.Name);
    }
}

プロパティを書き換えてSaveChangesを呼びます。DbSetのLocalメソッドでDBに再度アクセスすることなく、ローカルに存在するデータを取得できます。実行結果はこんな感じ。

6件更新
145 田中1 *
146 田中2 *
147 田中3 *
148 田中4 *
149 田中5 *
150 田中6 *

続いて、クエリにAsNoTrackingを指定した場合。Localにデータは入りません。さらにAsNoTrackingで取得したデータを書き換えてSaveChangesしてもDBは更新されません。

using (var ctx = new SampleContext())
{
    // AsNoTracking
    foreach (var p in ctx.People.AsNoTracking())
    {
        Console.WriteLine("{0} {1}", p.Id, p.Name);
    }
    Console.WriteLine("--");
    // Localにデータ入らない
    foreach (var p in ctx.People.Local)
    {
        Console.WriteLine("{0} {1}", p.Id, p.Name);
    }
    // データ書き換えても無駄
    foreach (var p in ctx.People.AsNoTracking())
    {
        p.Name = "NoTracking太郎";
    }
    Console.WriteLine("{0}件更新", ctx.SaveChanges());
}

こんな感じの実行結果になります。Localにデータが入ってないことがわかります。あとデータ書き換えても更新されません。

163 田中1
164 田中2
165 田中3
166 田中4
167 田中5
168 田中6
--
0件更新

次に、DbContextのConfigurationから変更を自動で検知しないという状態にしたときの動き。

Console.WriteLine("全部トラッキングしない------------------");
using (var ctx = new SampleContext())
{
    // 全体的にトラッキングしない
    ctx.Configuration.AutoDetectChangesEnabled = false;

    foreach (var p in ctx.People)
    {
        Console.WriteLine("{0} {1}", p.Id, p.Name);
        p.Name += " * ";
    }
    Console.WriteLine("{0}件更新", ctx.SaveChanges());
    // Localにはデータ入ってる
    foreach (var p in ctx.People.Local)
    {
        Console.WriteLine("{0} {1}", p.Id, p.Name);
    }
    
    // 変更チェックを有効にしてSaveChanges
    ctx.Configuration.AutoDetectChangesEnabled = true;
    Console.WriteLine("{0}件更新", ctx.SaveChanges());
}

実行結果を見ると、Localにデータは保持されてるけど変更はDBまでいかない感じです。でも、再度AutoDetectChangesEnabledをtrueにしてSaveChangesを呼ぶと変更箇所を認識してくれるみたいです。
何度もローカルのデータを再利用しつつ、読み取り専用のデータ向きなのかな…?いまいちメリットが把握できない。

193 田中1
194 田中2
195 田中3
196 田中4
197 田中5
198 田中6
0件更新
193 田中1 *
194 田中2 *
195 田中3 *
196 田中4 *
197 田中5 *
198 田中6 *
6件更新

最後はどうにでもな〜れ!な方法。

using (var ctx = new SampleContext())
{
    // 一括更新みたいなのをトランザクションはってやりたいときの最後の手段
    using (var tx = new TransactionScope())
    {
        Console.WriteLine(
            "{0}件削除",
            ctx.Database.ExecuteSqlCommand("DELETE FROM PEOPLE"));
        tx.Complete();
    }
}

SQL直実行なのでなんでもできます。特定の条件にマッチするものを一括更新とかの場合は、こっちのほうが効率よさそうですね。実行結果は以下のような感じです。

6件削除