2015-10-15 3 views
5

J'ai une application de serveur WCF en cours d'exécution Entity Framework 6.Entity Framework 6 - DataServiceContext Detect A Les changements

Mon application client consomme OData à partir du serveur via un DataServiceContext, et dans mon code client Je veux être en mesure d'appeler une méthode HasChanges() sur le contexte pour voir si des données ont changé.

J'ai essayé d'utiliser la méthode d'extension suivante:

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

Mais il revient toujours faux, même si une entité, il est suivi n'avoir des changements. Par exemple, étant donné que j'ai une entité suivie nommée Customer, le code suivant retourne toujours avant d'appeler SaveChanges().

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

Si je commente le sinon ctx.HasChanges() puis retour ligne de code, les modifications sont enregistrées avec succès et je suis heureux que l'entité a reçu le changement et est en mesure de sauvegarde le.

Il semble que le changement est se suivi par le contexte, juste que je ne peux pas déterminer ce fait de mon code.

Quelqu'un peut-il me dire comment déterminer HasChanges sur un DataServiceContext?

+0

Peut-être que je ne comprends pas le cas d'utilisation, mais pourquoi ne pas simplement appeler SaveChanges()? S'il n'y a pas de modifications, EF ne fera rien. Vraisemblablement, EF fait quelque chose de similaire à l'interne, et vous ne faites que réinventer la roue. – Vlad274

+0

Merci Vlad, je veux faire un dialogue contextuel pour dire "Etes-vous sûr de vouloir enregistrer les modifications" avant de sauvegarder les données. S'il n'y a pas de changements, je ne veux pas faire apparaître le dialogue. –

Répondre

2

Extrémité. Je viens de lire DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged), qui est appelé directement à partir de UpdateObject(entity) avec un argument false.

La logique se lit comme:

  • Si déjà modifié, le retour; (court-circuit)
  • Si non inchangé, lancer si failIfNotUnchanged; (true uniquement à partir de ChangeState())
  • Sinon, définissez l'état sur modifié. (Pas de chèques de données ont eu lieu)

Alors les regards de celui-ci, UpdateObject ne se soucie pas de/vérifier l'état interne de l'entité, juste le State ENUM. Cela rend les mises à jour un peu inexactes en l'absence de modifications.

Cependant, je pense que votre problème est alors dans le PO 2ème bloc de code, vous vérifiez votre extension HasChangesavant d'appelerUpdateObject. Les entités sont seulement des POCOs glorifiés (comme vous pouvez le lire dans votre Reference.cs (Afficher les fichiers cachés, puis sous la référence du service)). Ils ont les propriétés évidentes et quelques opérations On- pour notifier le changement. Qu'est-ce qu'ils font pas faire en interne est l'état de la piste. En fait, il y a un EntityDescriptor associé à l'entité, qui est responsable du suivi de l'état au EntityTracker.TryGetEntityDescriptor(entity).

Bottom line est opérations fonctionnent en fait très simplement, et je pense que vous avez juste besoin de faire votre code comme

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

Bien que nous savons maintenant, cela toujours HasChanges rapport == true, vous peut aussi sauter la vérification.

Mais ne désespérez pas! Les classes partielles fournies par votre référence de service peuvent être étendues pour faire exactement ce que vous voulez. Il s'agit d'un code totalement nouveau, donc vous pouvez écrire un .tt ou un autre codegen. Quoiqu'il en soit, tout ce tweak à vos entités:

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 
} 

Mais maintenant, cela fonctionne très bien et est tout aussi expressif à l'OP:

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

HTH!

+0

Todd vous êtes génial =) Vous m'avez mis au point exactement là où je devais regarder. Ce que j'ai fini par créer était une classe partielle avec la propriété 'HasChanges()'. Dans le constructeur j'ai créé un délégué pour PropertyChanged 'PropertyChanged + = Customer_PropertyChanged;' et ai implémenté un gestionnaire d'événement dans ma classe partielle comme suit: 'private void Customer_PropertyChanged (expéditeur d'objet, PropertyChangedEventArgs e) => HasChanges = true;'. Après avoir chargé l'entité, j'ai défini 'HasChanges()' sur false. Et toute propriété qui déclenche l'événement PropertyChanged marque l'entité comme ayant des modifications. Merci! –

+1

** Supplémentaire: ** Nous venons de trouver un problème mineur avec la solution ci-dessus. La création d'une propriété 'HasChanges()' dans la classe partielle amène l'entité à croire qu'elle possède une colonne de base de données nommée ** HasChanges **. Au lieu d'utiliser une propriété, j'ai fini par utiliser une variable privée avec une méthode getter et setter séparée. À votre santé. –

2

Est-il possible que vous n'ajoutiez/éditiez pas correctement vos entités? MSDN indique que vous devez utiliser AddObject, UpdateObject ou DeleteObject pour activer le suivi des modifications sur le client (https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - voir Gestion de la concurrence). Sinon, votre méthode d'extension semble bonne.

+0

Merci Todd. L'objet dans mon contexte est chargé dans le contexte via OData avant d'être accédé sur le client. Quelque chose dans le style de '(de c dans ctx.Customers où c.CustomerId == CustomerId sélectionnez c) .FirstOrDefault();' Les options de fusion de contexte laissées à défaut qui est d'activer le suivi des modifications.Les modifications sont renvoyées à la base de données, il semble que je ne puisse pas voir les changements moi-même. –

+0

Avez-vous besoin de joindre()? –

+0

Je n'avais pas vu la méthode 'Attach()' auparavant, merci pour la suggestion, mais je ne suis pas sûr que cela m'aiderait car il semble que les entités déconnectées soient mises dans le contexte afin qu'elles puissent être mises à jour au lieu d'être insérées. Mon entité est définitivement dans le contexte et est suivie et mise à jour bien. –

0

Pour que cela fonctionne, le suivi automatique des modifications doit être activé. Vous pouvez trouver ce paramètre dans

ctx.Configuration.AutoDetectChangesEnabled 

Tous les objets d'entité doivent également être suivis par le contexte ctx. Cela signifie qu'ils doivent être renvoyés par l'une des méthodes de ctx ou explicitement ajoutés au contexte. Cela signifie également qu'ils doivent être suivis par la même instance que DataServiceContext. Êtes-vous en train de créer plus d'un contexte?

Le modèle doit également être configuré correctement. Peut-être Customer.Address1 n'est pas mappé à une colonne de base de données. Dans ce cas, EF ne détectera pas les modifications apportées à la colonne.

+0

Merci Jørgen. Configuration.AutoDetectChangesEnabled n'est pas une propriété de DataServiceContext. Mon contexte est configuré pour effectuer le suivi des modifications, cela fonctionne lorsque je modifie les données et que j'appelle SaveChanges. Je n'utilise qu'une seule fois le contexte. Tous les mappings sont bons, comme je l'ai dit, le code lit et écrit correctement les données, je ne peux pas détecter les changements moi-même. –

0

Je doute que le datacontext dans le client n'est pas le même.so les changements est toujours false.

Vous devez être sûr que le Datacontext est le même (instance), pour tous les changements du Datacontext. Ensuite, pour détecter les changements est significatif.

Une autre façon, vous devez suivre les changements par vous-même. Simplement en utilisant le Trackable Entities pour vous aider à suivre les changements d'entités dans le contexte de données.

BTW. I Utilisez le code 'ctx.ChangeTracker.HasChanges()' pour détecter les changements de 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

Merci huoxudong125. Le contexte que j'utilise est la même instance que celle utilisée pour charger l'entité, les modifications sont enregistrées avec succès lorsque 'SaveChanges()' est appelé. Je crée le DataServiceContext sur un client OData et 'ChangeTracker' n'est pas membre de DataServiceContext dans ce scénario. –