2013-02-28 4 views
0

У меня есть простое соотношениеEntityFramework не обновляет навигационные свойства

enter image description here

Я создал простое приложение с моделью, как показано выше. Модель в приложении должна обновляться каждый раз, когда изменяется БД. Я могу получить последние изменения, вызвав хранимую процедуру 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 обновить «навигационное свойство»?

Что мне здесь не хватает?

--- EDIT --- для покрытия ответ 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); 
    } 
} 

ответ

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-to-Entities к LINQ-to-Objects, то есть каждый оператор LINQ, который будет применяться после того, как AsEnumerable() будет выполнен в памяти, а не в базе данных. Поскольку вы ничего не применяете, AsEnumerable() фактически не влияет на ваш запрос.

Поскольку вы загружаете всю таблицу, будет загружена недавно вставленная StrategyRule, и вместе будет ключ к объекту _strategy. Автоматическая привязка ObjectContext устанавливает связь с навигационной коллекцией в _strategy и _strategy.StrategyRules.Count будет 7. (Вы можете удалить звонок Refresh и просто позвонить Model.StrategyRules.ToList(), и результат все равно будет 7.)

Теперь все это не работает в случае удаления.Вы все еще запускаете запрос для загрузки всей таблицы StrategyRules из базы данных, но EF не удаляет или не удаляет сущности из контекста, которые больше не находятся в результирующем наборе. (И насколько я знаю, нет возможности принудительно удалить такое автоматическое удаление.) Удаленный объект по-прежнему находится в контексте с его ключом, ссылающимся на strategy, и счет будет оставаться 7.

Что мне интересно, почему вы не используете рычаг, чтобы ваш набор DBChanges appentyenty точно знал, что было удалено в 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; 
+0

Прежде всего большое спасибо за объяснение. Это имеет смысл, и вы правы, мне не нужно вызывать Refresh (получилось такое же поведение без него), но как вы это объясните: Model.StrategyRules.SingleOrDefault (sr => sr.Id == myId) имеет значение null после того, как DB delete, но _strategy.StrategyRules.SingleOrDefault (sr => sr.Id == myId) в не null (он по-прежнему содержит правило «удалено»). В основном _strategy.StrategyRules.Count = 7, но Model.StrategyRules.ToList() возвращает 6 элементов. Так ли это удаление из контекста или нет? Я добавлю больше кода утром, чтобы сделать его более ясным. – Novitzky

+0

Во-вторых, приятное предложение с корпусом «D» с коммутационным футляром, но у меня нет объекта _strategy. (это только пример). Также я не могу делать Model.Strategies.SingleOrDefault (s => s.Id == ???); так как не знаю StrategyId (только StrategyRuleId). Я думал о создании объекта стратегии, вызывая Model.StrategyRules.SingleOrDefault (sr => sr.Id == myID) .Strategy, но он не работает, поскольку StrategyRule имеет значение null (уже удалено из контекста). – Novitzky

+0

@Novitzky: К вашему первому комментарию. Исключенное правило ** не ** удаляется из контекста. 'Model.StrategyRules.ToList()' запрашивает базу данных, а не записи контекста. Таким образом, он возвращает все, кроме уже удаленного правила -> count = 6. '_strategy.StrategyRules' является графиком объекта в памяти, и у него все еще есть удаленное правило -> count = 7. – Slauma

Смежные вопросы