Datensatz sperren in Entity Framework 6: Optimistisches vs. Pessimistisches Sperren

„Wie arbeite ich mit Datensatzsperren?“  ist eine der häufigsten Fragen beim Einstieg in die Arbeit mit dem Entity Framework. Hintergrund ist der Wunsch, die Änderungen, die ein Benutzer in der Datenbank hinterlassen hat, nicht ohne Weiteres durch andere Schreibzugriffe zu überschreiben – die sogenannte Concurrency.
Aus älteren Datenbanksystemen kennt man das pessimistische Sperren, was mit Technologien wie dem klassischen Recordset oder ähnlichen, Cursor-artigen Konzepten kein Problem war: Ein Datensatz wurde einfach zur Bearbeiten durch andere gesperrt, sobald ein Benutzer begann, diesen zu bearbeiten.
In mehrschichtigen Szenarien, wie sie heute in modernen Desktopanwendungen und Apps verwendet werden, ist diese Sperrung nur auf Datenbankserverseite möglich, erst Recht bei Webanwendungen, wo noch ein aufwändiges Session-Handling dazukommt. Entity Framework 6 arbeitet jedoch applikations- oder clientseitig, ein direkter Serverzugriff ist nicht vorgesehen. Daher unterstützt Entity Framework nur „Optimistisches Sperren“ – heißt: Alle Änderungen werden zugelassen, und erst beim Schreibzugriff wird geprüft, ob sich in der Zwischenzeit die zu bearbeitenden Daten geändert haben.

Entity Framework 6 unterstützt den Entwickler auf sehr einfache, aber effektive Weise: Datenbanktabellen, in denen konkurrierende Schreibzugriffe abgefangen werden müssen (gilt längst nicht für jede Tabelle in der DB!), bekommen zusätzlich eine Spalte, deren Wert beim Schreiben grundsätzlich verändert wird. In Microsofts SQL-Welt gibt es dafür den timestamp-Datentyp, der hier nachträglich in die Customers-Tabelle der Nordwind-Datenbank eingebaut wird:

timestamp_column
Beim Erstellen des Entity-Modells wird daraus eine TimeStamp-Property in der generierten Entitätsklasse:
efmodell
Spannend für die Concurrency-Behandlung sind die beiden Properties Parallelitätsmodus (engl. „Concurrency Mode“) und StoreGeneratedPattern (zum Glück hat man darauf verzichtet, dies zu übersetzen) der generierten TimeStamp-Property:

efmodell_props
Während „StoreGeneratedPattern“ bei Feldern vom Typ timestamp bereits vom EF-Designer auf „Computed“ gesetzt wird (die anderen hier zulässigen Werte sind „Identity“ – siehe Auto-Inkrement-Primärschlüssel – und „None“), muss der „Parallelitätsmodus“ von Hand auf „Fixed“ gesetzt werden. Hierdurch wird beim Speichern des Datensatzes auf anderweitige Veränderungen geprüft und eine DbUpdateConcurrencyException geworfen, falls der Datensatz anderweitig geändert wurde, während er im speichernden Client geöffnet war.

Um das alles einfach zu testen, kann man sich hervorragend den Umstand zu Nutze machen, dass zwei verschiedene Objekt-Kontexte auch innerhalb einer Routine denselben Datensatz verändern können, ohne dass der jew. andere Kontext etwas davon mitbekommt. ctxA und ctxCC seien diese Kontexte, die beide versuchen, ein Customer-Objekt (die Nordwind-Datenbank ist nicht tot!) zu speichern:

using (NWindEntities ctxAA = new NWindEntities())
{
   using (NWindEntities ctxCC = new NWindEntities())
   {
      Customer cuCC = ctxCC.Customers.Where(cu => cu.CustomerID == cuAA.CustomerID).FirstOrDefault();

      cuCC.ContactName = new string(cuAA.ContactName.Reverse().ToArray());
      ctxCC.SaveChanges();
   }

   ctxAA.SaveChanges();
}

cuAA ist ein Customer-Objekt, das bereits geändert wurde. Bevor ctxAA unseren cuAA speichert, schnappt sich ctxCC unseren Customer mit Hilfe einer weiteren Objektreferenz cuCC, dreht den ContactName auf links und speichert diese Änderung direkt. Wenn ctxAA speichern möchte, tritt besagte DbUpdateConcurrencyException auf, die verhindert, dass die Änderungen in der Datenbank überschrieben werden.

Und nun?

Zum einen natürlich ein ordentliches Exception-Handling: Die DbUpdateConcurrencyException wird sauber abgefangen und an die GUI weitergereicht, damit der Benutzer informiert wird. Je nach Anwendungsfall, wobei vielleicht auch Berechtigungskonzepte eine Rolle spielen können, gibt es verschiedene Möglichkeiten:

  • Die Änderungen werden verworfen (Database wins).
    • Bei einem sauberen Aufbau der Applikation ist hierzu häufig lediglich das Disposen des geänderten Objekts oder des DbContext nötig. Anderenfalls würde man im obigen Beispiel mit ctx.Entry(cuAA).Reload(); das Customer-Objekt auch zurücksetzen können.
      -> Der klassische Weg aus Framework 5 und früher über ObjectContext.Refresh(RefreshMode.StorageWins, entity) steht im Framework 6 nur noch über ein mühsames Casting zur Verfügung, weil die im Framework 6 verwendete DbContext-Klasse nicht von ObjectContext erbt, sondern lediglich das IObjectContextAdapter-Interface implementiert.
  • Die Änderungen werden dennoch geschrieben (Client wins).
    • Damit beim Speichern keine erneute Exception auftritt, muss dem DbContext vorgegaukelt werden, die alten und die neuen Werte seien gleich: ctxAA.Entry(cuAA).OriginalValues.SetValues(ctxAA.Entry(cuAA).GetDatabaseValues());. Ein erneutes Speichern wirft dann keine Exception mehr.
      -> Der klassische Weg aus Framework 5 und früher über ObjectContext.Refresh(RefreshMode.ClientWins, entity) steht im Framework 6 nur noch über ein mühsames Casting zur Verfügung, weil die im Framework 6 verwendete DbContext-Klasse nicht von ObjectContext erbt, sondern lediglich das IObjectContextAdapter-Interface implementiert.
  • Die Änderungen werden mit den Originalwerten verglichen, der Benutzer kann entscheiden, welche er haben will.
    • Erfordert deutlich mehr Coding-Aufwand, dürfte aber die höchste Benutzerfreundlichkeit bieten.

LINQ to Office? Cast!

Jeder, der mich auch nur ein bißchen als Entwickler kennt, weiß, dass ich LINQ liebe – Mengenoperationen in der prozeduralen Programmierung, was will man mehr? Vielleicht genau dort einsetzen, wo viele von uns, mich eingeschlossen, Objektmengen zum ersten Mal erlebt haben, nämlich in den Office-Objektmodellen?

Eine Aufgabe, die sich zum Beispiel beim Programmieren von Office-Lösungen häufig stellt, ist das „Auffinden“ eines Objekts an Hand seines Namens oder anderer Eigenschaften. Oft finden sich solche Konstrukte im Code, hier am Beispiel von Excel:

	public Worksheet GetWorksheetByNameClassic(string name)
        { 
            Workbook wbkAktiv = Globals.ThisAddIn.Application.ActiveWorkbook;

            foreach (Worksheet ws in wbkAktiv.Worksheets)
            {
                if (ws.Name == name)
                {
                    return ws;
                }
            }
            return null;
        }

Nicht schön, erzielt aber das gewünschte Ergebnis.

Ein erster Versuch, diese Methode in LINQ zu implementieren, schlägt fehl:

"Could not find an implementation of the query pattern for source type 'Microsoft.Office.Interop.Excel.Sheets'. 'Where' not found." lautet in Visual Studio die zugehörige Fehlermeldung. Nunja, wie so oft nicht wirklich hilfreich, insbesondere für LINQ-Neulinge.

Hintergrund ist der Umstand, das LINQ nur verwendet werden kann, wenn die abzufragende Objektmenge auch LINQ-fähig ist. Weil unsere VSTO-Klassen aber lediglich Verkapselungen von COM32-Objekten sind – sie sind ja nicht einmal „richtige“ Klassen, sondern in den meisten Fällen lediglich Interfaces -, sind die Auflistungen in VSTO nicht LINQ-fähig.

Schade.

Quatsch, wir machen sie einfach LINQ-fähig! Das Zauberwort heißt „Casting“ und ist jedem Entwickler, der Option Strict On verwendet, schon längst hinlänglich bekannt. LINQ bringt dafür unter anderem eine eigene Extension Method mit, und plötzlich weiß nicht nur die Intellisense von Visual Studio, was wir meinen, sondern es funktioniert auch das spätere Kompilat ohne Zicken:

        public Worksheet GetWorksheetByName(string name)
        {
            Workbook wbkAktiv = Globals.ThisAddIn.Application.ActiveWorkbook;

            var q = from ws in wbkAktiv.Worksheets.Cast<Worksheet>()
                    where ws.Name == name
                    select ws;

            return q.FirstOrDefault();
        }

Die Zeilenzähler unter den Lesern werden jetzt natürlich sagen: „Schön. Wir machen uns also die Mühe und bauen ein ausgefuchstes LINQ Statement, nur um am Ende im Grunde die gleiche Methode zu haben wie zuvor, und haben damit nicht einmal eine Codezeile gewonnen?“ – Gegen solche Argumente hilft naturgemäß kein Verweis auf die wesentlich elegantere Struktur von LINQ, verbessertes Speichermanagement und die Unsauberkeit abgebrochener For-Schleifen.

Stattdessen eher diese eine(!) Codezeile:

Worksheet wsNamensTest = wbkAktiv.Worksheets.Cast<Worksheet>()
                                            .Where(ws => ws.Name == "Test")
                                            .FirstOrDefault();

Eingesetzt an der Stelle, wo unsere GetWorksheetByName-Funktion aufgerufen wird, und die ganze Funktion ist komplett überflüssig (und alle ihre Geschwister, die ähnliche Aufgaben erledigen)!

Objektorientierter Zugriff auf OneNote-XML 2010/2013 mit LINQ to XML

Die OneNote API mit ihrem Namensraum Microsoft.Office.Interop.OneNote (Developer Referenz für 2013 hier) bietet zwar für den Zugriff auf Fenster oder Enumerationen ein Objektmodell, wie wir es aus anderen Office-Anwendungen gewohnt sind.
Doch dort, wo es wirklich spannend würde, hört das Modell abrupt auf und stellt lediglich simple API-Aufrufe zur Verfügung: Beim Zugriff auf Notizbücher, Abschnitte und Seiten sieht sich der geneigte .NET-Entwickler plötzlich XML-Manipulationen gegenüber, die in den meisten Beispielen (nicht nur im Dev Center) in wilde String-Bearbeitungen ausarten. Deswegen hier der Versuch eines objektorientierten Ansatzes.

Schön wäre etwas wie

	meinNotizbuch.Sections.Count();

Natürlich ist ein einzelner Blogbeitrag zu klein, um ein ganzes Objektmodell zu entwickeln, doch einen Ansatz dafür möchte ich hier anbieten.

Ganz prinzipiell und stark verkürzt sieht das Schema von OneNote XML so aus (wer das ganze XSD haben möchte – hier), wie sie uns die GetHierarchy-Methode der OneNote-Application liefert:

<?xml version="1.0"?>
<one:Notebooks xmlns:one="http://schemas.microsoft.com/office/onenote/2013/onenote">
	<one:Notebook name="" ID="" path="">
		<one:Section name="" ID="" path="">
			<one:Page  name="" ID="" />
			<one:Page  name="" ID="" />
			<!-- weitere Pages -->
		</one:Section>
		<one:Section name="" ID="" path="">
			<one:Page  name="" ID="" />
			<one:Page  name="" ID="" />
			<!-- weitere Pages -->
		</one:Section>
		<!-- weitere Sections -->
	</one:Notebook>
</one:Notebooks>

Wir brauchen also für einen objektorientierten Ansatz eine Notebook-, eine Section– und eine Page-Klasse, wo bei jeweils eine Notebook-Instanz eine Sections-Auflistung anbietet, die ihrerseits jeweils eine Pages-Auflistung enthält – man beachte die Verwendung des Plurals bei den Auflistungen:
Simples Objektmodell

Für LINQ wurde in C# der sogenannte Objektinitialisierer eingeführt (mehr dazu in der MSDN), der im Prinzip so aussieht:

Typ meineInstanz = new Typ() { 
                               Property1 = Wert, 
                               Property2 = Wert 
                             };

Dieser läßt sich ganz prima innerhalb von LINQ-Statements verwenden, um Objektmengen zu initialisieren. Im Falle unseres Objektmodells bräuchten wir also in einer aufrufenden Instanz, zum Beispiel einer OneNoteController-Klasse, eine Funktion, die uns das OneNote-XML parst und die Objektmengen aus unserem Objektmodell baut:

using ont = Microsoft.Office.Interop.OneNote;
using System.Xml.Linq;

[...]

private List<Notebook> GetNotebookStructure()
{
        string strOneNoteXml;
        appOneNote.GetHierarchy(null, ont.HierarchyScope.hsPages, out strOneNoteXml);

        XDocument xdNotebooks = XDocument.Parse(strOneNoteXml);

        XNamespace one = "http://schemas.microsoft.com/office/onenote/2013/onenote";

        var qNotebooks = from nd in xdNotebooks.Root.Descendants(one + "Notebook")
                select new Notebook()
                {
                        Name = nd.Attribute("name").Value,
                        ID = nd.Attribute("ID").Value,
                        Path = nd.Attribute("path").Value,
                        Sections = (from ab in nd.Descendants(one + "Section")
                                     select new Section()
                                     {
                                          Name = ab.Attribute("name").Value,
                                          ID = ab.Attribute("ID").Value,
                                          Path = ab.Attribute("path").Value,
                                          Pages = (from pg in ab.Descendants(one + "Page")
                                                  select new Page()
                                                  {
                                                       Name = pg.Attribute("name").Value,
                                                       ID = pg.Attribute("ID").Value
                                                   }).ToList()
                                     }).ToList()
                        };
        List<Notebook> lst = qNotebooks.ToList();
        return lst;
}

Das passiert hier:
Die GetHierarchy-Methode betankt unseren strOneNoteXML-String. Damit wird ein XDocument (Namensraum System.Xml.Linq) erzeugt, das uns danach für LINQ-Abfragen zur Verfügung steht. Die Descendants([XName])-Methode liefert uns für den jeweiligen Ausgangsknoten die in der Hierarchie darunterliegenden Knoten mit dem gegebenen Namen. Da diese (siehe oben in der XML-Struktur) jeweils ihre Kinder mitbringen, lassen sich unsere Auflistungsproperties Notebook.Sections bzw. Section.Pages gleich innnerhalb dieses LINQ-Statements ebenfalls aus Queries befüllen.

Eine Rohform der bereits erwähnten OneNoteController-Klasse könnte also so aussehen:

using ont = Microsoft.Office.Interop.OneNote;
using System.Xml.Linq;

namespace ont2013OM
{
    public class OneNoteController
    {
        ont.Application appOneNote = null;

        public OneNoteController(ont.Application oneNoteApp)
        {
            appOneNote = oneNoteApp;
        }

        private List<Notebook> GetNotebookStructure() [...]
        
        public List<Notebook> Notebooks
        {
            get
            {
                return GetNotebookStructure();
            }
            private set { }
        }
    }
}

Wird diese Klasse beim Start eines OneNote-AddIns instanziiert, steht dem engagierten OneNote-Entwickler danach ein – zugegebenermaßen noch sehr rudimentäres – Objektmodell für OneNote-Elemente zur Verfügung, das solchen Code erlaubt:

    OneNoteController meinController = new OneNoteController(OneNote.Application);

    Notebook nbMeineNotizen = meinController.Notebooks
                                   .Where(nb => nb.Name == "Meine Notizen")
                                   .FirstOrDefault();

    int anzahlAbschnitte = nbMeineNotizen.Sections.Count();

Die bisherigen Erfahrungen mit diesem Ansatz zeigen gute Performanz, auch bei größeren Notebook-Sammlungen.
Analog zum XmlDocument aus System.Xml stellt das hier verwendete XDocument aus System.Xml.Linq auch Methoden zur Manipulation der XML-Struktur bereit, wodurch Änderungen an der Notebook-Struktur von OneNote keine Unmöglichkeit mehr sind. Die XDocument.ToString()-Methode gibt nach Abschluss aller Manipulationen den fertigen XML-Code des XDocuments zurück und kann daher ganz hervorragend in den Application.UpdateHierarchy()– bzw. Application.UpdatePageContent()-Methoden verwendet werden, um den Inhalt von OneNote anzupassen.

OneNote 2013 Desktop: AddIn Vorlage für Visual Studio

Visual Studio liefert kein fertiges Template für OneNote-AddIns. Das OneCode Team stellt im Office Developer Center jedoch eine Alternative bereit – Download hier. Obwohl für OneNote 2010 entwickelt, ist dieser Code auch für OneNote 2013 verwendbar!

Wer einen grafischen Designer fürs Ribbon erwartet, wird zwar enttäuscht, eine funktionsfähige Ribbon-XML-Datei liegt jedoch in den Ressourcen des Projekts und steht sofort zur Verfügung, da das OneCode Team bereits die Implementierung des IRibbonExtensibility-Interfaces umgesetzt hat – sogar das Fensterhandling in COM32-Umgebungen ist bereits implementiert!

Leider liegt die angebotene Projektmappe im Format für Visual Studio 2010 vor, und das OneCode Team hat, wohl im Bemühen, uns verzweifelten Entwicklern das Leben so einfach wie möglich zu machen, gleich auch ein fertiges Setupprojekt mit hineingebaut. Visual Studio 2013 unterstützt diesen Projekttyp nicht mehr, weswegen es beim Konvertieren jede Menge Zicken macht. Ein getrenntes Öffnen des eigentlichen AddIn-Projekts (Ordner „CSOneNoteRibbonAddIn“) in Visual Studio 2013 ist jedoch nahezu problemlos möglich, ein Installshield-Projekt kann der geneigte Entwickler dann später immer noch hinzufügen.

Zur Installation auf dem Entwicklerrechner müssen einige Registry-Keys für das AddIn angepasst/hinzugefügt werden:

reg32.onenote

Die GUID der Assembly (findet sich in der AssemblyInfo.cs bzw. den Projekteigenschaften) muß an den entsprechenden Stellen eingetragen werden (Suchen & Ersetzen leistet hier gute Dienste), desweiteren der Einstiegspunkt des AddIns sowie Name und Beschreibung – letztere erscheinen im AddIn-Manager von OneNote, werden also vom Benutzer wahrgenommen!

Die echte Programmierung des AddIns mit C# beginnt in der beiliegenden Connect.cs, wo sich neben den AddIn-Ereignissen auch die Formularsteuerung findet. Hier können saubere Objektmodelle für die eigentliche AddIn-Idee angeflanscht werden.