Sparse Matrix in C#

Hin und wieder will man wieder etwas einfacheres machen und etwas, dass es schon 100.000mal auf dieser Welt gibt auch wieder neu erfinden. Ich hoffe ich habe jetzt hier nicht irgendein Softwarepatent gröber verletzt, wenn ja – mea culpa.

Die Sparse-Matrix hat gegenüber einem Array vor allem Vorteile, wenn man nur wenige Elemente der Matrix (bzw. des Arrays) belegt, da wäre eine Array eine Speicherverschwendung (überhaupt bei grossen Dimensionen). Anbei meine einfache Implementierung, ich habe bewusst nur eine einfache .NET Liste mit Tupeln verwendet, zur Abfrage der Liste verwende ich einfach LINQ (es ist einfach zu praktisch…) und hab noch eine kleine API draufgesetzt.

Sicherlich gäbe es auch einen effizientere Datenstrukturen dafür, aber ich denke für die meisten Fälle wird wohl die einfache Liste ausreichend sein, bzw. müsste man dann ja auch noch Vergleichsmessungen anstellen.

Mit dabei sind auch ein paar klitzekleine Tests, natürlich hätte die man auch als Unit-Tests auch ausführen können, aber sooo viel Bock hatte ich dann auch nicht.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SparseMatrix {
  class Program {
    static void Main(string[] args) {

      var sm = new SparseMatrix();

      sm.SetAt(5, 6, "Hallo");
      sm.SetAt(6, 7, "Welt!");

      Console.WriteLine(sm.GetAt(5, 6));
      Console.WriteLine(sm.GetAt(6, 7));

      sm.SetAt(6, 7, "Harald");

      Console.WriteLine(sm.GetAt(6, 7));
      Console.WriteLine("RowCount row=7: {0}", sm.GetRowCount(6));
      Console.WriteLine("ColCount col=6: {0}", sm.GetColumnCount(7));

      Console.WriteLine("Entferne Element auf row=6 / col=7");
      sm.SetAt(6, 7, null);
      Console.WriteLine("RowCount row=7: {0}", sm.GetRowCount(6));
      Console.WriteLine("ColCount col=6: {0}", sm.GetColumnCount(7));

      sm[6, 6] = "Blogleser!";

      List results = sm.GetRowData(6).ToList();
      // Sollte "Hallo Blogleser " ausgeben
      foreach (string s in results) Console.Write("{0} ", s);
      Console.WriteLine();

      Console.WriteLine("Auf Postion 5/6 steht {0}", sm[5, 6]);


      Console.ReadLine();


    }
  }
  
  public class SparseMatrix {
    private List<Tuple<int, int, T>> data = new List<Tuple<int, int, T>>();

    private Tuple<int, int, T> getTuple(int row, int col) {
      return data.Where(e => e.Item1 == row && e.Item2 == col).FirstOrDefault();
    }

    public T GetAt(int row, int col) {
      var e = getTuple(row, col);
      if (e == null) {
        return default(T);
      } else {
        return e.Item3;
      }
    }

    public void RemoveAt(int row, int col) {
      var e = getTuple(row, col);
      if (e != null) data.Remove(e);
      return;
    }

    public void SetAt(int row, int col, T value) {
      var e = getTuple(row, col);
      if (e == null && EqualityComparer.Default.Equals(value, default(T))) {
        return;
      }
      if (e != null && EqualityComparer.Default.Equals(value, default(T))) {
        data.Remove(e);
        return;
      }
      if (e == null) {
        data.Add(new Tuple<int, int, T>(row, col, value));
        return;
      }

      data.Remove(e);
      data.Add(new Tuple<int, int, T>(row, col, value));
    }

    public IEnumerable GetRowData(int row) {
      return data.Where(e => e.Item2 == row).Select(e => e.Item3);
    }

    public IEnumerable GetColumnData(int col) {
      return data.Where(e => e.Item1 == col).Select(e => e.Item3);
    }

    public int GetColumnCount(int col) {
      return data.Where(e => e.Item2 == col).Count();
    }

    public int GetRowCount(int row) {
      return data.Where(e => e.Item1 == row).Count();
    }

    public T this[int row, int col] {
      get {
        return GetAt(row, col);
      }
      set {
        SetAt(row, col, value);
      }

    }


  }
}

 

Visual Studio Lightswitch 2010 – Einen Search Screen bei Änderungen automatisch aktualisieren.

Eine Applikation mit Visual Studio Lightswitch zu machen, ist eine spaßige Angelegenheit, man kommt schnell voran weil man sich nicht um die Infrastruktur der Applikation kümmern muss, aber trotzdem eine schöne, mehrschichtige Anwendung bekommt. Der Anfänger kann leicht einfache Use-Cases umsetzen, die Fortgeschrittenen können sich viel anpassen und auch erweitern.

Ein kleines Manko, welches mir bei der Umsetzung einer Anwendung aufgefallen ist (bzw. deren Anwender):

Man hat eine Listenansicht (SearchScreen) die eine bestimmte Menge an Datensätzen anzeigt. Fügt man nun in einen anderen Bildschirm (z.B. einer Detailansicht (Details Screen) oder einer Maske zum Hinzufügen (Add New Screen)) einen neuen Datensatz zu dieser Menge hinzu, schließt den Bildschirm und kehrt wieder zur Listenansicht zurück, so stellt man fest, dass der neue Datensatz nicht angezeigt wird, erst nachdem man auf die standardmäßig vorhandene Schaltfläche „Aktualisieren“ drückt, erscheinen die neuen Daten.

Ich möchte hier einen Weg vorstellen wie das auch automatisch funktioniert, erfunden habe es aber nicht ich, sondern Sheel Shah vom Visual Studio Lightswitch Team. Den Originalbeitrag findet ihr hier. Die Idee daran ist es, das ganze über einen eigenen EventHandler abzuhandeln. Das heisst, die Applikation bekommt einen Eventhandler verpasst, der Search Screen registriert sich für alle Events dieses Handlers. Wird nun in einem anderen Screen ein Datensatz hinzugefügt, oder verändert, so wird beim speichern dieses Event ausgelöst. Der Search Screen bekommt das Event mitgeteilt und kann damit die Datenquelle aktualiseren. Hier greifen dann die normalen Lightswitch-Mechanismen und die GUI wird aktualisiert.

Der einzige zu Sheel’s Beitrag ist, dass ich bei mir einen Search Screen aktualisiere, und kein einzelnes Steuerelement.

Ausgangslage, die Tabelle mit den Namen „Kunden“:

Tabelle Kunden - Übersicht
Tabelle Kunden

Ausgehend von dieser Tabelle gibt es zum Einem den Search Screen „Kunden“ sowie die Eingabemaske zum Anlegen eines neuen Kunden „Neuer Kunde“

Beginnen wir nur nun mit den Adaptionen an der Eingabemaske „Neuer Kunde“, die erste Änderung muss im Code an der Stelle „KundeNeu_CanRun“ vorgenommen werden.

Bearbeiten von "KundeNeu_CanRun"
Bearbeiten von „KundeNeu_CanRun“

Folgende Anpassungen sind dort zu machen: Anlegen eines Delegaten und eines Eventhandlers, sowie einer Methode die das Event „feuert“.

public delegate void RecordAdded();
public event RecordAdded CustomerAdded;

public void RaiseCustomerAdded() {
  if (this.Details.Dispatcher.CheckAccess()) {
    CustomerAdded();
  } else {
    this.Details.Dispatcher.BeginInvoke(delegate() { CustomerAdded(); });
  }
}

Wir bleiben im Detailformular, jetzt wird etwas Code zum Ereignis „KundeNeu_Saved“ hinzugefügt, die Ergänzung ist einfach, es wird einfach das Event „abgefeuert“:

partial void KundeNeu_Saved() {
  this.Close(false);
  Application.RaiseCustomerAdded();
}

So, wir haben nun den Event-Handler definiert, wir haben dafür gesorgt, dass das Event auch ausgelöst wird, nun brauchen wir noch jemanden, der sich für diese Events auch interessiert, und das ist nun das Suchformular „Kunden“. In dieser Ansicht gibt es oben im Ribbon eine Schaltfläche, mit der man einen Kunden neu anlegen kann – hier registriert man sich für das Event:

partial void NewCustomer_Execute() {
  Application.CustomerAdded += new Application.RecordAdded(CustomerAdded);
  Application.ShowKundeNeu();
}

Das ist die Methode, die dann aufgerufen wird, es wird einfach ein Refresh der Datenquelle vorgenommen, die dem Search Screen zugrundeliegt. Der Rest wird dann vom internen Eventsystem abgehandelt, und die GUI initialisiert.

private void CustomerAdded() {
  this.Details.Dispatcher.BeginInvoke(() => this.CustomersByName.Refresh());
}

Zu guterletzt sollte man auch noch aufräumen, ich denke aber, dass diese Lösung hier noch nicht unbedingt sauber ist, das muss ich mir bei Gelegenheit noch genauer ansehen, wenn der Search Screen geschlossen wird, wird der der Eventhandler wieder „deregistriert“:

partial void Kunden_Closing(ref bool cancel) {
  Application.CustomerAdded -= new Application.RecordAdded(CustomerAdded);
}

Kurzes Fazit: Die Implementierung ist doch ein wenig aufwändig, vor allem wenn man die Mechanismen des Event-Handlings noch nicht so gut versteht, aber es ist eine saubere und zuverlässige Umsetzung.

Parallelinstallation MS SQL Server 2010 Developer & MS SQL Server 2010 Express

Hin und wieder geht man sein Entwicklerleben doch etwas blauäugig an, bzw. glaubt man einfach an das gute im Menschen.

Wenn man Visual Studio installiert, so wird ja gleich die Express Version des MSSQL-Servers mit auf die Platte geknallt. Reicht auch für die meisten Vorkommnisse, aber dann gibts doch Fälle, wo man doch eine unlimitierte Version des Servers braucht. Also schön brav (zumindest dachte ich es mir) zuerst die Express-Edition wegräumen (deinstallieren), um anschliessend gleich die ausgewachsene Developer Edition installiert.

Läuft doch super! Stimmt auch teilweise, nur…

Diese komischen MDF Files für lokale Datenbanken – gehen irgendwie nicht mehr, versteh ich zwar nicht – aber oke, braucht man nicht unbedingt, man ja hat ja eh das grosse Ding auch laufen. Und dann kam LightSwitch – geiles Teil, darin sehe ich ein grosses Potential – wenn es dafür verwendet wird, was es wirklich ist – ein Tool um schnell relativ einfache, datenzentrierte Applikationen zu erstellen. Aber ich schweife ab: Lightswitch geht ohne die Express-Edition nicht. Punkt. Toll!

Ist ja kein Problem, wir installieren doch einfach die Express-Edition wieder einfach dazu! Runtergeladen, installiert – läuft! Zumindest laufen beide Dienste, beide Server-Instanzen sind über das Management-Studio ansprechbar. Toll! Doch funktionieren tuts nicht…

Dann kam an diesen Abend noch ein guter alter Freund vorbei, Google – eine treue Seele… Gemeinsam zogen wir aus den tiefsten Winkeln des Internetzes eine Lösung hervor – zumindest mir hat sie geholfen: http://www.aspdotnetfaq.com/Faq/fix-error-Failed-to-generate-a-user-instance-of-SQL-Server-due-to-a-failure-in-starting-the-process-for-the-user-instance.aspx

Hier das ganze in Deutsch:

Schritt 1: Aktivieren der „User Instances“ des SQL-Express Servers
Öffnen eines Abfrage-Fensters im SQL-Server Management Studios und eintippen folgender Abfrage:

exec sp_configure 'user instances enabled', 1.
Go
Reconfigure

Ausführen der Abfrage und den Server neu starten.

Schritt 2: Löschen nicht mehr benötigter Dateien:
Die Informationen über die alten, nun nicht mehr benötigten „User Instances“ muss verschwinden.

Dazu unter (Windows 7) C:\Users\***BENUTZER***\Application Data\Microsoft\Microsoft SQL Server Data\SQLEXPRESS

Bitte das ***BENUTZER*** mit Deinen Benuzternamen austauschen 😉

Und jetzt sollte das ganze auch mit ASP.NET und mit Lightswitch wieder wunderbar funktionieren.

Mit Lambda-Expressions in C# den Code verkürzen

Lambda-Expressions sind zwar schon ein alter Hut in C#, trotzdem möchte ich auch mal ein kleines Beispiel bringen, wo diese Dinger ungemein praktisch sind. Sie verkürzen nicht nur die LOC sonder tragen meiner Meinung nach ganz wesentlich dazu bei, den Code auch übersichtlich zu behalten.

Ich verwende es um in einem Silverlight WCF-RIA Projekt die Callbacks des asynchronen Webserviceaufrufs entgegenzunehmen. Um den Unterschied zu verdeutlichen, das ganze natürlich in der klassischen „Vorher / Nachher“-Variante

Vorher: Natürlich zuerst einmal ohne Lambdas, 3 Methoden, 17 Zeilen Code:

public override void LoadData() {
  LoadOperation loadOperationCountry = _context.Load(_context.GetCountrySetQuery(), loadOperationCountryCallback, null);
  LoadOperation loadOperationCustomer = _context.Load(_context.GetCustomerSetQuery(), loadOperationCustomerCallback, null);
}

void loadOperationCustomerCallback(LoadOperation customers) {
  if (customers != null) {
    Customers = new ObservableCollection(customers.Entities);
    CurrentCustomer = Customers[0];
  }
}

void loadOperationBranchCallback(LoadOperation branches) {
  if (branches != null) {
    Branches = new ObservableCollection(branches.Entities);
  }
}

Nachher: 11 Zeilen Code, also ca. 1/3 weniger, und nur mehr 1 Methode. Man muss also nicht mehr aufpassen, dass man die Methoden im Quellcode zusammenhält um später mal nicht so lange suchen zu müssen wo denn nu der verdammte Quellcode für den Callback ist.

public override void LoadData() {
  LoadOperation loadOperationCountry = _context.Load(_context.GetCountrySetQuery(), 
    (e) => {
      if (e!=null) Countries = new ObservableCollection(e.Entities);
    }, null);

  LoadOperation loadOperationCustomer = _context.Load(_context.GetCustomerSetQuery(), 
    (e) => { 
      if (e != null) Customers = new ObservableCollection(e.Entities); 
    }, null);
}