数日かけて、Entity Framework (バージョン 4.4.0.0) の奇妙な動作によって引き起こされたバグを見つけました。説明のために、小さなテスト プログラムを作成しました。最後に、それについて私が持っているいくつかの質問を見つけます。
宣言
ここに、テスト データセットを表すクラス「Test」があります。ID(主キー)と「値」プロパティのみがあります。TestContext では、「Test」オブジェクトをデータベース テーブルとして処理する DbSet テストを実装します。
public class Test
{
public int ID { get; set; }
public int value { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Test> Tests { get; set; }
}
初期化
ここで、(存在する場合) エントリを "Tests" テーブルから削除し、唯一の "Test" オブジェクトを追加します。ID=1 (主キー) と値=10 です。
// Create a new DBContext...
TestContext db = new TestContext();
// Remove all entries...
foreach (Test t in db.Tests) db.Tests.Remove(t);
db.SaveChanges();
// Add one test entry...
db.Tests.Add(new Test { ID = 1, value = 10 });
db.SaveChanges();
テスト
最後に、いくつかのテストを実行します。元の値 (=10) でエントリを選択し、エントリの「値」を 4711 に変更します。ただし、db.SaveChanges(); は呼び出しません。!!!
// Find our entry by it's value (=10)
var result = from r in db.Tests
where r.value == 10
select r;
Test t2 = result.FirstOrDefault();
// change its value from 10 to 4711...
t2.value = 4711;
ここで、元の値 (=10) で (古い) エントリを見つけようとし、その結果についていくつかのテストを行います。
// now we try to select it via its old value (==10)
var result2 = from r in db.Tests
where r.value == 10
select r;
// Did we get it?
if (result2.FirstOrDefault() != null && result2.FirstOrDefault().value == 4711)
{
Console.WriteLine("We found the changed entry by it's old value...");
}
プログラムを実行すると、「古い値で変更されたエントリが見つかりました...」というメッセージが実際に表示されます。これは、r.value == 10 のクエリを実行したことを意味し、何かが見つかりました... これは受け入れられます。しかし、すでに変更されたオブジェクトを受け取ります(値== 10を満たしていません)!!!
注: "where r.value == 4711" に対して空の結果セットが返されます。
さらなるテストで、Entity Framework は常に同じオブジェクトへの参照を渡すことがわかりました。一方の参照で値を変更すると、もう一方の参照でも変更されます。まあ、それは大丈夫です...しかし、それが起こることを知っておくべきです.
Test t3 = result2.FirstOrDefault();
t3.value = 42;
if (t2.value == 42)
{
Console.WriteLine("Seems as if we have a reference to the same object...");
}
概要
同じデータベース コンテキストで (SaveChanges() を呼び出さずに) LINQ クエリを実行すると、同じ主キーがあれば、同じオブジェクトへの参照を受け取ります。奇妙なことに、オブジェクトを変更しても、古い値で (のみ!) それを見つけることができます。しかし、すでに変更されたオブジェクトへの参照を受け取ります。これは、クエリの制限 (値 == 10) が、SaveChanges() の最後の呼び出し以降に変更されたエントリに対して保証されないことを意味します。
質問
もちろん、私はおそらくここで何らかの影響を受けて生きなければなりません。しかし、少し変更するたびに「SaveChanges()」を実行することは避けたいと思います。特に、トランザクション処理に使用したいので...何か問題が発生した場合に変更を元に戻すことができるようにします。
誰かが私に次の質問の1つまたは両方に答えてくれたらうれしいです:
トランザクション中に通常のデータベースと通信するかのようにエンティティ フレームワークの動作を変更する可能性はありますか? もしそうなら...どうすればいいですか?
「エンティティ フレームワークのコンテキストを使用する方法」に回答するための適切なリソースはどこにありますか? 「何を頼りにすればいいの?」などの質問に答えます。および「DBContext オブジェクトのスコープを選択する方法」?
編集#1
Richard は、元の (変更されていない) データベース値にアクセスする方法を説明しました。これは価値があり役に立ちますが、目標を明確にしたいという衝動に駆られています...
SQL を使用するとどうなるか見てみましょう。テーブル「テスト」をセットアップします。
CREATE TABLE Tests (ID INT, value INT, PRIMARY KEY(ID));
INSERT INTO Tests (ID, value) VALUES (1,10);
次に、値が 10 のエンティティを最初に検索するトランザクションがあります。その後、これらのエントリの値を更新し、それらのエントリを再度検索します。SQL では、更新されたバージョンで既に作業しているため、2 番目のクエリの結果は見つかりません。結局、「ロールバック」を行うので、エントリの値は再び 10 になるはずです...
START TRANSACTION;
SELECT ID, value FROM Tests WHERE value=10; {1 result}
UPDATE Tests SET value=4711 WHERE ID=1; {our update}
SELECT ID, value FROM Tests WHERE value=10; {no result, as value is now 4711}
ROLLBACK; { just for testing transactions... }
Entity Framework (EF) でまさにこの動作をしたいと思います。ここで、db.SaveChanges(); ここで、すべての LINQ クエリは「SELECT」ステートメントと同等であり、エンティティへのすべての書き込みアクセスは「UPDATE」と同じです。EF が実際に UPDATE ステートメントを呼び出すタイミングは気にしませんが、SQL データベースを直接使用する場合と同じように動作する必要があります...もちろん、「SaveChanges()」が呼び出されて正常に返される場合は、すべてのデータが正しく永続化されていることが保証されます。
注: はい、すべてのクエリの前に db.SaveChanges() を呼び出すことができますが、「ロールバック」の可能性が失われます。
よろしく、
ステファン