2015-10-15 3 views
5

У меня есть приложение с WCF сервера под управлением Entity Framework 6.Entity Framework 6 - DataServiceContext Обнаружение Имеет изменения

Мой клиент приложение потребляет OData с сервера через DataServiceContext, и в моем клиентском коде я хочу, чтобы иметь возможность позвонить метод HasChanges() в контексте, чтобы увидеть, изменились ли какие-либо данные в нем.

Я попытался, используя следующий метод расширения:

public static bool HasChanges(this DataServiceContext ctx) 
    { 
     // Return true if any Entities or links have changes 
     return ctx.Entities.Any(ed => ed.State != EntityStates.Unchanged) || ctx.Links.Any(ld => ld.State != EntityStates.Unchanged); 
    } 

Но он всегда возвращает ложь, даже если объект его отслеживания действительно есть изменения.

Например, если у меня есть отслеживаемый объект с именем Customer, следующий код всегда возвращается перед вызовом SaveChanges().

Customer.Address1 = "Fred" 
    if not ctx.HasChanges() then return 
    ctx.UpdateObject(Customer) 
    ctx.SaveChanges() 

Если я закомментируйте если не ctx.HasChanges() затем возвращают строку кода, изменения будут сохранены успешно, так что я рад, что компания получила изменения и в состоянии сохрани это.

Кажется, что изменение является получения отслеживается контекстом, только что я не могу определить, что факт из моего кода.

Может ли кто-нибудь сказать мне, как определить HasChanges в DataServiceContext?

+0

Возможно, я не понимаю вариант использования, но почему бы просто не вызвать SaveChanges()? Если изменений нет, EF ничего не сделает. Предположительно, EF делает что-то подобное внутри, и вы просто изобретаете колесо. – Vlad274

+0

Спасибо Владу, я хочу открыть диалог, чтобы сказать «Вы действительно хотите сохранить изменения», прежде чем фактически сохранить данные. Если изменений нет, я не хочу всплывать диалог. –

ответ

2

Далеко. Я просто прочитал DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged), который называется непосредственно из UpdateObject(entity) с аргументом false.

Логика читается как:

  • Если уже модифицирована, возвращение; (короткое замыкание)
  • Если не изменилось, бросьте, если failIfNotUnchanged; (true only from ChangeState())
  • Else set state to modified. (Без проверки данных не произошло)

Так, судя по всему, UpdateObject не заботится о/проверить внутреннее состояние объекта, только State ENUM. Это делает обновления немного неточными, когда изменений нет.

Однако, я думаю, ваша проблема, то в OP 2 блока кода, вы проверяете расширение HasChangesперед тем вызова UpdateObject. Субъекты являются только прославленными POCOs (как вы можете прочитать в своем Reference.cs (Показать скрытые файлы, затем по ссылке службы)). У них есть очевидные свойства и несколько операций On- для уведомления об изменении. Что они делают не do внутренне это состояние трека. Фактически, существует EntityDescriptor, связанный с объектом, который отвечает за отслеживание состояния в EntityTracker.TryGetEntityDescriptor(entity).

Нижняя линия операции на самом деле работает очень просто, и я думаю, что вам просто нужно сделать ваш код

Customer.Address1 = "Fred"; 
ctx.UpdateObject(Customer); 
if (!ctx.HasChanges()) return; 
ctx.SaveChanges(); 

Хотя, как мы теперь знаем, это будет всегда отчет HasChanges == верно, так что вы может также пропустить чек.

Но не отчаивайтесь! Частичные классы, предоставленные вашей службой, могут быть расширены, чтобы делать именно то, что вы хотите. Это полностью шаблонный код, поэтому вы можете написать .tt или какой-нибудь другой код. Несмотря на это, просто настроить это для ваших сущностей:

namespace ODataClient.ServiceReference1 // Match the namespace of the Reference.cs partial class 
{ 
    public partial class Books // Your entity 
    { 
     public bool HasChanges { get; set; } = false; // Your new property! 

     partial void OnIdChanging(int value) // Boilerplate 
     { 
      if (Id.Equals(value)) return; 
      HasChanges = true; 
     } 

     partial void OnBookNameChanging(string value) // Boilerplate 
     { 
      if (BookName == null || BookName.Equals(value)) return; 
      HasChanges = true; 
     } 
     // etc, ad nauseam 
    } 
    // etc, ad nauseam 
} 

Но теперь это прекрасно работает и так же выразительны к OP:

var book = context.Books.Where(x => x.Id == 2).SingleOrDefault(); 
book.BookName = "W00t!"; 
Console.WriteLine(book.HasChanges); 

HTH!

+0

Тодд, ты потрясающий =) Ты меня обманул, точно там, где мне нужно было посмотреть. В результате я создал частичный класс с свойством 'HasChanges()'. В конструкторе я создал делегат для PropertyChanged «PropertyChanged + = Customer_PropertyChanged;» и реализовал обработчик событий в моем частичном классе следующим образом: 'private void Customer_PropertyChanged (object sender, PropertyChangedEventArgs e) => HasChanges = true;'. После того, как я загрузил объект, я установил 'HasChanges()' в false. И любое свойство, которое запускает событие PropertyChanged, помещает объект как имеющий изменения. Благодаря! –

+1

** Дополнительно: ** Только что нашли небольшую проблему с вышеупомянутым решением. Создание свойства 'HasChanges()' в частичном классе заставляет сущность полагать, что у нее есть столбец базы данных с именем ** HasChanges **. Вместо использования свойства я закончил использование частной переменной с отдельным методом getter и setter. Приветствия. –

2

Возможно ли, что вы не добавляете/не редактируете свои объекты должным образом? MSDN заявляет, что вы должны использовать AddObject, UpdateObject или DeleteObject, чтобы отслеживать отслеживание изменений для клиента (https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - см. Управление параллелизмом). В противном случае ваш метод расширения выглядит хорошо.

+0

Спасибо Тодд. Объект в моем контексте загружается в контекст через OData, прежде чем он будет доступен на клиенте. Что-то в строках '(от c в ctx.Customers, где c.CustomerId == CustomerId выбирает c) .FirstOrDefault();' Параметры слияния контекста, оставшиеся по умолчанию, чтобы включить отслеживание изменений.Изменения успешно отправляются обратно в базу данных, кажется, что я сам не вижу изменений. –

+0

Нужно ли прикрепить()? –

+0

Я раньше не видел метод 'Attach()', спасибо за это предложение, но не уверен, что это поможет мне, поскольку похоже, что он нацелен на то, чтобы отключить сущности в контексте, чтобы их можно было обновить, а не вставлять. Моя сущность определенно находится в контексте и отслеживается и обновляется отлично. –

0

Для этого необходимо включить автоматическое отслеживание изменений. Вы можете найти этот параметр в

ctx.Configuration.AutoDetectChangesEnabled 

Все объекты сущности должны быть также отслеживаются контекстом ctx. Это означает, что они должны быть возвращены одним из методов ctx или явно добавлены в контекст.

Это также означает, что они должны отслеживаться одним и тем же экземпляром DataServiceContext. Вы каким-то образом создали несколько контекстов?

Модель также должна быть правильно настроена. Возможно, Customer.Address1 не сопоставляется с столбцом базы данных. В этом случае EF не обнаружит изменений в столбце.

+0

Спасибо, Йорген. Configuration.AutoDetectChangesEnabled не является свойством DataServiceContext. Мой контекст - это настройка отслеживания изменений, это работает, когда я изменяю данные и вызываю SaveChanges. Я использую только один экземпляр контекста. Все сопоставления хороши, поскольку я сказал, что код читает и записывает данные в порядке, я просто не могу сам обнаружить изменения. –

0

Я сомневаюсь, что datacontext в клиенте не является тем же самым. Так что изменения всегда равны false.

Вы должны быть уверены, что Datacontext - это тот же самый экземпляр для каждого изменения Datacontext. Затем для обнаружения изменений имеет смысл.

Другой способ, вы должны отследить изменения самостоятельно. Просто используйте Trackable Entities, чтобы помочь вам отслеживать изменения объектов в datacontext.

BTW. Я использую код «ctx.ChangeTracker.HasChanges()» для обнаружения изменений DataContext.

public bool IsContextDirty(DataServiceContext ctx) 
    { 
#if DEBUG 
     var changed = ctx.ChangeTracker.Entries().Where(t => t.State != EntityState.Unchanged).ToList(); 
     changed.ForEach(
      (t) => Debug.WriteLine("entity Type:{0}", t.Entity.GetType())); 
#endif 
     return ctx != null && ctx.ChangeTracker.HasChanges(); 
    } 
+0

Спасибо huoxudong125. Контекст, который я использую, - это тот же самый экземпляр, который использовался для загрузки объекта, изменения успешно сохраняются при вызове 'SaveChanges()'. Я создаю DataServiceContext на клиенте OData, а 'ChangeTracker' не является членом DataServiceContext в этом сценарии. –

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