0

私は単純な関係を持っています

ここに画像の説明を入力

上記のようなモデルで簡単なアプリを作成しました。DBが変更されるたびに、アプリケーション内のモデルを更新する必要があります。GetDBChanges ストアド プロシージャを呼び出すことで、最新の変更を取得できます。(メソッド T1Elapsed を参照)

ここにアプリがあります:

class Program
{
    private static int? _lastDbChangeId;
    private static readonly MASR2Entities Model = new MASR2Entities();
    private static readonly Timer T1 = new Timer(1000);
    private static readonly Timer T2 = new Timer(1000);
    private static Strategy _strategy = null;

    static void Main(string[] args)
    {
        using (var ctx = new MASR2Entities())
        {
            _lastDbChangeId = ctx.GetLastDbChangeId().SingleOrDefault();
        }
        _strategy = Model.Strategies.FirstOrDefault(st => st.StrategyId == 224);

        T1.Elapsed += T1Elapsed;
        T1.Start();

        T2.Elapsed += T2Elapsed;
        T2.Start();

        Console.ReadLine();
    }

    static void T2Elapsed(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("All rules: " + Model.StrategyRules.Count());
        Console.WriteLine("Strategy: name=" + _strategy.Name + " RulesCount=" + _strategy.StrategyRules.Count);
    }

    private static void T1Elapsed(object sender, ElapsedEventArgs e)
    {
        T1.Stop();
        try
        {
            using (var ctx = new MASR2Entities())
            {
                var changes = ctx.GetDBChanges(_lastDbChangeId).ToList();
                foreach (var dbChange in changes)
                {
                    Console.WriteLine("DbChangeId:{0} {1} {2} {3}", dbChange.DbChangeId, dbChange.Action, dbChange.TableName, dbChange.TablePK);
                    switch (dbChange.TableName)
                    {
                        case "Strategies":
                            {
                                var id = Convert.ToInt32(dbChange.TablePK.Replace("StrategyId=", ""));
                                Model.Refresh(RefreshMode.StoreWins, Model.Strategies.AsEnumerable());
                            }
                            break;
                        case "StrategyRules":
                            {
                                var id = Convert.ToInt32(dbChange.TablePK.Replace("StrategyRuleId=", ""));
                                Model.Refresh(RefreshMode.StoreWins, Model.StrategyRules.AsEnumerable());
                            }
                            break;
                    }
                    _lastDbChangeId = dbChange.DbChangeId;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("ERROR: " + ex.Message);
        }
        finally
        {
            T1.Start();
        }
    }
}

実行すると、これは出力例です。

All rules: 222
Strategy: name=Blabla2 RulesCount=6

次に、子テーブル (戦略ルール) に行を追加します。

DbChangeId:1713 I StrategyRules StrategyRuleId=811
All rules: 223
Strategy: name=Blabla2 RulesCount=7

最後に、StrategyRules から行を削除します

DbChangeId:1714 D StrategyRules StrategyRuleId=811
All rules: 222
Strategy: name=Blabla2 RulesCount=7

なぜ RulesCount はまだ 7 なのですか? EFに「ナビゲーションプロパティ」を強制的に更新させるにはどうすればよいですか?

ここで何が欠けていますか?

---編集--- Slaumaの答えをカバーする

case "StrategyRules":
{
   var id = Convert.ToInt32(dbChange.TablePK.Replace("StrategyRuleId=", ""));
   if (dbChange.Action == "I")
   {
       //Model.Refresh(RefreshMode.StoreWins, Model.StrategyRules.AsEnumerable());       
   }
   else if (dbChange.Action == "D")
   {
      var deletedRule1 = Model.StrategyRules.SingleOrDefault(sr => sr.Id == id); 
      //the above one is NULL as expected

      var deletedRule2 = _strategy.StrategyRules.SingleOrDefault(sr => sr.Id == id);
      //but this one is not NULL - very strange, because _strategy is in the same context
      //_strategy = Model.Strategies.FirstOrDefault(st => st.StrategyId == 224);
   }  
}
4

1 に答える 1

2

ObjectContext.Refreshメソッドに渡すエンティティのスカラープロパティを、関連するエンティティを参照するキーとともに更新します。メソッドに渡したエンティティがデータベースに存在しなくなった場合、そのエンティティはその間に削除されたためRefresh、アタッチされたエンティティには何の影響も与えず、無視されます。(それは私の側からの推測ですが、なぜあなたが1)例外を受け取らRefreshない(「削除されたためにエンティティを更新できない」など)2)エンティティがまだコンテキストに接続されているように見える理由を説明できませんでした。)

呼び出したために挿入ケースは機能しませんが、次の行でテーブルRefresh全体をメモリにロードしたため機能します。StrategyRules

Model.Refresh(RefreshMode.StoreWins, Model.StrategyRules.AsEnumerable())

Refresh2番目のパラメーターのコレクションを内部的に列挙します。反復を開始することにより、Model.StrategyRulesテーブル全体をロードするだけのクエリがトリガーされます。AsEnumerable()は、LINQ-to-EntitiesからLINQ-to-Objectsへの切り替えにすぎません。つまり、適用するすべてのLINQ演算子はAsEnumerable()、データベースではなくメモリで実行されます。何も適用しないため、AsEnumerable()実際にはクエリに影響はありません。

テーブル全体をロードするため、最近挿入されたものもロードされ、一緒にエンティティStrategyRuleへのキーがロードされます。_strategyObjectContext自動関係修正は、のナビゲーションコレクションとの関係を確立_strategy_strategy.StrategyRules.Countます7。(呼び出しを削除してRefresh呼び出しただけModel.StrategyRules.ToList()でも、結果はそのままになります7。)

現在、これはすべてDeleteの場合には機能しません。データベースからテーブル全体をロードするクエリを実行しStrategyRulesますが、EFは、結果セットに含まれていないエンティティをコンテキストから削除またはデタッチしません。(そして、私が知る限り、そのような自動削除を強制するオプションはありません。)削除されたエンティティは、そのキーが参照しているコンテキストにstrategyあり、カウントは残り7ます。

私が疑問に思っているのは、あなたのセットがプロパティDBChangesで削除されたものを正確に知っていることを利用しない理由です。dbChange.TablePK使用する代わりに、次のRefreshようなものを使用できませんでした。

case "StrategyRules":
{
    switch (dbChange.Action)
    {
        case "D":
        {
            var removedStrategyRule = _strategy.StrategyRules
                .SingleOrDefault(sr => sr.Id == dbChange.TablePK);
            if (removedStrategyRule != null)
                _strategy.StrategyRules.Remove(removedStrategyRule);
        }
        break;

        case ...
    }
}
break;
于 2013-02-28T20:05:04.027 に答える