EntityFramework不会刷新导航属性

时间:2013-02-28 17:49:07

标签: c# .net entity-framework-4

我有一个简单的关系

enter image description here

我用上面的模型创建了一个简单的应用程序。每次DB更改时,必须更新应用程序中的模型。我可以通过调用GetDBChanges存储过程来获取最新的更改。 (见方法T1Elapsed)

这是app:

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);
   }  
}

1 个答案:

答案 0 :(得分:2)

ObjectContext.Refresh刷新传递给方法along with any keys that refer to related entities的实体的标量属性。如果传入方法的实体在数据库中不再存在,因为它已被删除,同时Refresh对附加的实体不执行任何操作,只是忽略它。 (这是我的猜测,但我无法解释为什么你1)不在Refresh上得到例外(例如“因为它已被删除而无法刷新实体”)和2)实体显然是仍然依附于上下文。)

您的插入案例不起作用,因为您调用了Refresh但是它有效,因为您将整个StrategyRules表加载到此行的内存中:

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

Refresh在内部枚举第二个参数中的集合。通过启动迭代,它会触发仅Model.StrategyRules =加载整个表的查询。 AsEnumerable()只是从LINQ到实体到LINQ到对象的切换,也就是说,在内存中执行AsEnumerable()而不是在数据库上执行后,将应用的每个LINQ运算符。由于您不应用任何内容,AsEnumerable()实际上对您的查询没有任何影响。

因为您加载了整个表格,所以最近插入的StrategyRule也将被加载,并且_strategy实体的密钥将一起加载。 ObjectContext的自动关系修正建立了与_strategy中导航集合的关系,_strategy.StrategyRules.Count7Refresh (您可以移除Model.StrategyRules.ToList()来电,只需致电7,结果仍为StrategyRules。)

现在,所有这些在删除案例中都不起作用。您仍然运行查询以从数据库加载整个strategy表,但EF不会从上下文中删除或分离不在结果集中的实体。 (据我所知,没有强制执行此类自动删除的选项。)删除的实体仍在上下文中,其键引用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; ,你不能使用像:

这样的东西
{{1}}