Archiv für den Monat März 2013

SQL Transaktion inkl. Rollback (Skript)


Transkationen bieten die Möglichkeit eine Datenbank wieder in den Zustand vor dem Ausführen eines z.B. Fehlerhaften SQL Skriptes zurückzusetzten.

Wenn man z.B. eine Abfolge von SQL Befehlen auf einer DB ausführt an der man an unterschiedlichen Tabellen Änderungen vornimmt und beim Update ein Fehler auftritt, dann hat man die Möglichkeit mit Hilfe von Transkationen wieder den Ausgangszustand der DB vor dem Ausführen des Skriptes wieder herzustellen.

Ich verwende dazu das folgende Script, das Script führt bei einem Fehler automatisch ein Rollback der Transaktion durch, funktioniert auch noch für den SQL Server 2005.

 BEGIN TRY
  BEGIN TRAN

 /* SQL CODE der ausgeführt werden soll HIER einfügen */
  
  COMMIT;
 END TRY
 BEGIN CATCH
  /* Fehlerauswertung und der Rollback bei einem Fehler */
  if @@TRANCOUNT > 0
  BEGIN
	ROLLBACK TRAN
  END	

  DECLARE @ErrorMessage VARCHAR(4000)
  SELECT @ErrorMessage = 'Message: '+ ERROR_MESSAGE(); 
  raiserror (@ErrorMessage,16,1)
 END CATCH
Advertisements

HTML5 Canvas – Timeline Diagramm erstellen


Da ich für ein privates Webprojekt ein Diagramm benötige um verschiedene Abläufe eines Tages abzubilden habe ich mir die Möglichkeiten angeschaut wie man über HTML5 Bilder/Animationen darstellen kann. Dabei standen mir zwei Technologien zur Verfügung.

1. HTML5 SVG Element (Scalable Vector Graphics)

Bei dieser Technologie kann man direkt auf bereits bestehende Mausevents zurückgreifen um z.B. Animationen oder Click Events abzubilden. Denn hier wird jede Linie (Grafikelement) im DOM unseres aktuellen HTML Dokuments abgelegt und kann daher auch recht “einfach” mit Hilfe von JavaScript manipuliert werden. Hier besteht aber ein Problem bei komplexen Diagrammen, da alle Elemente im DOM abgelegt, kann es hier sehr schnell zu Performance Problemen kommen.

2. HTML5 Canvas Element

Hier handelt es sich um eine einfache Fläche der Größe X mal Y auf der man mit Hilfe einfacher Befehle Linien, Texte, einfache Formen und Kurven zeichnen kann. Hier stehen keine nativen Events zur Verfügung, denn wenn man etwas zeichnet, dann weiß das Element nach dem Zeichnen der Linie nicht mehr das es da eben eine Linie gezeichnet hat. Wenn man hier Events einbinden möchte muss man diese in Handarbeit selbst erstellen oder auf bestehende JavaScript Libraries wie kinectJs zurückgreifen die diese Events bereits “handlich” in ein Framework verpackt haben.

Ich habe mich für das Canvas Elemente zur Umsetzung meines Timeline Diagramm entschieden und da ich hier keine Animationen benötige, sondern nur Elemente zeichnen möchte, habe ich auch keine zusätzliche Bibliothek verwendet.

Umsetzung des Timeline Diagramms mit Canvas

Das folgende Ergebnis wollen wir am Ende durch das Zeichnen erreichen.

image

Ein wichtiger Aspekt beim Zeichnen mit Canvas und Linien/Rechtecken ist das hier standardmäßig eine “Art Antialiasing” stattfindet, d.h. da wir mit einer 1px breiten Linien zeichnen und wir nicht aufpassen, dann kann eine Linie die 1px breit sein soll aussehen wie eine 2px breite verschwommene Linie. Im Folgenden Beispiel sind beide Linien im Original 1px breit.

image

der passende JavaScript Code für die beiden Linien:

//Das Canvas Element heraussuchen
var canvas = document.getElementById('timeCanvas2'),
//Den aktuellen Kontext ermitteln
ctx = this.canvas.getContext('2d'); 

ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.stroke();

ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(10, 15.5);
ctx.lineTo(100, 15.5);
ctx.stroke();

Der einzige Unterschied ist, dass wir die zweite Linie die auch wirklich nur 1px breit gezeichnet wird nicht auf 15.0 zeichnen sondern auf 15.5, d.h. alle Linien die ich im Diagramm zeichne werden, egal ob auf der X-Achse oder Y-Achse immer auf X.5 oder Y.5 gezeichnet, um das “Antialiasing” zu umgehen.

1. Zeichnen des Diagramms

Als erstes benötigen wir eine Funktion die uns das Diagramm zeichnet und alle veränderbaren Variablen bereitstellt. Ich habe die einzelnen Funktionen zum Zeichnen in Prototype Funktionen “ausgelagert”.

image

Basisklasse “TimeDiagram” für diese werden dann die passenden prototype Funktionen erstellt. Ich habe versucht alle Optionen dementsprechend zu beschriften. Die Diagrammeinträge (diagramTypes) können beliebig erweitert werden (die Legende passt sich dann automatisch an).

//Zeitdiagramm Contextvariablen zum Anpassen.
function TimeDiagram() {
    //Die unterschiedlichen Diagrammtypen mit den passenden Farben und Legendennamen
    this.diagramTypes = {
        Work: { value: 0, name: 'Arbeitszeit', gradStart: '#5BC0DE', gradEnd: '#339BB9' },
        Pause: { value: 10, name: 'Pause', gradStart: '#FBB450', gradEnd: '#F89406' },
        Holiday: { value: 20, name: 'Urlaub', gradStart: '#EE5F5B', gradEnd: '#C43C35' },
        Project: { value: 30, name: 'Projektzeit', gradStart: '#62C462', gradEnd: '#57A957' }
    },
    //Die Beschriftung der X-Achse
    this.times = ['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00', '08:00',
                  '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00',
                  '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'],
    this.fontsize = 10, //Die Schriftgröße für die Beschriftung
    this.leftRightBorder = 20, //Der Abstand zum linken und rechten Rand des Diagramms
    this.topBottomBorder = 20, //Der Abstand zum oberen und unteren Rand des Diagramms
    this.diagramColor = '#ccc', //Farbe des diagramms
    this.ctx = undefined, //Der Aktuelle Kontext, der dieser wird in DrawDiagramm gesetzt.
    this.canvWidth = undefined,
    this.canvHeight = undefined,
    this.diagramEntryHeight = 15, //Die Höhe der Zeiteinträge im Diagramm
    this.diagramEntryOffset = 3, //Der Abstand zwischen den Balken im Diagram
    this.diagramStartPosition = 10, //Die Startposition der Balkeneinträge im Diagramm
    this.LegendYPos = 10, //Die Y-Position unserer Legende (Achtung 0,0 liege Links Oben)
    this.LegendXPos = 10, //Die X-Position unserer Legende
    this.ctxAlpha = 0.75;  //Die globalen Alpha Einstellungen
};

Dan wird eine Funktion zum Zeichnen einer Linie benötigt, hier habe ich die Standardbefehle vom Canvas noch einmal in eine Funktion verpackt.

//Funktion zum Zeichnen einer Linine
TimeDiagram.prototype.drawLine = function (ctx, startX, startY, endX, endY) {
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(endX, endY);
    ctx.stroke();
};

Im Folgenden dann die Prototype Funktion zum Erstellen des Diagramms, hier werden die einzelnen Achsen erstellt, diese werden anhand der Größe des Canvas Elements berechnet und entsprechend erstellt. Es muss vor allem auf “leftRightBorder” geachtet werden, das der Abstand auch überall mit einberechnet wird. Außerdem verwende ich “Math.round(Zahl) + 0.5” um das Antialiasing Problem zu umgehen.

//Funktion zum Erstellen des Diagramms
TimeDiagram.prototype.drawDiagram = function (canvasId) {
    //Achtung wir müssen hier die Reihenfolge einhalten, das auch alle Variablen Initialisiert sind.
    //Erst in der drawDiagram Funktion wird der Context und das Canvas geladen.
    this.canvas = document.getElementById(canvasId); //Das Canvas Element heraussuchen
    this.ctx = this.canvas.getContext('2d'), //Den aktuellen Kontext ermitteln
    this.canvWidth = this.canvas.width, //Die Breite unseres Canvas bestimmen
    this.canvHeight = this.canvas.height; //Die Höhe unseres Canvas bestimmen

    var count = 0,
        //Den abstand zwischen den Elementen anhand der Breite des Canvas bestimmen und ACHTUNG das hier die Border ebenfalls mit abgezogen wird.
        abstand = (this.canvWidth - 2 * this.leftRightBorder) / (this.times.length), 
        quarter = Math.round(abstand / 4), //Den Abstand für eine Viertelstunde berechnen
        textPos = this.canvHeight - (this.topBottomBorder / 2) + 3; //Die Textposition für die Uhrzeit bestimmen

    this.ctx.font = 'normal ' + this.fontsize + 'px Calibri';
    this.ctx.strokeStyle = this.diagramColor;
    this.ctx.lineWidth = 1;

    //Die X-Achse zeichnen
    this.drawLine(this.ctx, this.leftRightBorder - 5, this.canvHeight - this.topBottomBorder, this.canvWidth - this.leftRightBorder, this.canvHeight - this.topBottomBorder);
    var counter = 0;
    //Y-Achsen erstellen Achtung hier muss bei den "i" Werten x-Position darauf geachtet werden das 0.5 dazu addiert wird damit das Antializing "deaktiviert" wird.
    for (var i = this.leftRightBorder; counter++ < this.times.length; i = i + abstand) {
        //Der wert für i wird zwar gerundet, sollte aber nicht direkt auf i passieren, damit der Wert nicht bei jedem Durchgang weiter verändert wird.
        var yPos = Math.round(i) + 0.5;
        //Stundenlinien erstellen
        this.drawLine(this.ctx, yPos, this.canvHeight - this.topBottomBorder + 5, yPos, this.topBottomBorder),
        //Erstellen der Virtelstundenanzeigen
        this.drawLine(this.ctx, yPos + quarter, this.canvHeight - this.topBottomBorder + 3, yPos + quarter, this.canvHeight - this.topBottomBorder - 3),
        this.drawLine(this.ctx, yPos + 2 * quarter, this.canvHeight - this.topBottomBorder + 5, yPos + 2 * quarter, this.canvHeight - this.topBottomBorder - 5),
        this.drawLine(this.ctx, yPos + 3 * quarter, this.canvHeight - this.topBottomBorder + 3, yPos + 3 * quarter, this.canvHeight - this.topBottomBorder - 3),
        //Uhrzeit anzeigen unterhalb der X-Achse
        this.ctx.fillText(this.times[count++], yPos - this.fontsize, textPos);
    }
};

Das Diagramm erstellen wir, in dem man ein neues Objekt von unserem TimeDiagram anlegt und drawDiagram aufruft.

var diagram = new TimeDiagram();
//Das Diagramm zeichnen
diagram.drawDiagram('timeCanvas');

2. Zeichnen der Diagrammdaten

Nachdem wir das Diagramm erfolgreich erstellt haben, muss dieses mit Daten gefüllt werden und sieht dann z.B. folgendermaßen aus:

image

Damit wir die passenden Daten an unsere Funktion zum Erstellen von Diagrammeinträgen übergeben können, habe ich noch einen Datentyp angelegt “TimeEntry”:

//Ein Zeiteintrag für das Diagramm
function TimeEntry(startMinutes, endMinutes, diagramType) {
    this.startMinutes = startMinutes, //Die Startzeit in Minuten von 00:00
    this.endMinutes = endMinutes, //Die Endezeit in Minuten von 00:00
    this.type = diagramType; //Der Typ des Eintrags 'diagramTypes'
}

Die prototype Funktion “drawTimeEntries” für unser TimeDiagram kann z.B. mit den Folgenden Werten aufgerufen werden:

var entries = [new TimeEntry(45, 240, diagram.diagramTypes.Work), new TimeEntry(120, 600, diagram.diagramTypes.Project),
               new TimeEntry(820, 960, diagram.diagramTypes.Project), new TimeEntry(320, 1140, diagram.diagramTypes.Work),
               new TimeEntry(520, 1200, diagram.diagramTypes.Holiday), new TimeEntry(60, 140, diagram.diagramTypes.Pause)];
//Die Zeiteinträge zeichnen.
diagram.drawTimeEntries(entries);

Wir übergeben dabei eine Liste (Array) mit TimeEntries an unsere “drawTimeEnties” Funktion. Wichtig ist hier das Einträge mit dem gleichen Diagrammtyp z.B. “Project” immer auf der gleichen Ebene (Y-Position) erstellt werden, damit sich die Einträge nicht überlagern.

//Funktion zum Erstellen der Zeitblöcke
TimeDiagram.prototype.drawTimeEntries = function (timeEntries) {
    var yPosByType = [],
        //Die YPostion für den übergebenen Typen ermitteln, wenn diese noch nicht vorhanden ist
        //wird der Typ der Listehinzugefügt.
        addYposType = function (type, index) {
            var diagramIdx = index;
            for (var i = 0; i < yPosByType.length; i++) {
                //Wenn der Typ gefunden wurde, dann die passende Position zurückgegeben.
                if (yPosByType[i].typ === type) {
                    return yPosByType[i].pos;
                }
            }

            var newPos = (yPosByType.length * (diagramIdx.diagramEntryHeight + diagramIdx.diagramEntryOffset));
            //Der Typ ist noch nicht enthalten, also muss er hinzugefügt werden
            yPosByType.push({ typ: type, pos: newPos });
            return newPos;
        };

    //Für jeden Eintrag das passende "viereck" zeichnen
    for (var i = 0; i < timeEntries.length; i++) {
        //Berechnen bei welchem Pixel das Zeichnen beginnen soll dafür werden die Minuten für einen Tag 1440 als 100% Maß genommen. 
        //ACHTUNG hier muss die Border von der Gesamtbreite abgezogen werden
        var startPix = Math.round(((this.canvWidth - 2 * this.leftRightBorder) * timeEntries[i].startMinutes / 1440) + this.leftRightBorder) + 0.5,
            //Beim Ende eines Eintrags, berechnen wir als erstes die Gesamtlänge vom Start des Diagramms, hier muss dann die Startzeit abgezogen werden um das tatsächliche Ende zu erhalten.
            endPix = Math.round(((this.canvWidth - 2 * this.leftRightBorder) * (timeEntries[i].endMinutes - timeEntries[i].startMinutes) / 1440)) + 0.5,
            //Die yPos wir anhand der X-Achse bestimmt und da wir von Links oben "zählen" mit 0,0 müssen hier die Werte entsprechend berechnet werden.
            //Außerdem muss die Position je nach dem welcher entryType angezeigt werden soll um Y Pixel verschoben werden, mit 'addYposType'
            yPos = (this.canvHeight - this.topBottomBorder - this.diagramStartPosition - this.diagramEntryHeight - addYposType(timeEntries[i].type, this)) + 0.5;

        //Erstellen Des Arbeitszeit eintrags mit den passenden Position und Farbe
        this.drawRectangle(this.ctx, startPix, yPos, endPix, this.diagramEntryHeight,
            timeEntries[i].type.gradStart, timeEntries[i].type.gradEnd);
    }
};

Es wird eine extra Funktion die das Erstellen eines Rechtecks kapselt benutzt um die Anzahl der Zeilen zu reduzieren, außerdem nutze ich noch einen Lineargradienten um die Balken “schöner” zu machen.

//Funktion zum Zeichnen eines Rechtecks
TimeDiagram.prototype.drawRectangle = function (ctx, startX, startY, width, height, gradStart, gradEnd, borderColor, borderWidth) {
    //Zeichnen eines Diagrammeintrags/Rechtecks
    ctx.globalAlpha = this.ctxAlpha;
    ctx.beginPath();
    //Erst das Rechteck zeichnen
    ctx.rect(startX, startY, width, height);
    //Dann im Rechteck den passenden Lineargradienten für die Farben einfügen
    var grd = ctx.createLinearGradient(startX, startY, startX, startY + height);
    grd.addColorStop(0, gradStart);
    grd.addColorStop(1, gradEnd);
    this.ctx.fillStyle = grd;
    this.ctx.fill();

    //Prüfen ob die Parameter auch der Funktion übergeben wurden.
    if (borderColor !== undefined && borderWidth !== undefined) {
        ctx.lineWidth = borderWidth;
        ctx.strokeStyle = borderColor;
        ctx.stroke();
    }
};

3. Hinzufügen einer Legende

Damit man auch weiß was auf dem Diagramm dargestellt wird, wird eine passende Legende benötigt. Die Position der Legende kann über die Optionen im TimeDiagram eingestellt werden (LegendYPos und LegendXPos).

image

Für das Erstellen der Legende wird erneut das Array mit den TimeEntries mit übergeben, damit wir wissen welche Typen auch auf dem Diagramm angezeigt werden sollen und welche nicht in der Legende dargestellt werden sollen.

//Erstellen einer Legende für unser Diagramm
TimeDiagram.prototype.drawLegend = function (timeEntries) {
    //Prüfen ob der Typ der übergeben wurde in unserer Zeitliste der Einträge die angezeigt werden enthalten ist.
    var checkTimeEntriesForType = function (typeToCheck) {
        for (var i = 0; i < timeEntries.length; i++) {
            //Nur wenn der Typ gefunden wurde, dann wird dieser auch angezeigt.
            if (timeEntries[i].type === typeToCheck) {
                return true;
            }
        }
        return false;
    },
        //Zählt wie viele unterschiedliche timeEntries/diagramTypes in der übergebenen Liste enthalten sind.
        countTimeEntryTypes = function () {
            var anz = [];
            for (var i = 0; i < timeEntries.length; i++) {
                //Prüfen ob der Typ schon in unserer Liste enthalten ist.
                if(anz.indexOf(timeEntries[i].type) === -1) {
                    anz.push(timeEntries[i].type);
                }
            }
            //Die anzahl der Listeneinträge zurückgegeben
            return anz.length;
        };

    //Die Umrahmung und den Hintergrund für unsere Legende festlegen
    this.drawRectangle(this.ctx, this.LegendXPos - 2.5, this.LegendYPos - 2.5, 125, countTimeEntryTypes() * 15, '#F5F5F5', '#F5F5F5', '#ccc', 1);
    //Die yPos setzten, da diese verändert wird darf nicht this.LegendYPos verwendet werden.
    var yPos = this.LegendYPos;

    //Alle Properties unserer Diagrammtypen durchgehen und mit den Werten in den Zeiteinträgen vergleichen, damit wir wissen welche auch im Diagramm angezeigt werden.
    for (var prop in this.diagramTypes) {
        //Wenn es sich um ein Property handelt, welches zu unserem Diagrammtyp gehört, prüfen ob dies ebenfalls in timeEntries enthalten ist.
        if (this.diagramTypes.hasOwnProperty(prop) && checkTimeEntriesForType(this.diagramTypes[prop])) {
            //Die Farbe unseres Eintrags an der richtigen Stelle erstellen
            this.drawRectangle(this.ctx, this.LegendXPos, yPos, 10, 10,
                this.diagramTypes[prop].gradStart, this.diagramTypes[prop].gradEnd);

            this.ctx.fillStyle = 'black';
            //Den Namen unseres Eintrags neben die Farbe schreiben
            this.ctx.fillText(this.diagramTypes[prop].name, this.LegendXPos + 15, yPos + 9);

            //Die nächste Position berechnen für den nächsten Eintrag.
            yPos = yPos + 15;
        }
    }
};

Aufruf zum Zeichnen unserer Legende:

 //Erstellen der Legende
 diagram.drawLegend(entries);

4. Zusammenfassung

Zum einfachen Zeichnen eines Diagramms benötigt man für das Canvas Element noch keine erweiterte JavaScript Bibliothek, hier ist nur ein einfaches Mathematisches Verständnis notwendig um die passenden Linien an die richtige Stelle zu zeichnen. Da ich das erste mal mit Canvas gearbeitet habe, kann ich leider nicht beurteilen ob ich es mir an einer Stelle besonders schwierig gemacht habe oder ob man das ganze anders angeht. Für Tipps bin ich immer offen.

Quelle:

http://www.html5canvastutorials.com/tutorials/html5-canvas-tutorials-introduction/

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier dann die Wichtigste Datei im Webprojekt unter “Views/Home/Html5Canvas.cshtml”. Das Projekt sollte auch so lauffähig sein.

VS 2012 Fake DLLs für Tests (stubs und shims)


Aktuell hatte ich das “Vergnügen” mich mit der Unit Test Testbarkeit eines Projektes auseinander zu setzten. Da ich bisher nur mit Oberflächentests (Coded Ui) zu tun hatte, war eine Recherche angesagt und hier bin ich über diverse Mocking Frameworks wie z.B. “Moq” gestoßen die für Unit Tests zwingend benötigt werden. Außerdem gibt es ein neues Visual Studio CTP Update 2 hier steht jetzt ein eigenes Framework zum erstellen von Fake DLLs für die Visual Studio Versionen 2012 Premium und Ultimate zur Verfügung, diese Fake DLLs können ergänzend zu den Mocking Frameworks eingesetzt werden um Units Tests zu erstellen.

Im Folgenden will ich nur meinen kurzen Ausflug zum Erstellen eines Fake HttpContextes mit shims von MS Zeigen, was ich ziemlich Cool fand. Kommt bei mir aktuell leider nicht zum Einsatz, aber ein kurzes Beispiel schadet nicht.

Um eine Fake DLL zu erstellen muss die Referenz zum aktuellen Testprojekt hinzugefügt werden und dann mit der Rechten Maustaste auf der jeweiligen Referenz kann eine Fake Bibliothek angelegt werden.

image

Wenn man dann z.B. im Programm den Zugriff auf den HttpContext nutzt.

  public static CurrentSession GetSession()
    {
        if (HttpContext.Current.Session["MySession"] == null)
        {
            HttpContext.Current.Session["MySession"] = new CurrentSession();
        }

        return (CurrentSession)HttpContext.Current.Session["MySession"];
    }

Und in einem Unit Test auf den HttpContext zugegriffen wird, dann existiert der in den Unit Tests nicht und muss “Simuliert” werden. Dazu können wir jetzt unsere Fake DLL von “System.Web” Nutzen. Ich habe hier eine Statische Funktion erstellt, da ich auf das gleiche Konstrukt noch öfter zugreifen muss.

public static void LoadCurrentSession()
{
     var session = new System.Web.SessionState.Fakes.ShimHttpSessionState();
     session.ItemGetString = key =>
     {
           if (key == "MySession")
           {
               return new CurrentSession();
            }
           return null;
      };

      var context = new ShimHttpContext();
      ShimHttpContext.CurrentGet = () =>
      {
         return context;
      };

       ShimHttpContext.AllInstances.SessionGet = p => { return session; };
}

Das Ganze muss dann im Test z.B. so aufgerufen werden:

using (ShimsContext.Create())
{
     LoadCurrentSession()
     
     //Test durchführen in dem z.b. auf den HttpContext.Current.Session zugefriffen wird,
     //Dann wird hier unser Fake Kontext verwendet.
}

Das Ganze geht auch wenn man auf die Items des aktuellen HttpContexts zugreifen will.

public static GetContextData()
{
    if (HttpContext.Current.Items["MyItem1"] == null)
    {
        HttpContext.Current.Items.Add("MyItem1", "Hallo");
    }

    return (string)HttpContext.Current.Items["MyItem1"];
}

Die passende Hilfsfunktion für den Test würde dann folgendermaßen aussehen

public static void LoadContextItem(string itemName)
{
      var context = new ShimHttpContext();
      ShimHttpContext.CurrentGet = () =>
      {
           return context;
       };

        ShimHttpContext.AllInstances.ItemsGet = (p) => { return new ListDictionary() {{ itemName, „blubb“}}; };
}

Wenn man dann im Test wieder auf den Kontext zugreift und sein Item haben möchte, dann kann man hier wieder folgendermaßen darauf zugreifen.

using (ShimsContext.Create())
{
    LoadContextItem("MyItem1");
    
    //Test durchführen und auf das Passende Item zugreifen

}

Quellen:

http://msdn.microsoft.com/en-us/library/hh549175.aspx

http://blogs.msdn.com/b/visualstudioalm/archive/2013/02/11/february-ctp-for-visual-studio-update-2.aspx

http://blog.christopheargento.net/2013/02/02/testing-untestable-code-thanks-to-ms-fakes/

C# Bitmaske mit Enum (Flags Attribute)


Bisher habe ich noch nie Gebrauch von einer Bitmaske gemacht, aber heute musste ich mich kurz mit der Thematik auseinandersetzten und wollte hier kurz zeigen was zu welchem Ergebnis führt, wenn man mit einer Bitmaske und einem Enum mit dem “Flags” Attribut arbeitet.

Als Beispiel habe ich mir ein Enum mit den Wochentagen Montag bis Sonntag erstellt, hier darf das Attribut “Flags” nicht vergessen werden, denn nur dann kann mit den Werten Bitweise gerechnet werden. (Was Bitweise genau bedeutet findet man im Netz genug Beispiele.)

[Flags]
public enum WeekDays
{
    Mon = 1,
    Tue = 2,
    Wed = 4,
    Thu = 8,
    Fri = 16,
    Sat = 32,
    Sun = 64
}

Ich werde im Folgenden einfach ein paar Beispiele Zeigen wie man z.B. Wochentage zu einer Weekdays Variable hinzufügt, entfernt und überprüft ob ein einzelner Wochentag enthalten ist oder mehrere Wochentage.

//Tage zu unserer Variablen hinzufügen
WeekDays days = WeekDays.Mon;
days |= WeekDays.Tue;
days |= WeekDays.Fri;
//geht auch so:
//WeekDays days = WeekDays.Mon |  WeekDays.Tue | WeekDays.Fri;

//Prüfen ob der "Tue" in unseren Tagen enhalten ist.
if (days.HasFlag(WeekDays.Tue))
{
    //Unsere Tage als Int ablegen um z.B. in der Db zu speichern
    int daysAsInt = ((int)days);

    //Unseren Int wert wieder umwandeln in die Tage die wir ausgewählt hatten
    WeekDays daysFromInt = (WeekDays)daysAsInt;

    //Prüfen ob unser umgewandelter Intwert auch noch den Freitag enthält
    if (daysFromInt.HasFlag(WeekDays.Fri))
    {
        //Wird erreicht, da der Freitag noch enthalten ist.
        string found = "found";
    }

    //Prüfen ob der Donnerstag enthalten ist.
    if (daysFromInt.HasFlag(WeekDays.Thu))
    {
        //Wird nicht erreicht, da kein Donnerstag enthalten ist.
        string notFound = "notFound";
    }
}

//Prüfen ob genau diese drei Tage enhalten sind
if (days == (WeekDays.Mon | WeekDays.Tue | WeekDays.Fri))
{
    //Wird erreicht, da genau die drei Tage enthalten sind.
    string allRights = "alle Tage enthalten";
}

//Prüfen ob genau diese beiden Tage enthalten sind
if (days == (WeekDays.Mon | WeekDays.Tue))
{
    //Wird nicht erreicht, da der "Fri" Fehlt und die
    //Objekt damit nicht gleich sind
    string allRights = "nicht alle Tage enthalten";
}

//Prüfen ob alle Tage enthalten sind
if (days == (WeekDays.Mon | WeekDays.Tue | WeekDays.Fri | WeekDays.Sat))
{
    //Wird nicht erreicht da ein Tag zuviel gesucht wird "Sat"
    string allRights = "nicht ein Tag enthalten";
}

//Prüfen ob in unserer Tageliste einer der drei Tage enthalten ist,
//da "Mon" enthalten ist, wird daysCheck1 größer 0 sein
WeekDays daysCheck1 = (days & (WeekDays.Mon | WeekDays.Sun| WeekDays.Sat));
if (daysCheck1 != 0)
{
    //Wird erreicht, da mind. einer der Tage "Mon" enthalten ist.
    string allRights = "mind ein Tag enthalten";
}

//Prüfen ob in unserer Tagesliste einer der drei Tage enthalten ist,
//da keiner der Tage enthalten ist wird daysCheck2 gleich 0 sein.
WeekDays daysCheck2 = (days & (WeekDays.Wed | WeekDays.Sun | WeekDays.Sat));
if (daysCheck2 != 0)
{
    //Wird nicht erreicht, da keiner der Tage enthalten ist.
    string allRights = "kein Tag enthalten";
}

//Direktes Prüfen ob "Mon" und "Tue" in der Liste enthalten sind.
if ( (days & (WeekDays.Mon | WeekDays.Tue)) != 0)
{
    //Wird erreicht, da mind. einer der Tage enthalten ist.
    string allRights = "alle Tage enthalten";
}

//Entfernen eines Tages
days -= WeekDays.Tue;

//Prüfen ob der Tag noch enthalten ist.
if (days.HasFlag(WeekDays.Tue))
{
    //Wird nicht erreicht, da kein Dienstag mehr enthalten ist.
    string notFound = "notFound";
}