Development

Tipps und Tricks rund um .NET und Visual Studio

Der Concurrency Mode beim Code-based Modelling
Keine Kommentare

Dr. Holger Schwichtenberg teilt in der Kolumne „.NETversum“ sein Wissen rund um .NET-Tools und WPF mit. In dieser Ausgabe geht es um das Setzen des Concurrency Modes für alle Spalten bei Code-based Modelling.

Genau wie bei den EDMX-basierten Vorgehensweise „Database First“ und „Model First“ gilt auch beim „Code-based Modeling“ (alias „Code First“ oder „Code Only“), dass das Entity Framework in der Grundeinstellung bei schreibenden Datenzugriffen die Strategie „Der Letzte gewinnt“ nutzt. Das ist jedoch in der Praxis oft kein gangbarer und für die Nutzer auch kein akzeptabler Weg. Entwickler können aber bei Entity Framework das „optimistische Sperren“ (wie beim ADO.NET Dataset) aktivieren.

Beim Code-based Modeling erfolgt dies durch Annotation mit dem ConcurrencyCheckAttribute (siehe Plaetze und FreiePlaetze in Listing 1) – egal, ob die Entitätsklasse selbst geschrieben oder aus einer bestehenden Datenbank von Visual Studio per Reverse Engineering generiert wurde.

public partial class Flug
 {
   [Key]
  [Column(Order = 1)]
  public int FlugNr { get; set; }
   
   [Required]
   [StringLength(50), MinLength(3)]
   public string Abflugort { get; set; }
   
   [Required]
   [StringLength(50), MinLength(3)]
   public string Zielort { get; set; }
 
  [Required]
  [Column("FlugDatum", Order = 3)]
  public System.DateTime Datum { get; set; }
 
  [Required]
  [ConcurrencyCheckAttribute]
  [Range(100, 250)]
  [Index("NCL_PlaetzeFreiePlaetze", 1, IsUnique = false, IsClustered = false)]
  public short? Plaetze { get; set; }
 
  [ConcurrencyCheckAttribute]
  [Index("NCL_PlaetzeFreiePlaetze", 2, IsUnique = false, IsClustered = false)]
  public short? FreiePlaetze { get; set; }
 }

Dann entsteht im Fall einer Änderung an den freien Plätzen eines Flug-Objekts ein UPDATE-Befehl wie der folgende, in dem in der WHERE-Bedingung die Ursprungswerte aufgenommen sind:

exec sp_executesql N'UPDATE [dbo].[Flug]
SET [FreiePlaetze] = @0
WHERE ((([FlugNr] = @1) AND ([Plaetze] = @2)) AND ([FreiePlaetze] = @3))
',N'@0 smallint,@1 int,@2 smallint,@3 smallint',@0=22,@1=101,@2=250,@3=24

Das Gleiche passiert bei DELETE-Befehlen.

Allerdings ist es lästig, das ConcurrencyCheckAttribute explizit vor jede Property schreiben zu müssen – und wehe, man vergisst es an einer Stelle. Dann droht Dateninkonsistenz!

Zum Glück bietet Entity Framework im Bereich des Code-based Modeling einige Flexibilität, und so ist es bei diesem Vorgehensmodell möglich, eine elegantere Lösung zu schaffen, um das optimistische Sperren zu aktivieren: Man kann sich eine eigene Entity-Framework-Konvention schreiben, die den Standard umkehrt, also im Normalfall für alle Spalten den Ursprungswertevergleich durchführt. Dabei kann man dann natürlich auch einbauen, dass Ausnahmen erlaubt sind. Diese Ausnahmen könnte man nach Regel definieren (z. B. Namensbestandteile).

Die Praxislösung im nachfolgenden Listing geht anders vor: Sie sucht nach der selbstdefinierten Annotation [ConcurrencyNoCheckAttribute]. Wenn diese Annotation bei einer Property verwendet wird, findet für die Property kein Ursprungswertevergleich statt. Es ist auch möglich, die Annotation [ConcurrencyNoCheckAttribute] auf Klassenebene einzusetzen und damit den Ursprungswertevergleich für die ganze Klasse zu deaktivieren.

/// <summary>
/// Annotation für DateTime-Properties, die in der Datenbank nur als "date" gespeichert werden soll
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)]
public class ConcurrencyNoCheckAttribute : Attribute
{
}

Listing 2 zeigt die selbstdefinierte Entity-Framework-Konvention ConcurrencyFixedConvention, die die Schnittstelle IConceptualModelConvention<EdmProperty> implementiert. Die Methode Apply() wird im Rahmen der internen Initialisierung des Entity-Framework-Kontexts für jede Property in jeder Entitätsklasse aufgerufen. Die Implementierung sucht nach dem ConcurrencyNoCheckAttribute auf Klassen- und auf Property-Ebene. Wenn diese Annotation auf beiden Ebenen nicht gefunden wurde, wird der ConcurrencyMode der Properties auf den Wert ConcurrencyMode.Fixed gesetzt, andernfalls auf ConcurrencyMode.None.

/// <summary>
/// Legt im Standard ConcurrencyMode.Fixed fest, außer bei mit [ConcurrencyNoCheckAttribute] annotierten Klassen und Properties
/// </summary>
class ConcurrencyFixedConvention : IConceptualModelConvention<EdmProperty>
{
 
 public void Apply(EdmProperty property, DbModel model)
 {
  List<System.Attribute> propertyAttributliste = null;
  List<System.Attribute> klassenAttributliste = null;
 
  // lese Annotation des Typs
  var klassenAnnotationen = property.DeclaringType.MetadataProperties.Where(a => a.Name == "ClrAttributes").FirstOrDefault();
  if (klassenAnnotationen != null && klassenAnnotationen.Value != null)
  {
   klassenAttributliste = klassenAnnotationen.Value as List<System.Attribute>;
  }
 
  // lese Annotationen der aktuellen Property
  var annotationen = property.MetadataProperties.Where(a => a.Name == "ClrAttributes").FirstOrDefault();
  if (annotationen != null && annotationen.Value != null)
  {
   propertyAttributliste = annotationen.Value as List<System.Attribute>;
  }
 
  // Gibt es die Annotation [ConcurrencyNoCheckAttribute] für die Klassen oder die Property?
  if ((klassenAttributliste != null && klassenAttributliste.OfType<ConcurrencyNoCheckAttribute>().Count() > 0) || (propertyAttributliste != null && propertyAttributliste.OfType<ConcurrencyNoCheckAttribute>().Count() > 0))  // Es gibt die Annotation [ConcurrencyNoCheckAttribute]
  {
   property.ConcurrencyMode = ConcurrencyMode.None;
   CUI.Print("ConcurrencyFixedConvention: " + property.DeclaringType + "." + property.Name + "-> ConcurrencyMode.None", ConsoleColor.Red);
  }
  else  // Es gibt keine Annotation [ConcurrencyNoCheckAttribute]
  {
   if (property.DeclaringType.BaseType == null)
    CUI.Print("ConcurrencyFixedConvention: " + property.DeclaringType + "." + property.Name + "-> ConcurrencyMode.Fixed", ConsoleColor.Green);
   property.ConcurrencyMode = ConcurrencyMode.Fixed;
  }
 
 
 }
}

Listing 3 zeigt, wie man die neue Konvention aktiviert mit modelBuilder.Conventions.Add() in der Methode OnModelCreating() der von DbContext abgeleiteten Kontextklasse.

public class FluggesellschaftContext : DbContext
{
   
 public DbSet<Flug> Fluege  { get; set; }
 public DbSet<Pilot> Piloten { get; set; }
 public DbSet<Person> Personen { get; set; }
 public DbSet<Passagier> Passagiere { get; set; }
 public DbSet<Flughafen> Flughafen { get; set; }
 
 
 /// <summary>
 /// Methode zur Aktivierung/Deaktivierung von Konventionen sowie zur manuellen Konfiguration per Fluent-API
 /// </summary>
 
protected override void OnModelCreating(DbModelBuilder modelBuilder)
 {
  Console.WriteLine("======== OnModelCreating");

// Eigene Konvention aktivieren
  modelBuilder.Conventions.Add<ConcurrencyFixedConvention>();
…
}
}

Zum Schluss der SQL-UPDATE-Befehl, der nun bei einer Änderung entsteht. Es gibt hier nun den ersehnten Ursprungswertevergleich auf allen Spalten:

exec sp_executesql N'UPDATE [dbo].[Flug]
SET [FreiePlaetze] = @0
WHERE ((((((((((([FlugNr] = @1) AND ([FlugGesellschaft] = @2)) AND ([FlugDatum] = @3)) AND ([Abflugort] = @4)) AND ([Zielort] = @5)) AND ([NichtRaucherFlug] = @6)) AND ([Plaetze] = @7)) AND ([FreiePlaetze] = @8)) AND ([PilotId] = @9)) AND ([CopilotId] = @10)) AND ([Bestreikt] = @11))
',N'@0 smallint,@1 int,@2 int,@3 datetime2(7),@4 nvarchar(50),@5 nvarchar(50),@6 bit,@7 smallint,@8 smallint,@9 int,@10 int,@11 bit',@0=15,@1=101,@2=0,@3='2016-02-29 13:34:58.6230000',@4=N'Kapstadt',@5=N'Moskau',@6=0,@7=250,@8=17,@9=103,@10=355,@11=1

Buchempfehlungdotnet_praxis

In den vergangenen Jahren haben Dr. Holger Schwichtenberg und Manfred Steyer im „Windows Developer“ regelmäßig Tipps und Tricks aus ihrem Alltag als Softwareentwickler, Berater und Trainer für .NET- und webbasierte Anwendungen veröffentlicht. Das vorliegende Buch ist eine aktualisierte und deutlich erweiterte Zusammenfassung dieser Tipps und Tricks. Es umfasst sowohl das .NET Framework und seine zahlreichen Teilbibliotheken als auch die beliebte JavaScript-Bibliothek AngularJS sowie die Entwicklungsumgebung Visual Studio und ergänzende Werkzeuge.

Mehr Infos: www.entwickler-press.de/Dotnet-Praxis

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -