2015-07-09 3 views
9

Ich erstelle die Standard-Concurrency-Strategie, die ich in meiner Anwendung verwenden werde.Optimistische Nebenläufigkeit: IsConcurrencyToken und RowVersion

Ich entschied mich für eine optimistische Strategie.

Alle meine Entitäten sind als Table per Type (TPT) zugeordnet (Vererbung). Bald erfuhr ich, dass es ein Problem ist, wenn Spalten vom Typ RowVersion mit Vererbung auf Entity Framework:

Product 

Id INT IDENTITY PRIMARY KEY 
RowVersion ROWVERSION 

Car (inherits Product records) 

Color TYNIINT NOT NULL, 
AnotherProperty....    

Wenn ich die RowVersion Spalte von Product Tabelle einen Datensatz der Car Tabelle aktualisieren, werden nicht aktualisiert.

Ich plane, eine Spalte des Typs datetime2 (7) in Product zu verwenden und manuell zu aktualisieren, wenn alle Datensätze der Tabellen, die diese Tabelle erben, geändert werden.

Ich denke, ich erfinde das Rad neu.

Gibt es eine andere Möglichkeit, die optimistische Concurrency-Strategie mit ROWVERSION zu verwenden, wenn Table per Type (TPT) in Entity Framework verwendet?

bearbeiten

Meine Mapping:

class Product 
{ 
    int Id { get; set; } 
    string Name { get; set; } 
    byte[] RowVersion { get; set; } 
} 

class Car : Product 
{ 
    int Color { get; set; } 
} 

CodeFirst Konventionen.

Nur die RowVersion Eigenschaft auf Product Unternehmen hat individuelle Definitionen:

modelBuilder.Entity<Product>() 
    .Property(t => t.RowVersion) 
    .IsConcurrencyToken(); 
+0

Jeder? :/Plx. –

+0

* Produkttabelle wird nicht aktualisiert *. Es ist tatsächlich, mit einem Dummy-Update, das die Zeilenversion erhöht (EF 6.1.3). –

+0

Hallo @GertArnold. Bei meinen Tests (EF6.1.3) wird die Zeilenversion nicht erhöht. –

Antwort

17

Sowohl in EF6 und EF-Kern, wenn sie mit SQL Server arbeiten, müssen Sie diese Zuordnung verwenden:

modelBuilder.Entity<Product>() 
.Property(t => t.RowVersion) 
.IsRowVersion(); // Not: IsConcurrencyToken 

IsConcurrencyToken konfiguriert eine Eigenschaft als Concurrency-Token, aber (wenn sie für eine byte[]-Eigenschaft verwendet wird)

  • der Datentyp varbinary(max)
  • sein Wert ist immer null, wenn Sie es
  • sein Wert nicht initialisiert wird nicht automatisch erhöht, wenn ein Datensatz aktualisiert wird.

IsRowVersion auf der anderen Seite,

  • hat Datentyp rowversion (in SQL Server oder timestamp in früheren Versionen), so
  • sein Wert nie null ist, und
  • sein Der Wert wird immer automatisch erhöht, wenn ein Datensatz aktualisiert wird.
  • und die Eigenschaft automatisch als optimistisches Concurrency-Token konfiguriert wird.

Nun, wenn Sie aktualisieren ein Car Sie zwei Update-Anweisungen sehen werden:

DECLARE @p int 
UPDATE [dbo].[Product] 
SET @p = 0 
WHERE (([Id] = @0) AND ([Rowversion] = @1)) 
SELECT [Rowversion] 
FROM [dbo].[Product] 
WHERE @@ROWCOUNT > 0 AND [Id] = @0 

UPDATE [dbo].[Car] 
SET ... 

Die erste Anweisung aktualisiert nicht alles, aber es erhöht die rowversion, und es wird eine Gleichzeitigkeit Ausnahme auslösen, wenn Die Zeilenversion wurde dazwischen geändert.

Das [System.ComponentModel.DataAnnotations.Schema.Timestamp] Attribut ist die Daten Anmerkungen Äquivalent IsRowVersion():

[Timestamp] 
public byte[] RowVersion { get; set; } 
+0

Welche Version von SQL Server verwenden Sie? Laut der Dokumentation sollte IsConcurrencyToken() für die Eigenschaft byte [] dem in SQL Server 2005 hinzugefügten Datentyp rowversion zugeordnet werden. –

+0

@ SørenBoisen Dies war SQL2012 IIRC. Ich habe es zuerst Code-getestet und berichtet, was ich bekommen habe. Wenn die Dokumentation etwas anderes sagt, ist es entweder falsch oder es setzt andere Schritte voraus, die ich nicht gemacht habe. Auf welche Dokumentation beziehen Sie sich? –

+0

Okay, vielleicht nicht genau Dokumentation, aber trotzdem ein Tutorial auf der offiziellen Seite :-) Hier: http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using -mvc-4/handling-concurrency-mit-der-entity-framework-in-einem-asp-net-mvc-application –

3

Nach ein bisschen Untersuchung ich in der Lage war IsConcurrencyToken auf einem Byte [8] Spalte namens RowVersion in Entity Framework verwenden 6.

Da wir den gleichen Datentyp in DB2 verwenden möchten (der in der Datenbank selbst keine Zeilenversion hat), können wir die Option IsRowVersion() nicht verwenden!

Ich habe ein wenig weiter untersucht, wie man mit IsConcurrencyToken arbeitet.

Ich habe die folgende, eine Lösung zu erreichen, die zu funktionieren scheint:

My Model:

public interface IConcurrencyEnabled 
{ 
    byte[] RowVersion { get; set; } 
} 

    public class Product : AuditableEntity<Guid>,IProduct,IConcurrencyEnabled 
{ 
    public string Name 
    { 
     get; set; 
    } 
    public string Description 
    { 
     get; set; 
    } 
    private byte[] _rowVersion = new byte[8]; 
    public byte[] RowVersion 
    { 
     get 
     { 
      return _rowVersion; 
     } 

     set 
     { 
      System.Array.Copy(value, _rowVersion, 8); 
     } 
    } 
} 

IConcurrencyEnabled verwendet Entitäten zu identifizieren, die eine rowversion haben, die eine besondere Behandlung benötigt.

habe ich fließend API Modelbuilder zu konfigurieren:

public class ProductConfiguration : EntityTypeConfiguration<Product> 
{ 
    public ProductConfiguration() 
    { 
     Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
     Property(e => e.RowVersion).IsFixedLength().HasMaxLength(8).IsConcurrencyToken(); 
    } 
} 

Und schließlich Ich habe eine Methode, um meine abgeleitet DbContext Klasse das Feld zu aktualisieren, bevor die base.SaveChanges aufgerufen:

 public void OnBeforeSaveChanges(DbContext dbContext) 
    { 
     foreach (var dbEntityEntry in dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified)) 
     { 
      IConcurrencyEnabled entity = dbEntityEntry.Entity as IConcurrencyEnabled; 
      if (entity != null) 
      { 

       if (dbEntityEntry.State == EntityState.Added) 
       { 
        var rowversion = dbEntityEntry.Property("RowVersion"); 
        rowversion.CurrentValue = BitConverter.GetBytes((Int64)1); 
       } 
       else if (dbEntityEntry.State == EntityState.Modified) 
       { 
        var valueBefore = new byte[8]; 
        System.Array.Copy(dbEntityEntry.OriginalValues.GetValue<byte[]>("RowVersion"), valueBefore, 8); 

        var value = BitConverter.ToInt64(entity.RowVersion, 0); 
        if (value == Int64.MaxValue) 
         value = 1; 
        else value++; 

        var rowversion = dbEntityEntry.Property("RowVersion"); 
        rowversion.CurrentValue = BitConverter.GetBytes((Int64)value); 
        rowversion.OriginalValue = valueBefore;//This is the magic line!! 

       } 

      } 
     } 
    } 

Das Problem, auf das die meisten Leute stoßen, ist, dass wir nach dem Setzen des Wertes der Entity immer eine UpdateDBConcurrencyException erhalten, weil sich der OriginalValue geändert hat ... auch wenn es nicht passiert ist!

Der Grund ist, dass für ein Byte [] sowohl Original als auch CurrentValue sich ändern, wenn Sie den CurrentValue allein einstellen (seltsames und unerwartetes Verhalten).

Also setze ich den OriginalValue wieder auf den ursprünglichen Wert, bevor ich die Zeilenversion aktualisiere ... Auch ich kopiere das Array, um die Referenzierung der gleichen Byte-Array zu vermeiden!

Achtung: Hier verwende ich einen inkrementellen Ansatz, um die Zeilenversion zu ändern, Sie können Ihre eigene Strategie verwenden, um diesen Wert einzugeben. (Zufällig oder zeitbasiert)

0

Das Problem ist nicht, wie Sie Setup sind. Was passiert, ist, dass der OriginalValue Ihres RowVersion Eintrags auf den neuen Wert gesetzt wird, sobald Sie ihn aus dem Kontext herausziehen.

var carInstance = dbContext.Cars.First(); 
carInstance.RowVersion = carDTO.RowVerison; 
carInstance.Color = carDTO.Color ; 


var entry = dbContext.Entry(carInstance); //Can also come from ChangeTrack in override of SaveChanges (to do it automatically)  

entry.Property(e => e.RowVersion) 
        .OriginalValue = entry.Entity.RowVersion;