1

数日かけて、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つまたは両方に答えてくれたらうれしいです:

  1. トランザクション中に通常のデータベースと通信するかのようにエンティティ フレームワークの動作を変更する可能性はありますか? もしそうなら...どうすればいいですか?

  2. 「エンティティ フレームワークのコンテキストを使用する方法」に回答するための適切なリソースはどこにありますか? 「何を頼りにすればいいの?」などの質問に答えます。および「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() を呼び出すことができますが、「ロールバック」の可能性が失われます。

よろしく、

ステファン

4

3 に答える 3

3

おわかりのように、Entity Framework は、読み込まれたエンティティを追跡し、同じエンティティにアクセスするクエリごとに同じ参照を返します。これは、クエリから返されたデータが現在のメモリ内バージョンのデータと一致し、必ずしもデータベース内のデータと一致しないことを意味します。

データベースの値にアクセスする必要がある場合は、いくつかのオプションがあります。

  1. newDbContextを使用してエンティティをロードします。
  2. .AsNoTracking()エンティティの追跡されていないコピーを読み込むために使用します。
  3. context.Entry(entity).GetDatabaseValues()データベースからプロパティ値をロードするために使用します。

ローカル エンティティのプロパティをデータベースの値で上書きする場合は、 を呼び出す必要がありますcontext.Entry(entity).Reload()

于 2012-10-30T13:29:34.563 に答える
2

更新をトランザクションにラップして、SQL の例と同じ結果を得ることができます。

using (var transaction = new TransactionScope())
{

    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;
    // send UPDATE to Database but don't commit transcation
    db.SaveChanges();

    var result2 = from r in db.Tests
                    where r.value == 10
                    select r;
    // should not return anything
    Trace.Assert(result2.Count() == 0);

    // This way you can commit the transaction:
    // transaction.Complete();

    // but we do nothing and after this line, the transaction is rolled back
}

詳細については、 http://msdn.microsoft.com/en-us/library/bb896325( v=vs.100 ).aspx を参照してください。

于 2012-10-30T15:57:43.893 に答える
1

あなたの問題は式ツリーだと思います。Entity Framework は、SaveChanges()既に述べたように、あなたが言うと、データベースに対してクエリを実行します。コンテキスト内で何かを操作すると、変更はデータベースでは発生せず、物理メモリで発生します。アクションを呼び出すとSaveChanges()、たとえば SQL に変換されます。

簡単なことをするとselect、データにアクセスした瞬間にデータベースが照会されます。したがって、 を呼び出していない場合、SaveChanges()(SQL) を使用してデータベース内のデータセットを検索しますSELECT* FROM Test WHERE VALUE = 10が、式ツリーから解釈すると、 である必要がありますvalue == 4711

EF のトランザクションはストレージで発生しています。あなたが以前に行うことはすべてSaveChanges()あなたの取引です。詳細については、MSDNを参照してください。

EF に関する情報については、おそらく最新の非常に優れたリソースは、Microsoft Data Developer Centerです。

于 2012-10-30T13:17:30.590 に答える