Montag, 9. Juni 2008

Fallstrick: DateTime.AddXXX()

Ich stolpere immer wieder darüber und wie ich heute gesehen habe, bin ich tatsächlich nicht der einzige, dem es so geht:

Auf einen vorhandenen DateTime soll eine bestimmte Anzahl von Stunden (oder Minuten, Sekunden, ...) addiert werden. Natürlich, kein Problem, dafür bietet der DateTime ja die Methode AddHours(). Ein Aufruf, um auf das aktuelle Datum mit der aktuellen Uhrzeit fünf Stunden zu addieren, sähe demnach so aus:

   1:  DateTime dt = DateTime.Now;
   2:  dt.AddHours(5.0);

Verwendet man jetzt jedoch den Wert von dt weiter, um ihn beispielsweise anzuzeigen, fällt auf, dass die fünf Stunden nicht wie erwartet auf den aktuellen Wert addiert wurden. Der Grund hierfür ist ein ganz einfacher, der einem sogar von IntelliSense angezeigt wird.
Der Aufruf geht einfach deshalb ins Leere, weil er nicht auf den übergebenen DateTime addiert, sondern das Ergebnis der Addition zurück gibt. Irgendwo logisch, es handelt sich ja nicht um einen ref- oder out-Parameter, andererseits ist es dennoch irgendwie verwirrend, da es einfach logisch erscheint, dass auf den Ursprungswert addiert wird.

Wie auch immer, der korrekte Aufruf muss wie folgt aussehen:

   1:  DateTime dt = DateTime.Now;
   2:  dt = dt.AddHours(5.0);

Auf dass ich in Zukunft nicht mehr hierüber stolpern werde!

Vergleich VB.NET und C#

Ich springe in letzter Zeit öfter mal zwsichen VB.NET und C# hin und her, wobei ich ganz klar aus der C#-Ecke komme und mich daher mit der Syntax von VB.NET nicht wirklich auskenne.
Da kommt mir diese Seite mehr als gelegen. Hier wird ein direkter Vergleich angestellt, wie etwas in C# und VB.NET realisiert wird. Danke dafür an meinen Kollegen Sebastian.

Sollte ein Leser des Öfteren zwischen C# und Java wechseln müssen, für den gibt es hier eine ähnliche Übersicht.

Dienstag, 3. Juni 2008

Hashtable in Key Reihenfolge abarbeiten

Ich hatte in einer Hashtable Strings mit einem Integer Wert als Key abgelegt und wollte die nun in numerischer Folge abarbeiten, mein erster gedankenloser Ansatz war etwas umständlich. Mit etwas nachdenken hatte ich einen eleganteren Weg gefunden.

Dazu hab ich ersteinmal die Hashtable in ein generisches Dictionary umgewandelt, dann die Keys in eine generische Liste geschaufelt und dann einfach den Sort angewendet. Natürlich hätte dies auch mit einer Hashtable und einer Arraylist funktioniert, aber da beide in jeder Hinsicht in der Performance den Generics unterlegen sind hab ich das umgeschrieben nach diesem Beispiel:

   1:  ...
   2:  Dictionary<int, string> ht = new Dictionary<int, string>(10);
   3:  ht.Add(1, "hallo");
   4:  ht.Add(2, "welt");
   5:  ht.Add(3, "!");
   6:  ...
   7:             
   8:  List<int> keys = new List<int>(ht.Keys);
   9:  keys.Sort();
  10:   
  11:  foreach (int k in keys)
  12:  {
  13:     System.Console.WriteLine("key = " + k.ToString() + " value = " + (string)ht[k]);
  14:  }
  15:  ...

Das funktioniert dann auch analog mit anderen Datentypen, ggf. muss dann der Comparer implementiert/überladen werden, je nach Geschmack :)

Donnerstag, 29. Mai 2008

Seltsames Verhalten im Event Log

Zur Zeit schreibe ich, wenn es die Zeit und die Laune zulassen, an einem kleinen Tool zum Auslesen des Windows Event Log. Dabei habe ich eine recht interessante Feststellung gemacht.
Zum Auslesen der Einträge einer bestimmten Quelle, die im Event Log registriert ist, habe ich folgendes getan:

   1:  public static EventLogEntryCollection GetEntriesForSource( string logSource )
   2:  {
   3:      EventLog log;
   4:      EventLogEntryCollection entries;
   5:   
   6:      source = logSource;
   7:   
   8:      // EventLog vorbereiten
   9:      log = new EventLog();
  10:      log.Source = logSource;
  11:              
  12:      // EventLog-Einträge auslesen
  13:      entries = log.Entries;
  14:   
  15:      return entries;
  16:  }

Hier tue ich nichts anders, als ein EventLog-Objekt zu erstellen, das, wer hätte es gedacht, den Zugriff auf das Windows Event Log erlaubt.
Anschließend wird die Quelle (Source) gesetzt, deren Einträge ausgelesen werden sollen. Damit ist die Vorbereitung auch schon abgeschlossen und die Einträge innerhalb der Source können in eine EventLogEntryCollection eingelesen werden.

Damit ist das Auslesen aus der gewünschten Quelle auch schon abgeschlossen - dachte ich. Seltsamerweise werden trotz des Setzens der zu lesenden Quelle alle Einträge des Logs, in dem sich die Quelle befindet, in die EventLogEntryCollection geschrieben.

Ich möchte jetzt nicht so weit gehen zu sagen, dass es sich hierbei um einen Fehler im Konzept des Event Log-Handlings handelt, aber für mich ist es nicht logisch, alle Einträge in meine Collection geschrieben werden, obwohl ich - für meine Begriffe - eine Eingrenzung auf eine ganz bestimmte Source vorgenommen habe.

Das Schlimme daran ist nicht die Tatsache an sich, sondern viel eher, dass man sie nicht direkt wahrnimmt. Ich habe beispielsweise in eigenen Tests nie zwei mal hintereinander Quellen aus dem gleichen Log verwendet, so dass mir nie aufgefallen ist, dass alle Quellen des Logs exakt gleich viele (und vor allem die selben) Einträge hat. Das hat erst ein Betatester festgestellt (vielen Dank dafür an tropensturm!).

Die Beseitigung des Problems gestaltet sich glücklicherweise denkbar einfach, so dass in meiner Anzeige jetzt tatsächlich nur noch die erwarteten Einträge zu finden sind. Hierfür wird einfach nachträglich nochmals die Source eingegrenzt.

   1:  if( !entry.Source.Equals( source ) )

Sonntag, 18. Mai 2008

HowTo: DateTimes sortieren im datengebundenen DataGridView

Ich hatte letztens die Anforderung, die Daten eines DataGridView, nach einem Datumswert zu sortieren. Die Daten innerhalb des DataGridView kamen hierbei aus einer Datenbindung an ein DataSet.
Das Sortieren an sich ist hierbei kein Problem, folgender Aufruf ist völlig ausreichend:

   1:  myDataGridView.Sort( myDataGridView.Columns[2], System.ComponentModel.ListSortDirection.Descending);

Bei meinen Datumswerten sah das zuerst auch sehr gut aus, jedenfalls bis zu dem Zeitpunkt, zu dem mir aufgefallen ist, dass der 28.04. weit vor dem 08.05. liegt, obwohl ich ja eigentlich absteigend sortiert habe. Das liegt ganz einfach daran, dass die Daten innerhalb der Spalte als String behandelt und demnach auch als solcher sortiert werden. Hierfür ist das Verhalten absolut korrekt, für meine Zwecke jedoch absolut falsch.

Es muss also irgendwie möglich sein, dem DataGridView mitzuteilen, dass diese Spalte keine Strings enthält, sondern DateTimes. Allerdings ist dies nicht direkt am DataGridView möglich, da zur Design-Zeit für dieses ja noch gar nicht fest steht, welche Daten, geschweige denn in welcher Reihenfolge die Daten vorliegen.

Demnach muss diese Einstellung direkt an der Datenquelle, hier am DataSet, getroffen werden. Auch das ist glücklicherweise mit nur einer Zeile Code zu machen.

   1:  myDataSet.Tables[0].Columns[2].DataType = typeof(DateTime);

Nun liefert die Sortierung der Datumswerte innerhalb des DataGridViews tatsächlich das gewünschte Ergebnis.

Das gleiche Verfahren kann übrigens angewandt werden, wenn innerhalb des DataGridView Bilder angezeigt werden sollen. Normalerweise hätte man nämlich den Namen der verwendeten Klasse - also System.Drawing.Bitmap - in der Spalte stehen, was natürlich nicht wirklich sinnvoll ist. Der folgende Aufruf sorgt dafür, dass das in der Zeile abgelegte Bild korrekt angezeigt wird.

   1:  myDataSet.Tables[0].Columns[0].DataType = typeof(Bitmap);

So klappt es dann auch mit der Anzeige von Bildern.

Donnerstag, 15. Mai 2008

Inline C# for dummys...

Bei einer Transformation (BizTalkMapping (.btm)) in einem Bizalk Projekt, hat man die Möglichkeit mit Hilfe des "Scripting" Functioniden C# (oder besser "Inline C#") einzusetzen. Vielerorts sehr hilfreich, da man etwas speziellere Mappings damit schnell implementieren kann.

Beispiel:

   1:  public string ConvertStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string1"))
   4:          return "1";
   5:      else  if(strInput.Equals("string2"))
   6:          return "2";
   7:   
   8:      return("0");
   9:  }

Benutzt man jetzt aber innerhalb EINER Transformation mehrere "Scripting" Functionide, gibt es eine Eigenart die einem eine schöne Zeit mit Fehlersuche bescherren kann, wenn man diese Eigenart nicht kennt.

Nehmen wir als Beispielt an, dass der Code von oben in unserem ersten Functionied eingebettet ist. Ziehen wir jetzt einen neuen "Scripting" Functionied in unsere Transformation rein und coden dort im "Inline C#" folgenden Code hinein:

   1:  public string ConvertStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string3"))
   4:          return "3";
   5:      else  if(strInput.Equals("string4"))
   6:          return "4";
   7:   
   8:      return("5");
   9:  }

Wir sehen das Ergebniss soll beim ersten Functionied 0, 1 oder 2 und beim zweiten 3, 4 oder 5 sein. Gleich vorweg, es wird nicht korrekt funktionieren.

Der "Fehler" liegt einfach daran das intern die Methoden anhand des METHODENNAMEN nur ein mal angelegt werden. Und da unsere beiden "Inline C#" Methoden den gleichen Namen haben, wird in beiden Fällen die SELBE Methode benuzt (im Normalfall die zuerst angelegte). Das perfide daran ist, da es einfach KEINE Fehlermeldung beim Übersetzen oder Ausführen des Projektes gibt. Es läuft alles durch, es wird aber eben in beiden Fällen die selbe Methode aufgerufen.

Abhilfe schafft das unterschiedliche Vergeben des Methodennamens für die Methoden in den verschiedenen "Scripting" Functionieden. Also sollet unser zweites Codebeispiel so aussehen damit alles funktioniert:

   1:  public string ConvertAnotherStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string3"))
   4:          return "3";
   5:      else  if(strInput.Equals("string4"))
   6:          return "4";
   7:   
   8:      return("5");
   9:  }

Mittwoch, 14. Mai 2008

Custom Pipeline Components Wizard, Toolbox Probleme und Testen

Martijn Hoogendoorn hat einen Wizard geschrieben, der einem viel programmierarbeit abnimmt beim erstellen von custom pipeline components: http://www.codeplex.com/btsplcw

Einfach den Source Code compilieren und im Visualstudio kann man mit einem Rechtsklick auf das Installer Projekt gleich "install" auswählen um den Wizard zu integrieren.

Legt man ein neues Projekt an kann man nun unter BizTalk Projects den BizTalk Server Pipeline Component Wizard auswählen. Dieser erzeugt einem dann ein C# (auf Wunsch auch VB.NET) Projekt wo man nur noch fertig implementieren muss wo ein #error Compilerflag gesetzt wurde.


Ich weiß nicht ob es nur so mir ging, aber ich fand es nicht eindeutig wie man denn nun eine selbst geschriebene Komponente in die Pipeline "hineinbekommt". Alles was ich gefunden habe lief ungefähr immer auf das Selbe hinaus, "einfach reinziehen in die Toolbox" ...das hatte jedenfalls nicht so funktioniert wie ich mir das anfangs dachte ;)

Ersteinmal muss man ein paar Vorraussetzungen schaffen damit es später keine Probleme beim deployen gibt:

1.) Die Komponente muss mit einem key signed sein

2.) Die Komponente sollte im GAC installiert werden

Hat man das muss man in der Toolbox der Pipeline Datei mit Rechtsklick das Menü holen und dort "Choose Items..." auswählen und dort die neue Komponente auswählen. Danach ist sie endlich auch für unsere Custom Pipeline verwendbar.


Zum Testen von Pipelines gibt es übrigens ein nützliches Werkzeug unter "...\Microsoft BizTalk Server 2006\SDK\Utilities\PipelineTools\Pipeline.exe". Damit kann man die .btp Datei direkt oder die .NET Assembly testen ohne zu deployen!!

Montag, 12. Mai 2008

Bugzilla VM

Bugtrackingtools sind bei einem professionellen Einsatz von Softwareenticklung im Team eine nicht zu unterschätzende Hilfe. Wer sich schnell und ohne Probleme solch ein Tool aufsetzen möchte, kann unter angegebenem Link eine VM (Virtuelle Maschine für VMWare) mit vorinstalliertem Bugzilla herunterladen. Die VM braucht ca.450MB auf der Festplatte und nur 128MB des Hauptspeichers. Es sollte also auch auf langsameren Rechnern kein Problem sein, diese nebenher laufen zu lassen.

Download: Bugzilla VM

DataGridView - Vorderste Spalte ausblenden

Heute beschäftigen wir uns mal mit etwas, das eigentlich sehr einfach ist, mich allerdings doch etwas aufgehalten hat, weil einfach das Wissen gefehlt hat. Damit dies nicht wieder passiert, wird hier gebloggt! ;)

Die Aufgabenstellung ist eine extrem einfache:
Verwendet man ein DataGridView zur Anzeige von Daten (z.B. via DataBinding auf ein DataSet), wird standardmäßig ganz links eine Spalte angezeigt, die es ermöglicht, die komplette Zeile zu markieren und durch einen kleinen Pfeil anzeigt, dass eine Spalte die aktuelle ist.
Nun ist in meinem Fall der DataGridView so eingestellt, dass immer die ganze Zeile markiert wird, egal, wo sie angeklickt wird. Daraus folgt, dass wir die besagte erste Spalte nicht benötigen.

Naheliegend wäre es nun ja, dass diese Spalte in der Columns-Collection des DataGridView enthalten ist und sie mit dem Aufruf

   1:  myDataGridView.Columns[0].Visible = false;

unsere Anforderung erfüllt.
Startet man jetzt allerdings die Applikation, wird man feststellen, dass statt der nutzlosen Spalte ganz links die erste eigene Spalte ausgeblendet wird, die unsere Daten anzeigen sollte - so geht es also nicht!

Statt dessen bietet das DataGridView eine Eigenschaft, die festlegt, ob besagte Spalte angezeigt wird oder nicht: DataGridView.RowHeadersVisible
Die Spalte, um die es hier geht, wird also offensichtlich als RowHeader bezeichnet. Reichlich interessant, wie ich finde. Zu meiner Schande muss ich jedoch gestehen, dass ich darauf nie im Leben gekommen wäre. Zum Glück gibt es ja das Internet.

Der korrekte Aufruf zum Ausblenden der ersten Spalte sieht dann also letztendlich so aus:

   1:  myDataGridView.RowHeadersVisible = false;

Die Eigenschaft kann aber natürlich auch einfach im Eigenschaften-Fenster des Designers gesetzt werden.

Problem gelöst, ich bin glücklich und meine GUI gefällt mir wieder. Daumen hoch ;)

Donnerstag, 8. Mai 2008

#error "Errors exist for one or more children."

Ich hatte gerade ein seltsames Problem mit einer Orchestration innerhalb eines VisualStudio BizTalk.Projektes. Ich konnte einfach partout nicht mehr kompilieren, obwohl im Orchestration-Designer alles bestens aussah (der Fehler war in meinem Fall '#error "Errors exist for one or more children."').

Nachdem ich alles was mir eingefallen ist ausprobiert hatte, öffnete ich die Orchestration im XML-Editor (eine Orchestration ist ja nichts weiter als ein XML). Der "normale" Aufbau einer Orchestration folgt folgendem Muster:

   1:  #if __DESIGNER_DATA
   2:   #error Do not define __DESIGNER_DATA.
   3:   <?xml version="1.0" encoding="utf-8" standalone="yes"?>
   4:   <om:MetaModel [...viele Attribute...]>
   5:    [...viel mehr XML...]
   6:   </om:MetaModel>
   7:  #endif // __DESIGNER_DATA

Manchmal findet man jedoch darunter noch Code im folgendem Stil:

   1:  [Microsoft.XLANGs.BaseTypes.BPELExportable(false)]
   2:  module xyz.IndustryTemplate.MSCRMToSAP
   3:  {
   4:   [...viel mehr Code]
   5:  }

In meinem Fall hat es nun geholfen den untenstehenden Code komplett zu löschen und die Orchestration zu speichern. Der erneuter Kompilierungsversucht ist danach fehlerfrei durchgelaufen.