Ein interessantes Thema – vorallem, weil die Schreibweisen von Adressen oft nicht trivial sind. Auf die Schnelle habe ich folgende zwei Strategien gefunden:
pg_trgm: Dieses Modul erlaubt es, die Ähnlichkeit von Wörtern zu messen. Durch das Anlegen eines Index, ist diese Methode extrem performant. Phonet: Erlaubt einen String auf einen „Aussprache“ String zu projizieren. Ähnlich klingende Strings mappen somit auf den gleichen String. Anschließend Abgleich mit pg_gtrm. Der Vorteil: Aussprachefehler werden „ausgebessert“ und führen zu einem besseren Match. Wann funktioniert das gut? Wenn man die Menge an Möglichkeiten voreinschränkt. Dazu bietet sich natürlich die Postleitzahl an. In Österreich 4-stellig – aber es reicht, wenn man auch nur 1-2 Stellen eingibt. Vorangestellt oder nicht – je nach dem. Ich war mit dem Ergebnis sehr zufrieden.
Health Endpoint eines Services Ich habe eine interessante Problemstellung bearbeitet: Wie könnte ein Health-Endpoint ausschauen? Nach kurzer Recherche im Internet:
the status of the connections to the infrastructure services used by the service instance the status of the host, e.g. disk space application specific logic (Quelle: https://microservices.io/patterns/observability/health-check-api.html)
Meine Meinung dazu:
Punkt 1: zu oberflächlich Punkt 2: aus meiner Sicht sind das Metriken – anderes Thema Punkt 3: ok (obwohl nicht genau gesagt wurde, was hiermit gemeint ist) .NET Core 2.2 hat es native enhalten: Health checks in ASP.NET Core. So wird z.B. bei SQL Server mit SELECT 1 überprüft, ob die Verbindung und die Authentifizierung funktioniert. Wie immer würde ich das gerne mit theoretischen Sichtweisen verschmelzen.
Katalonien hat den Grünen Pass abgeschafft. Da Off-Saison war, hatten wir auch keine Bedenken hinsichtlich Corona. Es war auch nicht sonderlich viel los. In Barcelona die klassischen Sehenswürdigkeiten:
Park Güell Sagrada Família Gute Lokale sind gar nicht leicht zu finden – hier ein paar:
El Nacional Cañete LOKAL BAR Louro - Restaurante do Centro Galego El Bosc de Les Fades An 2 Tagen hieß es wandern. Mit dem Zug „Rodalies“ ging es von hier nach Blanes – ca. 1:30h. Danach folgende Wanderungen:
Dapper Ansich nichts besonderes - trotzdem ein interessantes Thema. Points of Interst:
Wenn die Tabelle leer ist, muss man sich einer Hilfstabelle bedienen Gherkin Tests sollten nicht zu technisch sein - ob man jetzt tasächlich Tabellen prüfen sollte - fraglich. Anderseits ist es ein netter Integrationstest - ob BDD das beste Tool dafür ist, sei dahingestellt. Ich kenne zumindest keinen Business Analysten, der sich solche Tests selber zusammen schraubt. Allerdings kann es beim Verfassen von Akzeptanzkriterien hilfreich sein. using Dapper; using Npgsql; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; namespace PgsqlToGherkinTable { class Program { private static Dictionary<string, string> TableTypes; static void Main(string[] args) { // Check if database has the same one - we assume its allways en-US CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); if (args.Length != 5) { Console.WriteLine($"<server> <database> <schema> <userid> <password>"); Environment.Exit(1); } var server = args[0]; var database = args[1]; var tableSchema = args[2]; var user = args[3]; var password = args[4]; using (var connection = new NpgsqlConnection($"Server={server};Database={database};User Id={user};Password={password};")) { Console.WriteLine($"Connecting to {server} ..."); connection.Open(); using (var streamWriter = new StreamWriter("result.txt")) { Console.WriteLine($"Write result to {((FileStream)streamWriter.BaseStream).Name} ..."); InitializeTableTypes(connection, tableSchema); WriteTableContentsToStream(tableSchema, connection, streamWriter); } } } private static void InitializeTableTypes(NpgsqlConnection connection, string schema) { TableTypes = connection.Query<KeyValuePair<string, string>>("SELECT CONCAT(table_schema, '.', table_name, '.', column_name) AS Key, data_type As Value FROM information_schema.columns WHERE table_schema = @schema", new { schema = schema }).ToDictionary(p => p.Key, p => p.Value); } private static void WriteTableContentsToStream(string tableSchema, NpgsqlConnection connection, StreamWriter writer) { string query = "SELECT table_name FROM information_schema.tables WHERE table_schema = @schema AND table_name <> 'pg_stat_statements' ORDER BY table_name"; foreach (var tableName in connection.Query<string>(query, new { schema = tableSchema })) { writer.WriteLine($"And table \"{tableSchema}\".\"{tableName}\" contains rows:"); var result = connection.Query($"SELECT * FROM {tableSchema}.\"{tableName}\""); var rowsAsList = result.Cast<IDictionary<string, object>>().Select(r => ByPassTypeHandling(r, tableSchema, tableName)); if (rowsAsList.Any()) { var length = new Dictionary<string, int>(); var headerBuilder = new StringBuilder("|"); foreach (var column in rowsAsList.First().Keys) { var maxContentLength = rowsAsList.SelectMany(r => r.Where(c => c.Key == column)).Max(r => r.Value.ToString().Length); var maxLength = Math.Max(maxContentLength, column.Length); length[column] = maxLength; headerBuilder.AppendFormat($" {{0,-{maxLength}}} |", column); } writer.WriteLine(headerBuilder.ToString()); foreach (var rows in rowsAsList) { var rowBuilder = new StringBuilder("|"); foreach (var value in rows) { rowBuilder.AppendFormat($" {{0,-{length[value.Key]}}} |", value.Value); } writer.WriteLine(rowBuilder.ToString()); } } else { var columns = connection.Query<string>("SELECT column_name FROM information_schema.columns WHERE table_schema = @schema AND table_name = @table ORDER BY ordinal_position", new { schema = tableSchema, table = tableName }); var headerBuilder = new StringBuilder("|"); foreach (var column in columns) { headerBuilder.Append($" {column} |"); } writer.WriteLine(headerBuilder.ToString()); } writer.WriteLine(); } } private static IDictionary<string, string> ByPassTypeHandling(IDictionary<string, object> rows, string tableSchema, string tableName) { var result = new Dictionary<string, string>(); foreach (var column in rows) { var type = TableTypes[$"{tableSchema}.{tableName}.{column.Key}"]; if (type == "timestamp without time zone" && column.Value != null) { // ISO 8601 result[column.Key] = DateTime.Parse(column.Value.ToString()).ToString("yyyy-MM-ddTHH:mm:ssZ"); } else if (type == "timestamp with time zone" && column.Value != null) { // ISO 8601 result[column.Key] = DateTime.Parse(column.Value.ToString()).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); } else { result[column.Key] = column.Value == null ? string.Empty : column.Value.ToString(); } } return result; } } }
Wie kann man Daten am besten historisieren? Viele Wege führen nach Rom – ich möchte an dieser Stelle einen beschreiben.
Möglichkeit 1: Man nutzt Frameworks in der Zielsprache. Zu erwähnen wäre Hibernate Envers oder Entity Framework Auto-History bzw. Audit.NET.
Möglichkeit 2: Man lässt das die Persistenz erledigen. Datenbank wie SQL Server unterstützen Temporale Tabellen. PostgreSQL hat Plugins – aber man ist auch gut beraten, selber Hand anzulegen – warum, zeige ich gleich. Temporal Tables sind im SQL:2011 Standard zu finden.
30.11.2021 Wir schreiben nun Ende November. Wir sind in einer sogenannten 4. PCR-Testwelle. Die Testwelle entstand durch das inzwischen gute PCR-Gurgel Angebot. Musste man letztes Jahr noch mehr als 100 EUR löhnen, so genügt es heute zum Bipa zu gehen, Tests zu holen und bei einer Tankstelle oder beim Billa wieder abzugeben.
Trotz des höheren Testaufwands sank die Anzahl der positiven Tests (Y-Achse: Anzahl der positiven Tests in % - Aktualisiert 28.12.2021)
Zu diesem Zweck hab ich mir von Oreilly 3 Bücher heruntergeladen und bin diese überflogen, um die Konzepte zu verstehen. Die in den Büchern vorgestellten Datenbanken verwendeten die sogenannte Gremlin Query Language. Trotzdem muss ich sagen: Ich war skeptisch – wie ich bei sehr vielen NoSQL Datenbanken skeptisch bin. Es gibt sicher einige gute (Cassandra, Redis, …) – aber viele scheinen mir auch klassische Sonntagsprojekte zu sein und haben niemals den Tiefgang und die Professionalität von e.g PostgreSQL.
Dabei kann man diverse Schritte zwischen C# und IL analysieren und auch manipulieren (in Form von Ergänzung). Die Manipulation macht unter anderem bei Aspekt orientierter Programmierung Sinn, wenn man Cross-Cutting-Concerns auslagern will. Das ging bis dato nur mit PostSharp gut. Vor- und Nachteile wurden hier im Detail abgehandelt. Ich habe Roslyn für die Analyse von Source-Code verwendet. Es funktioniert sehr gut. Die Challenge ist eher der Kompilierprozess per API – der ist ein wenig wackelig.
Es war im Jahr 2020. Das Frühjahr war durch Corona geplagt. Daher entschied ich mich für eine Umrundung von Tschechien und anschließend der Slowakei. Beim Wegfahren hat es bereits in Strömen geregnet. Nähe Bärenkopf die erste Pause. Die folgenden 2. Bilder schauten dann nicht mehr so heldenhaft aus.
Gefahren bin ich mit einer Stadler Track II Pro. Fairerweise muss ich sagen:
Der Flug von Wien nach Nizza war mit einem kurzen Stopp in Zürich verbunden. In Nizza bekamen wir ein tolles Mietauto: Toyota Corolla Hybrid – wirklich ein Top-Auto (leider auch der Anschaffungspreis – aber na gut). Von Nizza ging es über eine interessante Bergstrasse nach Castellane. Die Straßen sind nicht ungefährlich: auf einer Seite Fels – auf der andere Seite Abgrund – mit einer kleinen Steinmauer gesichert. Wenn ein ausscherender Bus um die Ecke kommt – spannend.