Mittwoch, 24. Oktober 2012

SQL Code Snippet: Anzeigen aller durch Fremdschlüssel abhängigen Tabellen

Mit diesem MS SQL-Script ist es möglich zu einer gegebenen Tabelle alle Tabellen zu finden, von denen diese durch Foreign Key Constraints abhängig ist.
declare @TableName nvarchar(50)='TestTable2'
declare @SchemaName nvarchar(10)='dbo'

 select schRef.name+'.'+referenced.name from sys.foreign_keys fk
 join sys.tables referenced on referenced.object_id = fk.referenced_object_id
 join sys.tables base on base.object_id = fk.parent_object_id
 join sys.schemas schBase on schBase.schema_id = base.schema_id
 join sys.schemas schRef on schRef.schema_id = referenced.schema_id
 where base.name=@TableName and schBase.name=@SchemaName
um das ganze jetzt auch noch vollständig ausgeben zu können (inkl. Column), wäre folgendes der Code:
declare @TableName nvarchar(50)='TestTable2'
declare @SchemaName nvarchar(10)='dbo'

  select 
 schRef.name+'.'+referenced.name  ReferencedTable
 , colRef.name      ReferencedColumn
 , schBase.name+'.'+base.name  BaseTable
 ,colBase.name      BaseColumn
 from sys.foreign_keys fk
  -- ref table
 join sys.tables referenced on referenced.object_id = fk.referenced_object_id
 join sys.schemas schRef on schRef.schema_id = referenced.schema_id
 
 -- base table
 join sys.tables base on base.object_id = fk.parent_object_id
 join sys.schemas schBase on schBase.schema_id = base.schema_id
 
 -- columns
 join sys.foreign_key_columns fkc on fkc.constraint_object_id = fk.object_id
 
 join sys.all_columns colBase on colBase.column_id = fkc.parent_column_id and colBase.object_id = base.object_id
 join sys.all_columns colRef on colRef.column_id = fkc.referenced_column_id and colRef.object_id = referenced.object_id


 where base.name=@TableName and schBase.name=@SchemaName

Samstag, 13. Oktober 2012

Große XML Datei in mehrere kleine Dateien aufteilen

Problembeschreibung


Wer viel mit XML-Dateien zu tun hat, kennt vielleicht das Problem - man hat gerade im Programm (oder aus einer Datenbank) ein XML erzeugt, das witzige 50 MB groß ist. Eigentlich in der heutigen Zeit von Terabyte-Festplatten kein großer Wert, sollte man meinen, aber Visual Studio hängt sich beim Öffnen auf. Auch Notepad++ hat hier selbst mit SSD bedenkliche Probleme. Einzig das gute alte Notepad von Windows kann die Datei zumindest anzeigen - das war's aber dann auch, denn damit kann ich nicht wirklich viel anfangen, immerhin sind 50MB auf einer Zeile nicht wirklich übersichtlich.

Lösung

Ich habe mir ein kleines Programm geschrieben, dass ein Eingabe XML in mehrere kleinere XML aufteilt. Die Herausforderung lag darin die Datei nicht komplett einzulesen, sondern Knoten für Knoten zu verarbeiten. Außerdem musste sich das Porgramm merken, welche Knoten noch geöffnet sind, um diese dann zu schließen, sobald die maximale Anzahl an Bytes erreicht wurde. Dadurch ist gewährleistet, dass die resultierenden XML-Dateien auch valides XML repräsentieren.
 

Implementierung

Im Prinzip verwende ich für das Lesen der XML-Datei einen XmlTextReader , das hat den entscheidenden Vorteil, dass die XML-Elemente einzeln eingelesen werden und man XML spezifische Metainformationen zu jedem gelesenen Element erhält. Ich spar mir im Code das kopieren der Kommentare und der XML-Deklaration, wenn das jemand benötigt, ist das schnell zu ergänzen. Außerdem verzichte ich auf die Berücksichtigung von Namespaces.
 
 Der  Programmablauf ist recht einfach:
- Lese jeden Tag und puffere diesen
- öffnende Tags werden auf einen Stack gelegt
- bei schließenden Tags wird der letzte öffnende Tag vom Stack entfernt
- hat der Puffer die gewünschte Größe, so schließe alle noch offenen Tags und schreibe den Puffer auf Platte
- leere Puffer
- öffne die zuletzt offenen Tags wieder im aktuellen Puffer
 
Und hier nun der Code:
 
 
    class XmlUtility
    {
        #region XmlOpeningTag class
        class XmlOpeningTag
        {
            
            public XmlOpeningTag(string name, string fullOpeningTag)
            {
                TagName = name;
                FullOpeningTag = fullOpeningTag;
            }
            public string TagName { get; set; }
            public string FullOpeningTag { get; set; }
            public int ClosingTagSize
            {
                get 
                {
                    return TagName.Length + 3;
                }
            }
        }
        #endregion
        
        /// <summary>
        /// splits the given xml file into smaller parts
        /// </summary>
        /// <param name="pathToXmlFile">the path to the xml file, that is to be splitted</param>
        /// <param name="maxSizeInBytes">app. maximum size of the resulting parts</param>
        /// <param name="resultPath">where to write (path) the resulting files</param>
        /// <returns>list of filepaths, that were created</returns>
        public static List<string> Split(string pathToXmlFile, int maxSizeInBytes, string resultPath=null)
        {
            if (resultPath == null)
                resultPath=System.IO.Path.GetDirectoryName(pathToXmlFile);
            List<string> res = new List<string>();
            // initialize variables
            int currentFileNumber = 1;
            int sizeOfClosingTags = 0;
            string fileWithoutExtension = System.IO.Path.Combine(
                resultPath,               
                System.IO.Path.GetFileNameWithoutExtension(pathToXmlFile));
            StringBuilder output = new StringBuilder();
            Stack<XmlOpeningTag> _openElements = new Stack<XmlOpeningTag>();

            // open the xml file within a xmlreader
            System.Xml.XmlTextReader xmlReader = new System.Xml.XmlTextReader(pathToXmlFile);
            // read from the file, element by element
            while (xmlReader.Read())
            {
                string currentXmlLine = string.Empty;
                XmlOpeningTag newOpeningTag = null;
                switch (xmlReader.NodeType)
                {
                    case XmlNodeType.Element: //its an xml tag element (opening)
                        string xmlElementName = xmlReader.Name;
                        // start with the tag
                        currentXmlLine = "<" + xmlElementName;
                        bool isEmptyElement = xmlReader.IsEmptyElement;
                        // add all attributes
                        while (xmlReader.MoveToNextAttribute()) 
                            currentXmlLine += " " + xmlReader.Name + "='" + xmlReader.Value + "'";
                        // if the element is empty, close it in line
                        if (isEmptyElement )
                            currentXmlLine += "/>";
                        //otherwise just close the tag
                        else
                        {
                            currentXmlLine += ">";
                            newOpeningTag =
                             new XmlOpeningTag(
                                    xmlElementName,
                                    currentXmlLine
                                    );
                            sizeOfClosingTags += newOpeningTag.ClosingTagSize;
                        }
                        break;
                    case XmlNodeType.Text: // text node
                        currentXmlLine = xmlReader.Value;
                        break;
                    case XmlNodeType.EndElement: //end of element tag
                       // pop the last opening tag
                       XmlOpeningTag openingTag = _openElements.Pop();
                       sizeOfClosingTags -= openingTag.ClosingTagSize;
                       currentXmlLine = "</" + xmlReader.Name + ">";
                       break;
                }
                
                // lets see, if we can add this new line
                if (output.Length + sizeOfClosingTags + currentXmlLine.Length >= maxSizeInBytes
                    && xmlReader.NodeType != XmlNodeType.EndElement 
                    // endelements will be added anyways, we cant close the xml 
                    // before adding the endelement
                    )
                {
                    // na we cannot
                    // close all open tags
                    Stack<XmlOpeningTag> helperStack = new Stack<XmlOpeningTag>();
                    while (_openElements.Count > 0)
                    {
                        XmlOpeningTag ol = _openElements.Pop();
                        output.Append("</" + ol.TagName + ">");
                        helperStack.Push(ol);
                    }
                    
                    string filePathToSave = fileWithoutExtension + "." + currentFileNumber + ".xml";
                    res.Add(filePathToSave);
                    System.IO.File.WriteAllText(filePathToSave, output.ToString());
                    currentFileNumber++;
                    // clear the string builder, the old stuff was saved
                    output.Clear();
                    // open the "old open" tags in the new output
                    while (helperStack.Count > 0)
                    {
                        XmlOpeningTag ol = helperStack.Pop();
                        output.Append(ol.FullOpeningTag);
                        _openElements.Push(ol);
                    }

                }

                if (newOpeningTag != null)
                    _openElements.Push(newOpeningTag);
                output.Append(currentXmlLine);
            }
            if (output.Length > 0)
            {
                string filePathToSave = fileWithoutExtension + "." + currentFileNumber + ".xml";
                res.Add(filePathToSave);
                System.IO.File.WriteAllText(filePathToSave, output.ToString());
                currentFileNumber++;
            }
            
            xmlReader.Close();
            return res;
        }
    }

Montag, 8. Oktober 2012

WPF Diagramm zum Anzeigen der CPU Auslastung

Herausforderung

Ziel heute ist es ein eigenständiges UserControl in WPF zu schreiben welches "as is" in jede beliebige Alikation eingebunden werden. Aufgabe des UserControls ist das Anzeigen der Prozessorauslastung als Liniendiagramm.

1. Wie messe ich die aktuelle CPU Auslastung in %

Möglich wird das über den PerformanceCounter aus der Assembly System.Diagnostics
PerformanceCounter cpuCounter = new PerformanceCounter();
cpuCounter.CategoryName = "Processor";
cpuCounter.CounterName = "% Processor Time";
cpuCounter.InstanceName = "_Total";

Console.WriteLine("CPU Auslastung: {0} %", cpuCounter.NextValue());

Das war ja schonmal einfach... Jetzt fehlt nurnoch das Diagramm.

2. Liniendiagramm im XAML

Wie der Name schon sagt, besteht ein Liniendiagramm aus Linien, d.h wir benötigen ein ItemsControl, dass an eine Liste von Start-End-Punkten gebunden wird.
<ItemsControl ItemsSource="{Binding CPUUsagePoints}">
 <ItemsControl.ItemTemplate>
  <DataTemplate>
   <Canvas>
    <Line 
          X1="{Binding Start.X}" Y1="{Binding Start.Y}" 
          X2="{Binding End.X}" Y2="{Binding End.Y}" 
          Stroke="Green" StrokeThickness="0.3"
          >
    </Line>
   </Canvas>
  </DataTemplate>
 </ItemsControl.ItemTemplate>
</ItemsControl>
Da der C# System.Drawing.Point kein Referenzdatentyp, sondern ein Verbunddatentyp ist, benötigen wir eine analoge Klasse, und dazu noch eine Klasse für eine Strecke mit Start-Punkt und End-Punkt. Die Implementierung spar ich mir hier, und zeige lieber das aus dem ViewModel gebundene Property CPUUsagePoints.
public List<Point> _pointList = new List<Point>();
public List<Line> _diagramLines;
/// <summary>
/// diagram lines for binding
/// </summary>
public List<Line> DiagramLines
{
  get
  {
    if (_diagramLines == null)
    {
     _diagramLines = new List<Line>();
     InitDiagramm();
    }
  return _diagramLines;
  }
}

/// <summary>
/// initializes the diagram with f(x)=0
/// and connects the points with lines
/// </summary>
void InitDiagramm()
{
  int granularity = 2;
  for (int i = 0; i < (int)(_parent.RenderSize.Width / granularity); i++)
  {
    Line sep = new Line();
    if (_diagramLines.Count == 0)
    {
      sep.Start = new Point(i * granularity, _parent.RenderSize.Height - 1);
      _pointList.Add(sep.Start);
    }
    else
    {
      sep.Start = _diagramLines.Last().End;
    }

    sep.End = new Point(i * granularity, _parent.RenderSize.Height - 1);
    _pointList.Add(sep.End);

    _diagramLines.Add(sep);
  }
}

Wie man leicht sieht werden die Punkte so initialisiert, dass diese bei einem kartesischen Koordinatensystem die Funktion f(x)=0 abbilden, also eine klassische Nulllinie. Da wir in der Computergrafik ein anderes Koordinatensystem verwenden, müssen wir das Ganze die Y-Achse betreffend noch etwas umrechnen.

So weit, so gut - aber wie sollen wir nun die neuen Werte in das Diagramm "rein" bekommen, dafür gibt es bestimmt einige Möglichkeiten - ich habe mich dafür entschieden, die neuen Werte rechts anzuhängen und die bestehenden Punkte nach links zu verschieben. Abstrakt gesehen, brauche ich hier also 2 Funktionen:
  1. Füge_neuen_Wert_hinzu (prozent)
  2. Schiebe_alle_bisherigen_Punkte_nach_links()
Nicht ganz so abstrakt, aber mindestens genauso einfach wird die Implementierung:
/// <summary>
/// add a new value for the cpu usage
/// </summary>
/// <param name="percent">value in per cent</param>
void AddCpuUsage(int percent)
{
  try
  {
    percent = 100 - percent;
    lock (_pointList)
    {
      ShiftLeft(1);
      Point p = _pointList[_pointList.Count - 1];
      double valueToAchieve = ((float)percent / 100.0) * _parent.RenderSize.Height;
      p.Y = valueToAchieve;
    }
  }
  catch { }
 }

/// <summary>
/// shifts the diagram points left
/// </summary>
/// <param name="offset">the offset, how far to shift</param>
void ShiftLeft(int offset = 1)
{
  for (int i = 0; i < _pointList.Count - offset; i++)
  {
    Point p = _pointList[i];
    Point nextPoint = _pointList[i + offset];
    p.Y = nextPoint.Y;
  }
}
Wie man sieht, habe ich hier etwas geschummelt, ich verschiebe nicht die Punkte und hänge auch nichts hinten an, sondern shifte nur die Y-Werte nach links, und verändere dann den Y-Wert des letzten Punkts. Dadurch habe ich einen entscheidenden Vorteil - ich muss zum einen keine X-Verschiebung der einzelnen Punkte durchführen und zum Anderen kann ich die Liste DiagramLines so wie sie ist beibehalten, das Binding wird automatisch durch ein OnPropertyChanged in der Klasse Point bewerkstelligt.

Und für alle, die das Ganze nochmal praktisch nachvollziehen wollen, hier der Link zur Solution:
Solution CPUUsageVisualizer

Dienstag, 2. Oktober 2012

Event bei der Erstellung von neuen Fenstern

In komplexen Anwendungen ist es manchmal notwendig herauszufinden, wann eine MessageBox erscheint oder ein neues Fenster im aktuellen Prozess erzeugt wird. Oder gar, wann in einem anderen Prozess ein Fenster geöffnet wurde.

Das .Net Framework bringt von Hause aus leider kein solches Event mit, daher müssen wir hier etwas basteln. Die Windows API hat hier alles, was wir brauchen (nähere Infos zu unmanaged APIs unter http://www.pinvoke.net ) Um genau zu sein sind die Funktionen, die wir benötigen in der user32.dll.

EnumDesktopWindows

static private delegate bool EnumWindowsDelegate(IntPtr hWnd, int lParam);

[DllImport("user32.dll")]
static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsDelegate lpfn, IntPtr lParam);

Diese Funktion iteriert durch alle Fenster, die dem Fenster mit dem handle hDesktop untergeordnet sind. Für jede Iteration wird der delegate lpfn aufgerufen. Dadurch können wir uns zu jedem Zeitpunkt eine Liste mit den aktuell offenen WindowHandles aufbauen:
static List<intptr> _currentWindows = new List<intptr>();
IntPtr hwndDesktop = IntPtr.Zero; // current desktop
bool success = EnumDesktopWindows(hwndDesktop, 
                new EnumWindowsDelegate(
                 (hWnd, lparam) => 
                 { 
                   currentWindows.Add(hWnd); 
                   return true; 
                 }), IntPtr.Zero);

GetWindowThreadProcessId

Da wir nicht zwingend alle neu erstellten Fenster melden möchten, sondern wahlweise auch nur die Fenster, die zum aktuellen Prozess gehören,  wird die ProzessID des WindowHandle benötigt. Das können wir folgendermaßen auflösen:
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// get the process id of a window handle
uint lpdwProcessId;
GetWindowThreadProcessId(winHndl, out lpdwProcessId);

GetWindowText

Zu guter Letzt sollten wir neben dem WindowHandle vielleicht noch den Text des Fensters herausfinden, denn mit dem Handle kann man vermutlich im verwalteten Code nicht viel anfangen.

[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
// get the window title
StringBuilder windowName = new StringBuilder(256);
GetWindowText(hWnd, windowName, windowName.Capacity);

Alle Drei zusammen

Der Ablauf des Programms sollte nun eigentlich relativ klar sein (hier ein kurzer Abschnitt des essentiellen Ablaufs als Pseudocode):
erzeuge neuen Thread mit folgendem Code:
Liste bekannteFenster := leere Liste
Liste aktuelleFenster := leere Liste
do
  • aktuelleFenster <- EnumDesktopWindows
  • für alle f in aktuelleFenster , aber nicht in bekannteFenster
    • GetWindowThreadProcessId ?= CurrentProcess.Id
    • -> Raise WindowCreatedEvent(f, GetWindowText(f))
while (true)

Und für alle, die es gleich mal ausprobieren wollen, hier mein Implementierungsvorschlag:

static class ChildWindowOpenHook
{
  #region private fields / delegates
  private delegate bool EnumWindowsDelegate(IntPtr hWnd, int lParam);
  static private Thread _workerThread = null;
  static List _currentWindows = new List();
  static List _knownWindows = new List();
  static bool _isRunning = true;
  static bool _onlyOwnWindows = false;
  #endregion 

  #region DLLImport
  [DllImport("user32.dll", SetLastError = true)]
  static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsDelegate lpfn, IntPtr lParam);
  
  [DllImport("user32.dll", SetLastError = true)]
  static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
  
  [DllImport("user32.dll")]
  private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
  #endregion

  #region Starter
  /// 
  /// C'tor
  /// starts the enumeration thread
  /// 
  public static void Start(bool onlyOwnWindows)
  {
   _onlyOwnWindows = onlyOwnWindows;
   // 1. start enumerating the first time
   EnumerateWindows();
   // 2. copy those current windows in out old window list
   _knownWindows.AddRange(_currentWindows);
   // 3. start the enumeration thread, which will enumerate through the
   // current windows periodically
   ThreadStart tStart = new ThreadStart(ThreadRunner);
   _workerThread = new Thread(tStart);
   _workerThread.Name = "window watcher thread";
   _workerThread.Priority = ThreadPriority.BelowNormal;
   _workerThread.IsBackground = true; // important, so the mainthread will not wait on this one

   _workerThread.Start();
  }
  #endregion

  #region ThreadRunner
  /// 
  /// Thread Runner 
  ///  - iterates through all desktop windows
  ///  - fires new window event, when a new window has been detected
  /// 
  static private void ThreadRunner()
  {
   try
   {
    while (_isRunning)
    {
     // clear the current window list
     _currentWindows.Clear();
     // populate the currentwindow list
     EnumerateWindows();
     // find out which windows are not known already
     foreach (IntPtr winHndl in _currentWindows.Where(hndl => !_knownWindows.Contains(hndl)))
     {
      // add the windowhandle of a "NEW WINDOW" to our list of known windows
      _knownWindows.Add(winHndl);
      
      // get the process id of that new window
      uint lpdwProcessId;
      GetWindowThreadProcessId(winHndl, out lpdwProcessId);
      // find out if its on the same process, as we are
      if (
       !_onlyOwnWindows
       || (uint)System.Diagnostics.Process.GetCurrentProcess().Id == lpdwProcessId)
      {
       // raise the event
       OnWindowCreated(winHndl);
      }

     }
     Thread.Sleep(500);
    }


   }
   catch (Exception aException)
   {
    // write down the exeption that occured
    Console.WriteLine("exception in thread:" + aException);
   }
  }
  #endregion

  #region EnumerateWindows
  static private void EnumerateWindows()
  {
   IntPtr hwndDesktop = IntPtr.Zero; // current desktop
   bool success = EnumDesktopWindows(hwndDesktop, 
    new EnumWindowsDelegate(
     (hWnd, lparam) => 
     { 
      _currentWindows.Add(hWnd); 
      return true; 
     }), IntPtr.Zero);

   // if we have no success, lets throw an error
   if (!success)
   {
    // get the last win32 error code
    int errorCode = Marshal.GetLastWin32Error();
    string errorMessage = string.Format("EnumDesktopWindows failed, error code: {0}.", errorCode);
    throw new Exception(errorMessage);
   }
  }
  #endregion

  #region Stopper
  /// 
  /// Stops the ChildWindowOpenHook
  /// 
  public static void Stop()
  {
   _isRunning = false;
   _workerThread.Abort();
  }
  #endregion

  #region Event Handling
  /// 
  /// delegate for WindowCreated event
  /// 
  /// name of the window, that has been created
  /// window handle
  public delegate void WindowCreatedEventHandler(string windowName, IntPtr hWnd);
  
  /// 
  /// handles window created events
  /// 
  public static event WindowCreatedEventHandler WindowCreated;

  /// 
  /// being called, when a window has been created
  /// 
  /// the window handle
  private static void OnWindowCreated(IntPtr hWnd)
  {
   if (WindowCreated!=null && _isRunning)
   { 
    // get the window title
    StringBuilder windowName = new StringBuilder(256);
    GetWindowText(hWnd, windowName, windowName.Capacity);
    // handle the event
    WindowCreated(windowName.ToString(), hWnd);
   }
  }
  #endregion
}

Man kann sich folgendermaßen an das event hängen:
public MainWindow()
{
 InitializeComponent();
 ChildWindowOpenHook.Start(true);
 ChildWindowOpenHook.WindowCreated += ChildWindowOpenHook_WindowCreated;
}

void ChildWindowOpenHook_WindowCreated(string windowName, IntPtr hWnd)
{
 Console.WriteLine(windowName);
}