2010-12-09 11 views
20

UPDATE (2010-12-21): Diese Frage wurde aufgrund von Tests, die ich gemacht habe, komplett neu geschrieben. Auch das war früher eine POCO-spezifische Frage, aber es stellt sich heraus, dass meine Frage nicht unbedingt POCO-spezifisch ist.OptimisticConcurrencyException funktioniert in bestimmten Situationen nicht im Entitätsframework

Ich benutze Entity Framework und ich habe eine Timestamp-Spalte in meiner Datenbanktabelle, die verwendet werden sollte, um Änderungen für optimistische Parallelität zu verfolgen. Ich habe den Gleichzeitigkeitsmodus für diese Eigenschaft im Entity Designer auf "Fixed" gesetzt und bekomme inkonsistente Ergebnisse. Im Folgenden finden Sie einige vereinfachte Szenarien, die zeigen, dass die Parallelitätsprüfung in einem Szenario, jedoch nicht in einem anderen Szenario funktioniert.

Erfolgreich wirft OptimisticConcurrencyException:

Wenn ich eine getrennte Einheit anschließen, dann wird Savechanges eine OptimisticConcurrencyException werfen, wenn es einen Konflikt Zeitstempel:

[HttpPost] 
    public ActionResult Index(Person person) { 
     _context.People.Attach(person); 
     var state = _context.ObjectStateManager.GetObjectStateEntry(person); 
     state.ChangeState(System.Data.EntityState.Modified); 
     _context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 

Nicht werfen OptimisticConcurrencyException:

Auf der anderen Seite, wenn ich eine neue Kopie meiner Entität aus dem d ATENBANK und ich auf einigen Gebieten eine teilweise Aktualisierung tun, und dann Savechanges() aufrufen, dann, obwohl es ein Zeitstempel Konflikt ist, verstehe ich nicht ein OptimisticConcurrencyException:

[HttpPost] 
    public ActionResult Index(Person person) { 
     var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
     currentPerson.Name = person.Name; 

     // currentPerson.VerColm == [0,0,0,0,0,0,15,167] 
     // person.VerColm == [0,0,0,0,0,0,15,166] 
     currentPerson.VerColm = person.VerColm; 

     // in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166] 
     // in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167] 
     _context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 

Basierend auf SQL Profiler, es sieht aus wie Entity Framework ignoriert die neue VerColm (die Timestamp-Eigenschaft) und verwendet stattdessen die ursprünglich geladene VerColm. Aus diesem Grund wird niemals eine OptimisticConcurrencyException ausgelöst.


UPDATE: Hinzufügen von zusätzlichen Informationen pro Jans Anfrage:

Bitte beachte, dass ich auch Kommentare zu den obigen Code hinzugefügt übereinstimmen mit dem, was ich in meinem Controller-Aktion zu sehen, während er durch dieses Beispiel arbeiten.

Dies ist der Wert des VerColm in der Datenbank vor der Aktualisierung: 0x0000000000000FA7

Hier ist, was SQL Profiler zeigt, wenn das Update durchführen:

exec sp_executesql N'update [dbo].[People] 
set [Name] = @0 
where (([Id] = @1) and ([VerColm] = @2)) 
select [VerColm] 
from [dbo].[People] 
where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(50),@1 int,@2 binary(8)',@0=N'hello',@1=1,@2=0x0000000000000FA7 

Beachten Sie, dass @ 2 sollte 0x0000000000000FA6 gewesen sein , aber es ist 0x0000000000000FA7

Hier ist die VerColm in meiner Datenbank nach dem Update: 0x0000000000000FA8


Weiß jemand, wie ich dieses Problem umgehen kann? Ich möchte, dass Entity Framework eine Ausnahme auslöst, wenn ich eine vorhandene Entität aktualisiere und es einen Zeitstempelkonflikt gibt.

Dank

+0

Bitte geben Sie den Code ein, der die Speicherung durchführt. –

+0

Ich kann das nicht reproduzieren. Ich erhalte eine OptimisticConcurrencyException, wenn ich versuche, die geladene und geänderte Entität mit einem Timestamp-Konflikt zu speichern. Sind Sie sicher, dass Sie einen Timestamp-Konflikt haben? Können Sie bitte Ihre profilierte SQL-Abfrage posten? – Jan

+0

Hallo Jan, ich habe oben auf Ihre Anfrage zusätzliche Informationen hinzugefügt. –

Antwort

24

Erklärung

Der Grund, warum Sie nicht die erwartete OptimisticConcurrencyException auf dem zweiten Codebeispiel bekommen ist aufgrund der Gleichzeitigkeit EF prüft Weise:

Wenn Sie Objekte abrufen, indem Sie Beim Abfragen Ihrer Datenbank speichert EF den Wert aller mit ConcurrencyMode.Fixed markierten Eigenschaften zum Zeitpunkt der Abfrage als ursprüngliche, nicht geänderte Werte.

Dann ändern Sie einige Eigenschaften (einschließlich der Fixed markierten) und rufen Sie SaveChanges() auf Ihrem DataContext.

EF prüft auf gleichzeitige Aktualisierungen, indem die aktuellen Werte aller gekennzeichneten db-Spalten Fixed mit den ursprünglichen, unveränderten Werten der markierten Eigenschaften Fixed verglichen werden. Der Schlüsselpunkt hier ist, dass EF die Aktualisierung Ihrer Zeitstempeleigenschaft als normal Dateneigenschaftenaktualisierung behandelt. Das Verhalten, das Sie sehen, ist von Entwurf.

Lösung/Abhilfe

Um Abhilfe Sie haben folgende Möglichkeiten:

  1. Verwenden Sie Ihre erste Ansatz: Schalten Sie den db für Ihre Einheit nicht erneut abfragen, sondern die neu Entität Ihrem Kontext anhängen .

  2. Gefälschte Ihre Timestamp-Wert den aktuellen db Wert sein, so dass die EF Parallelität Überprüfung Ihrer gelieferten Wert wie dargestellt verwendet unten (siehe auch this answer auf eine ähnliche Frage):

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
    currentPerson.VerColm = person.VerColm; // set timestamp value 
    var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson); 
    ose.AcceptChanges();  // pretend object is unchanged 
    currentPerson.Name = person.Name; // assign other data properties 
    _context.SaveChanges(); 
    
  3. Sie können prüfen, für Concurrency Sie sich von Ihrem Zeitstempel Wert auf den erneut abgefragt Zeitmarkenwert zu vergleichen:

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
    if (currentPerson.VerColm != person.VerColm) 
    { 
        throw new OptimisticConcurrencyException(); 
    } 
    currentPerson.Name = person.Name; // assign other data properties 
    _context.SaveChanges(); 
    
+0

Vielen Dank für die ausführliche Erklärung. –

5

Hier ist eine weitere ap proach, die ein bisschen mehr generisch und passt in die Datenschicht ist:

// if any timestamps have changed, throw concurrency exception 
var changed = this.ChangeTracker.Entries<>() 
    .Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
     x.OriginalValues.GetValue<byte[]>("Timestamp"))); 
if (changed) throw new OptimisticConcurrencyException(); 
this.SaveChanges(); 

Es prüft nur wenn die Timestamp zu sehen, hat sich verändert und Gleichzeitigkeit Ausnahme auslöst.

+0

Ich habe das versucht und es scheint gut zu funktionieren.Allerdings möchte ich eine ähnliche DbUpdateConcurrencyException werfen wie EF tut, einschließlich der Entities-Auflistung, jedoch gibt es keinen Setter für diese Sammlung. Ich habe versucht, mit ILSpy in die Quelle zu schauen, aber das hat nichts Nützliches ergeben, ohne viel Code durchwaten zu müssen (für den ich keine Zeit habe). Wer weiß, wie das geht? – Anttu

+0

Hinweis: Der Beispielcode funktioniert nicht für gelöschte Entitäten. Sollte auf "... Any" (x => x.State == EntityState.Modified &&! X.CurrentValues.GetValue <....) aktualisiert werden. Andernfalls wird eine InvalidOperationException ausgelöst, wenn Sie versuchen, eine Löschung vorzunehmen. – Anttu

+0

Ich habe nachbearbeitet diese Idee passt in meine DAL, funktioniert super und ist einfach. Danke. – RitchieD

2

Wenn es zuerst EF Code ist, dann verwenden Sie Code ähnlich wie unten. Dies wird den ursprünglichen TimeStamp, der von db in den von der UI geladen wird, ändern und sicherstellen, dass OptimisticConcurrencyEception auftritt.

db.Entry(request).OriginalValues["Timestamp"] = TimeStamp; 
+0

Das ist, was ich gesucht habe, danke. –

Verwandte Themen