Optimistische Parallelität: IsConcurrencyToken und RowVersion

Ich schaffe die Standard-Parallelität Strategie, die ich in meiner Anwendung verwenden werde.

Ich habe mich für eine optimistische Strategie entschieden.

Alle meine Entitäten werden als Table per Type (TPT) (mit inheritance) abgebildet. Ich habe bald gelernt, dass es ein Problem bei der Verwendung von Spalten von Typ RowVersion mit inheritance auf Entity Framework:

 Product Id INT IDENTITY PRIMARY KEY RowVersion ROWVERSION Car (inherits Product records) Color TYNIINT NOT NULL, AnotherProperty.... 

Wenn ich einen datasatz der Car Tabelle aktualisiere, wird die RowVersion-Spalte aus der Product nicht aktualisiert.

Ich plane, eine Spalte vom Typ datetime2 (7) im Product und es manuell zu aktualisieren, wenn alle datasätze der Tabellen, die diese Tabelle erben, geändert werden.

Ich denke, ich bin neu erfinden das Rad.

Gibt es einen anderen path, um die optimistische Parallelitätsstrategie mit ROWVERSION bei der Verwendung von Table per Type (TPT) in Entity Framework zu verwenden?

Bearbeiten

Meine Mapping:

 class Product { int Id { get; set; } string Name { get; set; } byte[] RowVersion { get; set; } } class Car : Product { int Color { get; set; } } } class Product { int Id { get; set; } string Name { get; set; } byte[] RowVersion { get; set; } } class Car : Product { int Color { get; set; } } } class Product { int Id { get; set; } string Name { get; set; } byte[] RowVersion { get; set; } } class Car : Product { int Color { get; set; } } } class Product { int Id { get; set; } string Name { get; set; } byte[] RowVersion { get; set; } } class Car : Product { int Color { get; set; } } } class Product { int Id { get; set; } string Name { get; set; } byte[] RowVersion { get; set; } } class Car : Product { int Color { get; set; } } } 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 hat benutzerdefinierte Definitionen:

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

Solutions Collecting From Web of "Optimistische Parallelität: IsConcurrencyToken und RowVersion"

Du musst das Mapping verwenden

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

IsConcurrencyToken konfiguriert eine Eigenschaft als Parallelitätstoken, aber (bei Verwendung für eine byte[] -Eigenschaft )

  • der datatyp ist nvarchar (max)
  • Sein Wert ist immer null, wenn Sie es nicht initialisieren
  • Sein Wert wird nicht automatisch erhöht, wenn ein datasatz aktualisiert wird.

IsRowVersion auf der anderen Seite,

  • hat datatyp rowversion (in Sql server oder timestamp in früheren Versionen), also
  • Sein Wert ist niemals null und
  • Sein Wert wird immer automatisch erhöht, wenn ein datasatz aktualisiert wird.
  • und es konfiguriert automatisch die Eigenschaft zu einem optimistischen Parallelitätstoken.

Jetzt, wenn Sie ein Car aktualisieren, sehen Sie zwei Update-statementen:

 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 statement aktualisiert nichts, aber es erhöht die Zeilenumwandlung, und es wird eine concurrencysstörung auslösen, wenn die Zeilenumwandlung zwischen ihnen geändert wurde.

Übrigens entspricht IsRowVersion dem Attribut 1506105277 :

 1506105277 public byte[] RowVersion { get; set; } 

Nach ein wenig zu untersuchen konnte ich IsConcurrencyToken auf einer Byte [8] Spalte namens RowVersion in Entity Framework 6 verwenden.

Weil wir denselben datatyp in DB2 verwenden wollen (der keine Zeilenumwandlung in der database hat), können wir die Option IsRowVersion () nicht verwenden!

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

Ich habe folgendes getan, um eine Lösung zu finden, die zu funktionieren scheint:

Mein 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); } } } }  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); } } } }  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); } } } }  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); } } } }  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); } } } }  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); } } } }  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); } } } }  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 wird verwendet, um Entitäten zu identifizieren, die eine Zeilenumwandlung haben, die eine spezielle Behandlung erfordert.

Ich habe fließend API verwendet, um den 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(); } } }  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 habe ich eine Methode zu meiner abgeleiteten DBContext-class hinzugefügt, um das Feld zu aktualisieren, bevor die base.SaveChanges aufgerufen wird:

  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!! } } } } }  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!! } } } } }  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!! } } } } }  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!! } } } } }  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, das die meisten Menschen begegnen, ist, dass nach dem Setzen des Wertes der Entität wir immer eine UpdateDBConcurrencyException bekommen, weil die OriginalValue sich geändert hat … auch wenn es nicht!

Der Grund dafür ist, dass für ein Byte [] sowohl die ursprüngliche als auch die currentValue sich ändern, wenn man den CurrentValue alleine setzt ("seltsames und unerwartetes Verhalten").

Also habe ich den OriginalValue wieder auf den ursprünglichen Wert gesetzt, bevor ich die rowversion aktualisiert habe … Auch ich kopiere das Array, um zu vermeiden, dass ich das gleiche Byte-Array referenziere!

Achtung: Hier verwende ich einen inkrementellen Ansatz, um die rowversion zu ändern, du kannst deine eigene Strategie nutzen, um diesen Wert auszufüllen. (Zufällig oder zeitbasiert)