2015-10-15 8 views
5

ich einen WCF-Server-Anwendung haben Entity Framework läuft 6.Entity Framework 6 - Dataservicecontext Detect hat Änderungen

Meine Client-Anwendung verbraucht OData vom Server über eine Dataservicecontext, und in meinem Client-Code möchte ich in der Lage sein, zu nennen eine HasChanges() - Methode für den Kontext, um festzustellen, ob sich darin Daten geändert haben.

Ich habe versucht, die folgende Erweiterung Methode:

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

Aber es gibt immer false zurück, auch wenn ein Unternehmen es Änderungen hat verfolgt.

Zum Beispiel, da ich eine verfolgte Entität mit dem Namen Customer hat, wird der folgende Code immer vor dem Aufruf SaveChanges() zurückgegeben.

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

Wenn ich, wenn nicht ctx.HasChanges die Kommentar aus() dann Codezeile zurückkehrt, werden die Änderungen erfolgreich so gespeichert Ich bin froh, dass das Unternehmen die Änderung erhalten hat und in der Lage, Speichere es.

Es scheint, dass die Änderung ist durch den Kontext verfolgt zu werden, nur, dass ich diese Tatsache nicht aus meinem Code bestimmen kann.

Kann mir jemand sagen, wie HasChanges auf einem DataServiceContext zu ermitteln?

+0

Vielleicht verstehe ich den Anwendungsfall nicht, aber warum nicht einfach SaveChanges() aufrufen? Wenn es keine Änderungen gibt, wird EF nichts tun. Vermutlich macht EF intern etwas ähnliches und erfinden das Rad einfach neu. – Vlad274

+0

Danke Vlad, ich möchte einen Dialog öffnen, um zu sagen "Bist du sicher, dass du Änderungen speichern willst" bevor du die Daten speicherst. Wenn es keine Änderungen gibt, möchte ich den Dialog nicht öffnen. –

Antwort

2

Weit draußen. Ich lese gerade DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged), die direkt von UpdateObject(entity) mit einem false Argument aufgerufen wird.

Die Logik liest sich wie:

  • Wenn bereits geändert, zurückkehren; (Kurzschluss)
  • Wenn nicht unverändert, werfen Sie, wenn failIfNotUnchanged; (true nur von ChangeState())
  • Else gesetzt Zustand zu geändert. (Keine Datenprüfungen geschehen)

So von den Blicken von ihm, UpdateObject schert sich nicht um/Check den internen Zustand der Einheit, sondern nur die State Enum. Dies führt dazu, dass Aktualisierungen sich ein wenig ungenau anfühlen, wenn keine Änderungen vorgenommen werden.

jedoch, ich denke, Ihr Problem dann im OP zweiten Code-Block ist, überprüfen Sie Ihre Erweiterung HasChangesvorUpdateObject aufrufen. Die Entitäten sind nur verherrlichte POCOs (wie Sie in Ihrer Reference.cs (Versteckte Dateien anzeigen, dann unter der Service-Referenz) lesen können). Sie haben die offensichtlichen Eigenschaften und ein paar On- Operationen, um über Änderungen zu informieren. Was sie tun nicht tun intern ist Spurzustand. Tatsächlich ist der Entität ein EntityDescriptor zugeordnet, der für die Statusverfolgung in EntityTracker.TryGetEntityDescriptor(entity) verantwortlich ist.

Unterm Strich ist Operationen einfach sehr tatsächlich funktionieren, und ich glaube, Sie müssen nur Ihren Code wie

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

Obwohl machen, wie wir jetzt wissen, wird dies immer Bericht Haschanges == true, so dass Sie kann auch den Scheck überspringen.

Aber verzweifeln Sie nicht! Die von Ihrer Servicereferenz bereitgestellten Teilklassen können erweitert werden, um genau das zu erreichen, was Sie möchten. Es ist völlig Boilerplate-Code, also möchten Sie vielleicht ein .tt oder ein anderes Codegen schreiben. Unabhängig davon, nur zwicken dies Ihre Entitäten:

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 
} 

Aber jetzt funktioniert dies sehr gut und ist ähnlich ausdruck zum OP:

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

HTH!

+0

Todd du bist super =) Du hast mich genau auf den Punkt gesetzt, wo ich hinschauen musste. Am Ende habe ich eine partielle Klasse mit der Eigenschaft 'HasChanges()' erstellt. Im Konstruktor habe ich einen Delegaten für PropertyChanged 'PropertyChanged + = Customer_PropertyChanged;' erstellt und einen Eventhandler in meiner partiellen Klasse wie folgt implementiert: 'private void Customer_PropertyChanged (Objektabsender, PropertyChangedEventArgs e) => HasChanges = true;'. Nachdem ich die Entity geladen habe, setze ich 'HasChanges()' auf false. Und jede Eigenschaft, die das PropertyChanged-Ereignis auslöst, markiert die Entität mit Änderungen. Vielen Dank! –

+1

** Zusätzlich: ** Habe gerade ein kleines Problem mit der obigen Lösung gefunden. Das Erstellen einer Eigenschaft 'HasChanges()' in der partiellen Klasse bewirkt, dass die Entität glaubt, dass sie eine Datenbankspalte mit dem Namen ** HasChanges ** hat. Anstatt eine Eigenschaft zu verwenden, verwendete ich eine private Variable mit einer separaten Getter- und Setter-Methode. Prost. –

2

Ist es möglich, dass Sie Ihre Entitäten nicht ordnungsgemäß hinzufügen/bearbeiten? MSDN besagt, dass Sie AddObject, UpdateObject oder DeleteObject verwenden müssen, um die Änderungsverfolgung auf dem Client auszulösen (https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - siehe Parallelität verwalten). Ansonsten sieht Ihre Erweiterungsmethode gut aus.

+0

Danke Todd. Das Objekt in meinem Kontext wird über OData in den Kontext geladen, bevor es auf dem Client aufgerufen wird. Etwas in der Art von '(aus c in ctx.Customers, wobei c.CustomerId == CustomerId c auswählt) .FirstOrDefault();' Die Kontextzusammenführungsoptionen, die bei den Standardwerten belassen wurden, um die Änderungsverfolgung zu aktivieren.Änderungen werden erfolgreich zurück an die Datenbank gesendet, es scheint nur, dass ich die Änderungen nicht selbst sehen kann. –

+0

Müssen Sie anhängen()? –

+0

Ich hatte die 'Attach()' Methode vorher nicht gesehen, danke für den Vorschlag, aber nicht sicher, dass es mir helfen würde, da es so aussieht, als ob es darauf abzielt, getrennte Entitäten in den Kontext zu bringen, damit sie aktualisiert werden können. Meine Entität ist definitiv im Kontext und wird gut verfolgt und aktualisiert. –

0

Damit dies funktioniert, muss die automatische Änderungsverfolgung aktiviert sein. Sie können

ctx.Configuration.AutoDetectChangesEnabled 

in dieser Einstellung finden Alle Entitätsobjekten auch durch den Kontext ctx verfolgt werden müssen. Das bedeutet, dass sie mit einer der Methoden ctx zurückgegeben oder explizit zum Kontext hinzugefügt werden müssen.

Es bedeutet auch, dass sie von der gleichen Instanz von DataServiceContext verfolgt werden müssen. Schaffen Sie irgendwie mehr als einen Kontext?

Das Modell muss ebenfalls korrekt konfiguriert sein. Vielleicht ist Customer.Address1 nicht einer Datenbankspalte zugeordnet. In diesem Fall erkennt EF keine Änderungen an der Spalte.

+0

Danke Jørgen. Configuration.AutoDetectChangesEnabled ist keine Eigenschaft von DataServiceContext. Mein Kontext ist eingerichtet, um Änderungen zu verfolgen, dies funktioniert, wenn ich die Daten modifiziere und SaveChanges aufrufen. Ich verwende nur einmal Instanz des Kontextes auch. Alle Zuordnungen sind gut, wie ich sagte, der Code liest und schreibt Daten gut, ich kann die Änderungen selbst nicht erkennen. –

0

Ich bezweifle, dass der Datenkontext im Client nicht der gleiche ist. So ist die Änderungen immer false.

Sie müssen sicherstellen, dass die Datacontext die gleiche (Instanz) ist, für jede Änderung der Datacontext. Dann ist es sinnvoll, die Änderungen zu erkennen.

Eine andere Möglichkeit, müssen Sie die Änderungen selbst verfolgen. Verwenden Sie einfach die Trackable Entities, um Sie verfolgen die Änderungen von Elementen im Datenkontext.

BTW. I Verwenden Sie den Code 'ctx.ChangeTracker.HasChanges()', um die Änderungen von DataContext zu erkennen.

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

Danke huoxudong125. Der Kontext, den ich verwende, ist dieselbe Instanz, die zum Laden der Entität verwendet wurde. Änderungen werden erfolgreich gespeichert, wenn 'SaveChanges()' aufgerufen wird. Ich erstelle den DataServiceContext auf einem OData-Client und 'ChangeTracker' ist in diesem Szenario kein Member von DataServiceContext. –

Verwandte Themen