In der Arbeit mit Azure Cosmos DB gibt es eine Reihe grundlegender Operationen, die regelmäßig durchgeführt werden, um Daten zu verwalten. In diesem Abschnitt geht es um das Erstellen, Auflisten und Löschen von Produktartikeln in einer Cosmos DB-Datenbank. Die Umsetzung dieser Aufgaben ist grundlegend für das Verständnis und die Nutzung von NoSQL-Datenbanken in der Praxis.

Zunächst einmal ist es wichtig zu wissen, wie man Artikel in der Datenbank anlegt. Dies geschieht durch das Erstellen von Elementen, die eine eindeutige ID und bestimmte Eigenschaften enthalten, wie beispielsweise productName und unitPrice. Bei einer Abfrage der Datenbank zeigt Cosmos DB an, wie viele Artikel bereits vorhanden sind und welche Anfragen verarbeitet wurden. Im Beispiel wird eine Abfrage durchgeführt, um festzustellen, ob ein Produkt bereits existiert, wobei die Antwort immer eine Bestätigung enthält, dass das Produkt existiert, und die RUs (Request Units) anzeigt, die für jede Abfrage verbraucht wurden.

Die zweite Aufgabe ist das Auflisten der Produktartikel. Eine einfache SQL-ähnliche Abfrage wird ausgeführt, um alle Elemente aus einem Container zu selektieren. Das Ergebnis ist eine Liste der Produkte, die ihren Statuscode, die Anzahl der Produkte und die zugehörigen Daten wie id, productName und unitPrice anzeigt. Dies ermöglicht eine einfache Übersicht über die vorhandenen Daten in der Datenbank. Bei der Ausführung dieses Prozesses ist es wichtig, dass der Entwickler sicherstellt, dass die Verbindung zur Azure Cosmos DB korrekt und stabil ist, um Abfragefehler zu vermeiden. Zudem wird in der Antwort der Anfrage auch die verbrauchte Anzahl an RUs angezeigt, was eine wertvolle Metrik für die Kostenoptimierung und Ressourcennutzung darstellt.

Die dritte wichtige Aufgabe ist das Löschen der Produktartikel. In der Praxis könnte es notwendig sein, alle Einträge in einem Container zu löschen. Dies geschieht durch eine einfache SQL-Abfrage, die alle Elemente auswählt und diese dann aus der Datenbank entfernt. Bei jedem Löschvorgang wird der Statuscode des Löschvorgangs sowie die Anzahl der RUs angezeigt, die für jede Löschoperation verwendet wurden. Der Developer sollte darauf achten, dass das Löschen von Elementen nicht rückgängig gemacht werden kann und daher mit Bedacht durchgeführt werden muss. Eine genaue Dokumentation und Sicherstellung der Datenintegrität vor dem Löschen ist entscheidend.

Darüber hinaus sind auch SQL-Abfragen eine wesentliche Fähigkeit für die Arbeit mit Cosmos DB. Mit der Core SQL API können einfache wie auch komplexe Abfragen formuliert werden, um spezifische Daten aus einem Container zu extrahieren. Es gibt eine Reihe von SQL-Schlüsselwörtern, die in Cosmos DB verwendet werden können: SELECT, FROM, WHERE, LIKE, IN, BETWEEN, ORDER BY und viele andere. Besonders wichtig ist es, mit den richtigen Filtern zu arbeiten, um nur die gewünschten Datensätze zu selektieren, was eine wichtige Grundlage für die effiziente Nutzung der Datenbank darstellt.

Eine besonders nützliche SQL-Abfrage im Beispiel ist diejenige, die Produkte aus einer bestimmten Kategorie auswählt. Dies wird mit einem WHERE-Filter durchgeführt, um die Produkte der Kategorie "Beverages" zu extrahieren. Diese Abfrage kann auch auf die Auswahl bestimmter Felder beschränkt werden, wie etwa id, productName und unitPrice. Eine solche gezielte Abfrage hilft dabei, Daten gezielt zu extrahieren, was besonders bei großen Datensätzen von Bedeutung ist.

Wichtig zu verstehen ist, dass die Abfragen und Operationen auf einer NoSQL-Datenbank wie Azure Cosmos DB nicht unbedingt das gleiche Verhalten wie bei relationalen Datenbanken aufweisen. Die Flexibilität der Schemafreiheit in Cosmos DB bedeutet, dass Daten nicht an ein starr definiertes Schema gebunden sind. Diese Flexibilität kann jedoch auch Herausforderungen mit sich bringen, insbesondere in Bezug auf Datenkonsistenz und -integrität. Es ist daher entscheidend, die Datenmodellierung und Abfragen so zu gestalten, dass die Anwendungsanforderungen optimal erfüllt werden.

Abschließend ist zu sagen, dass der Umgang mit Azure Cosmos DB und das Verständnis der Funktionsweise von SQL-Abfragen und der zugrundeliegenden Datenstruktur für eine effiziente Datenbankverwaltung unerlässlich ist. Die Fähigkeit, Daten zu erstellen, zu listen und zu löschen, sowie zu verstehen, wie man mit SQL-Abfragen bestimmte Daten extrahiert, ist grundlegend für den erfolgreichen Einsatz von Cosmos DB in der Praxis.

Wie man mit Reflection arbeitet: Assemblies, Attribute und benutzerdefinierte Attribute in .NET

In der modernen .NET-Entwicklung bietet Reflection eine leistungsstarke Möglichkeit, zur Laufzeit Informationen über Assemblies, Typen und deren Mitglieder zu erhalten. Reflection ermöglicht es, Metadaten zu extrahieren, Assemblies zu inspizieren und sogar die Ausführung von Code dynamisch zu ändern. In dieser Kapiteln werden wir einige grundlegende Konzepte und Arbeitsweisen mit Reflection durchgehen und dabei sowohl vorgefertigte als auch benutzerdefinierte Attribute einbeziehen.

Um mit Reflection in .NET zu arbeiten, müssen wir zunächst den entsprechenden Namensraum System.Reflection importieren. Sobald dieser importiert ist, können wir über die Assembly-Klasse Informationen über die Assemblies, die im aktuellen Prozess geladen sind, erhalten. Ein typisches Beispiel ist das Abrufen von Metadaten über die Assembly, die die Anwendung enthält:

csharp
using System.Reflection; WriteLine("Assembly metadata:"); Assembly? assembly = Assembly.GetEntryAssembly(); if (assembly is null) { WriteLine("Failed to get entry assembly."); return; } WriteLine($" Full name: {assembly.FullName}"); WriteLine($" Location: {assembly.Location}"); WriteLine($" Entry point: {assembly.EntryPoint?.Name}"); IEnumerable attributes = assembly.GetCustomAttributes(); WriteLine($" Assembly-level attributes:"); foreach (Attribute a in attributes) { WriteLine($" {a.GetType()}"); }

In diesem Code erhalten wir die vollständigen Metadaten der Assembly, einschließlich des Namens, des Speicherorts und des Einstiegspunkts der Anwendung. Darüber hinaus können alle Assembly-bezogenen Attribute extrahiert und deren Typen angezeigt werden. Ein wichtiger Punkt dabei ist, dass der vollständige Name einer Assembly aus verschiedenen Komponenten besteht: dem Namen, der Version, der Kultur und dem öffentlichen Schlüssel (wobei letzterer manchmal null ist).

Ein weiteres interessantes Merkmal von Reflection ist die Möglichkeit, spezifische Attribute einer Assembly abzurufen, wie beispielsweise AssemblyInformationalVersionAttribute und AssemblyCompanyAttribute. Diese Attribute liefern zusätzliche Informationen zur Assembly wie die Version oder den Namen des Unternehmens. Beispielsweise:

csharp
AssemblyInformationalVersionAttribute? version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>(); WriteLine($" Version: {version?.InformationalVersion}"); AssemblyCompanyAttribute? company = assembly.GetCustomAttribute<AssemblyCompanyAttribute>(); WriteLine($" Company: {company?.Company}");

Bei der Ausführung des Codes wird in der Regel der Standardwert für die Version und das Unternehmen angezeigt, falls diese nicht explizit gesetzt wurden. In vielen Fällen zeigt die Version die Standardnummer 1.0.0 an, und das Unternehmen wird auf den Namen der Assembly gesetzt, wenn keine andere Information angegeben ist.

Um diese Werte anzupassen, ist es notwendig, die Projektdatei (.csproj) zu bearbeiten und bestimmte Elemente wie die Version und das Unternehmen hinzuzufügen:

xml
<PropertyGroup> <Version>7.0.1</Version> <Company>Packt Publishing</Company> </PropertyGroup>

Nach der Bearbeitung der Projektdatei und der erneuten Ausführung des Codes können die geänderten Werte angezeigt werden.

Ein weiterer spannender Aspekt von Reflection ist die Möglichkeit, benutzerdefinierte Attribute zu erstellen, die bestimmte Metadaten zu Klassen oder Methoden hinzufügen. Um ein eigenes Attribut zu definieren, erben wir von der Attribute-Klasse und können so benutzerdefinierte Eigenschaften hinzufügen. Ein einfaches Beispiel wäre das Erstellen eines CoderAttribute, das den Namen des Entwicklers und das Datum der letzten Änderung eines Codes speichert:

csharp
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CoderAttribute : Attribute { public string Coder { get; set; } public DateTime LastModified { get; set; } public CoderAttribute(string coder, string lastModified) { Coder = coder; LastModified = DateTime.Parse(lastModified); } }

Dieses Attribut kann dann verwendet werden, um Methoden oder Klassen zu dekorieren, wie im folgenden Beispiel:

csharp
public class Animal {
[Coder("Mark Price", "22 August 2022")] [Coder("Johnni Rasmussen", "13 September 2022")] public void Speak() { WriteLine("Woof..."); } }

In einem weiteren Schritt kann der Code angepasst werden, um alle Typen und deren Mitglieder in einer Assembly zu durchsuchen und alle CoderAttribute zu extrahieren, um die Historie der Änderungen nachzuverfolgen:

csharp
WriteLine(); WriteLine($"* Types:"); Type[] types = assembly.GetTypes(); foreach (Type type in types) { WriteLine(); WriteLine($"Type: {type.FullName}"); MemberInfo[] members = type.GetMembers(); foreach (MemberInfo member in members) { WriteLine("{0}: {1} ({2})", member.MemberType, member.Name, member.DeclaringType?.Name); IOrderedEnumerable<CoderAttribute> coders = member.GetCustomAttributes<CoderAttribute>() .OrderByDescending(c => c.LastModified); foreach (CoderAttribute coder in coders) { WriteLine("-> Modified by {0} on {1}", coder.Coder, coder.LastModified.ToShortDateString()); } } }

Dieser Code gibt eine Liste von Typen und deren Mitgliedern aus, zusammen mit den Details zu den Entwicklern, die Änderungen vorgenommen haben, sowie den jeweiligen Änderungsdaten.

Es gibt jedoch auch komplexere Aspekte zu beachten. Einige Typen und Mitglieder werden vom Compiler automatisch generiert und können zusätzliche Metadaten enthalten, die für den Entwickler nicht direkt von Bedeutung sind. Ein Beispiel dafür sind sogenannte "Compiler-generated" Typen, wie sie durch den Namen Program+<>c angezeigt werden. Diese Typen und ihre Mitglieder können durch die CompilerGeneratedAttribute markiert werden und sind in der Regel nicht für die normale Anwendung erforderlich.

Zusätzlich können wir Attribute wie [Obsolete] verwenden, um veraltete Mitglieder oder Typen zu kennzeichnen, damit Entwickler gewarnt werden, diese nicht mehr zu verwenden. Dies ist besonders nützlich bei der Refaktorisierung von Code, um sicherzustellen, dass die Kompatibilität mit älteren Versionen erhalten bleibt, während gleichzeitig die Nutzung neuerer Implementierungen gefördert wird.

Der Einsatz von Reflection und benutzerdefinierten Attributen bietet eine enorme Flexibilität, um Metadaten zu analysieren, zu modifizieren und dynamisch auf die Struktur einer Anwendung zuzugreifen. Mit diesen Techniken können Entwickler tiefere Einblicke in ihren Code gewinnen und gegebenenfalls Anpassungen zur Laufzeit vornehmen. Es ist jedoch wichtig, den Überblick über die Performance-Auswirkungen von Reflection zu behalten, da dieser Mechanismus zur Laufzeit zusätzliche Rechenressourcen beanspruchen kann. In sicherheitskritischen Anwendungen sollten Reflexionstechniken ebenfalls mit Vorsicht eingesetzt werden, um potenzielle Angriffsflächen zu minimieren.