SQL Server & ASP .NET Blog

Interessantes und Wissenswertes

Git–neue Remote Branches erscheinen nicht in Visual Studio

Szenario: Ein Kollege hat einen neuen Branch angelegt und publiziert
Problem: Der Branch erscheint nicht in Visual Studio

Lösung:

Schritt 1 – Drittanbietertools installieren

http://msdn.microsoft.com/de-de/library/dd286572.aspx#setup_open

Schritt 2 – Konsole öffnen

16_konsole

Schritt 3 – Fetch und checkout

In der Konsole das aktuelle Repository holen “git fetch” und danach den neuen Branch auschecken mit dem Befehl “git checkout –b <branchname> origin/<branchname>” .

Daraufhin erscheint dieser auch im Visual Studio.

Export nach Excel aus eigenen Anwendungen

Für den Export von Excel-Dateien aus eigenen Anwendungen gibt es eine schöne OpenSource-Lösung unter http://epplus.codeplex.com/=> EPPlus

Mit dieser Bibliothek (unter Referenzen einbinden) lassen sich recht einfach – auch komplexe – Excel Sheets erstellen und exportieren.

Ein kleines Beispiel, welches nur ein paar Zellen mit Text füllt und eine Zeile auf unsichtbar schaltet ist unten zu sehen.

using OfficeOpenXml;
void ExportToExcel()
{
    using (var p = new ExcelPackage(new FileInfo("Data\\Template.xlsx"), true))
    {
        //ExcelWorksheet
        ExcelWorksheet ws = p.Workbook.Worksheets[1];
        
        //Fill
        ws.Cells["A4"].Value = "Dies";
        ws.Cells["A5"].Value = "ist";
        ws.Cells["A6"].Value = "eine";
        ws.Cells["A8"].Value = "Demo";

        //set row 10 invisible
        ExcelRow row = ws.Row(10);
        row.Hidden = true;

        //Save to Disk
        Byte[] bin = p.GetAsByteArray();

        const string file = "Data\\Export.xlsx";
        File.WriteAllBytes(file, bin);

        //Open
        System.Diagnostics.Process.Start("Data\\Export.xlsx");
}

Auf jeden Fall wesentlich einfacher zu bedienen als die “hauseigene” Integration mit Office.

SQLCE und ADO .NET Entity Framework

Möchte man das das ADO .NET Entity Framework und den SQL Server Compact in einer Anwendung zusammen verwenden gibt es einen interessanten Punkt zu beachten – keine IDENTITY Columns in der Datenbank verwenden. Durch die Einschränkung des SQL CE in einem Batch nur einen Befehl ausführen zu können ist es für das Entity Framework bei einem INSERT unmöglich festzustellen, welches der neue IDENTITY Wert ist – daher werden diese Spalten nicht unterstützt.

Einfache Lösung: Selbst erstellen des Schlüssels in der Anwendung. Um zu vermeiden hier auch erst wieder eine MAX(Id) herausfinden zu müssen bieten sich hier auch GUIDs (bzw. uniqueidentifier) an. Beim Erzeugen eines Objektes also einfach:

myObject.myId = Guid.NewGuid();

…dann ist auch das Entity Framework zufrieden.

Dazu noch ein Link (da ich meine Spalten vorher natürlich als INT IDENTITY erstellt habe ;-)) mit dem Opensource-Tool bzw. SSMS Addin http://exportsqlce.codeplex.com/ ist es ein leichtes die SQL CE Datenbank komplett inkl. Daten zu skripten.

Visual Studio – Quelltext während Debuggen bearbeiten

Um den Quelltext einer Anwendung während des Debuggens zu bearbeiten, muss “Edit and Continue” aktiviert werden. Dies geht für Webanwendungen an zwei (!!) Stellen. Einerseits bei den Optionen und andererseits in den Projekteigenschaften unter “Web”. (Die Bearbeitung funktioniert allerdings erst, sobald der Code an einem Haltepunkt angekommen ist).

image

image

ClientIDs in Repeater ändern sich…

Ich hatte das Problem, dass sich plötzlich die ClientIDs, die automatisch vom Framework vergeben werden – geändert haben. Eine ID hieß nun nicht mehr myRepeater__ctl1_textBoxMyText sondern plötzlich myRepeater_ctl01_textBoxMyText. Ein kleiner Unterschied (ctl1 und ctl01 und der zusätzliche Unterstrich)– aber wenn man per JavaScript auf die Elemente zugreifen möchte mehr als ärgerlich – denn das funktioniert nicht mehr.

Grund war ein Eintrag in der web.config.

<xhtmlConformance mode="Legacy"/>

Dieser Eintrag sorgt dafür, dass die Elemente einer Seite anders gerendert werden. Mehr Info dazu hier: [hier] und [hier]

RavenDB – NoSQL Datenbank - Tutorial

Ich habe in den letzten Tagen ein Wenig mit der NoSQL (übersetzt mit “not only sql”) Datenbank “RavenDB” herumgespielt. RavenDB ist eine Dokument-Datenbank. Das bedeutet in diesem Fall, dass die Daten nicht in Form von relationalen Tabellen, sondern in Form von JSON-Dokumenten gespeichert werden. In dieser Datenbank können Objekte “einfach so” ohne kompliziertes Mapping gespeichert werden.

Einige Vorteile bzw. Eigenarten von Dokument-basierten Datenbanken sind:

  • die DB muss kein festgelegtes Schema haben, da alles in Dokumenten gespeichert wird – die Dokumente an sich bilden das Schema
  • es muss kein Mapping von Klassen auf Datenbankobjekte vorgenommen werden
  • Performance-Vorteile beim schreibenden Zugriff, da die Konsistenz innerhalb der Datenbank nicht immer gewährleistet sein muss
  • große Datenbestände können auf mehrere kleine Server verteilt werden

Zu finden ist RavenDB unter http://ravendb.net/. Lizenziert wird die Datenbank “dual”, also frei für OpenSource-Projekte und kostenpflichtig für kommerzielle closed-source Software.

Einführung

Nun aber zur Datenbank an sich: Die Datenbank kann grundsätzlich in zwei verschiedenen Modi betrieben werden – embedded in der eigenen Anwendung oder als Server. Unterschied sind die einzubindenden Referenzen und die Einrichtung der Verbindung zur Datenbank. Wichtig zu wissen ist, dass die Embedded-Variante nur mit Anwendungen, die für das .NET Framework 4 kompiliert werden, funktioniert. Die Client-Server Variante funktioniert auch mit .NET 3.5. Die entpackte RavenDB hat diese Verzeichnisstruktur:

image

Um nun den Server zu starten genügt das Starten der Raven.Server.Exe im Verzeichnis “Server”. Der Server ist dann unter http://localhost:8080 zu erreichen und wartet mit einer kleinen Administrationsoberfläche unter der man sich die gespeicherten Dokumente anschauen kann, Statistiken einsehen kann und Map/Reduce Indizes anlegen kann – aber dazu später mehr.

image

RavenDB – Hello World Skiverleih – ein kleines Tutorial

Nun erst einmal zu einem kleinen “Hello-World” mit RavenDB. Nehmen wir an, wir betreiben einen Skiverleih (diesmal keine Autovermietung :-)) und wollen unsere Ski in der Datenbank ablegen und dazu noch Bewertungen der Ski unserer Kunden…

Also zunächst ein neues Visual Studio Projekt geöffnet, ich nehme hier eine Konsolenanwendung und den RavenDB-Client aus dem Verzeichnis Client eingebunden.

image

Die Ski werden durch die Klasse Ski repräsentiert.

public class Ski
{
    public string Id { get; set; }
    public decimal VerleihpreisTag { get; set; }
    public decimal Anschaffungspreis { get; set; }
    public List<Bewertung> Bewertungen { get; set; }
    public int LaengeInCm { get; set; }
    public bool Gewachst { get; set; }

    public Ski()
    {
        Bewertungen = new List<Bewertung>();
    }

    public double Durchschnittswertung
    {
        get
        {
            if (Bewertungen.Count > 0)
                return Bewertungen.Average(x => x.Wertung);
            else
                return 0;
        }
    }

}

Für die Datenbank und das “Wiederfinden” bestimmter Ski ist es notwendig, dass die Klasse ein Property mit dem Namen “Id” besitzt. Für die Erstellung von IDs in RavenDB gibt es drei Möglichkeiten:

  • explizites Festlegen – also bspw. durch mySki.Id = “1”
  • automatische Generierung – wird der Klasse kein Id-Property “spendiert”, dann generiert RavenDB beim Einfügen automatisch eine ID (GUID), wenn es also nicht wichtig ist konkrete Ski anhand ihrer ID wiederzufinden, kann auf die Id verzichtet werden
  • teilautomatische Generierung – die Id kann bei allen Instanzen der Ski-Klasse auf bspw. “ski/” gesetzt werden. Das veranlasst RavenDB dazu die Ids aufsteigend zu generieren, also “ski/1” für den ersten Ski, “ski/2” für den zweiten, etc.

Im Beispiel werde ich die Ids manuell vergeben.

Nun gibt es noch die Klasse Bewertung, die eine Wertung und einen Kommentar des Skifahrers enthält.

public class Bewertung
{
    public double Wertung { get; set; }
    public string Kommentar { get; set; }
}

Innerhalb der Ski-Klasse werden alle Bewertungen zu einem Ski als List<Bewertung> gespeichert und ein Property Durchschnittswertung errechnet den Durchschnitt aller Bewertungen des Ski.

Um nun Daten in RavenDB zu speichern, muss zuerst ein DocumentStore angelegt, instanziert und initialisiert werden:

//Initialisierung
var ds = new DocumentStore() { Url = "http://localhost:8080" };
ds.Initialize();

Hier genügt es die URL, unter der die Datenbank läuft anzugeben. Möchte man den Embedded Client verwenden, kann hier auch das Datadirectory angegeben werden, unter dem die Daten gespeichert werden sollen.

Nun erstelle ich ein paar Ski mit Bewertungen:

//ein Ski mit zwei Bewertungen
var mySki = new Ski();
mySki.Id = "Ski/1";
mySki.VerleihpreisTag = 11.0m;
mySki.Anschaffungspreis = 399.99m;
mySki.LaengeInCm = 170;
mySki.Gewachst = true;            
mySki.Bewertungen.Add(new Bewertung() { Wertung = 5, Kommentar = "Super Ski!" });
mySki.Bewertungen.Add(new Bewertung() { Wertung = 2, Kommentar = "Genial am Hang!" });

//noch ein Ski mit einer Bewertung ungewachst
var mySki2 = new Ski();
mySki2.Id = "Ski/2";
mySki2.VerleihpreisTag = 9.0m;
mySki2.Anschaffungspreis = 299.99m;
mySki2.LaengeInCm = 120;
mySki2.Gewachst = false;
mySki2.Bewertungen.Add(new Bewertung() { Wertung = 1, Kommentar = "Doof!" });

//noch ein Ski mit einer Bewertung ungewachst
var mySki3 = new Ski();
mySki3.Id = "Ski/3";
mySki3.VerleihpreisTag = 9.0m;
mySki3.Anschaffungspreis = 299.99m;
mySki3.LaengeInCm = 120;
mySki3.Gewachst = false;
mySki3.Bewertungen.Add(new Bewertung() { Wertung = 2, Kommentar = "Blöd!" });

Wie angekündigt habe ich die IDs manuell vergeben. Würde ich nun einen weiteren Ski mit der Id “Ski/” anlegen wäre seine Id automatisch “Ski/4”.

Speichern in der Datenbank

Zum Speichern der Ski in der Datenbank genügt folgender Code:

//Ski speichern
using (var session = ds.OpenSession())
{
    session.Store(mySki);
    session.Store(mySki2);
    session.Store(mySki3);
    session.SaveChanges();                
}

Hier wird eine Session mit der Datenbank geöffnet, die Ski gespeichert und SaveChanges() aufgerufen. SaveChanges() entspricht in etwa einem COMMIT in einer SQL-Datenbank.

Die gespeicherten Ski können nun auch direkt in der Datenbank angezeigt werden. Also http://localhost:8080 und Klick auf “Documents” ergibt folgendes Bild:

image

Die drei Ski sind also gespeichert – soweit so gut :-) Schauen wir uns einmal den Ski mit der ID “Ski/1” genauer an, um den Aufbau innerhalb der Datenbank zu sehen:

image

Der Ski wird also in einem Dokument inkl. aller Bewertungen gespeichert. Unter Document Metadata sind noch die von RavenDB vergebenen Metadaten enthalten. Interessant hier ist der “Raven-Entitiy-Name”, der als Plural der Klasse benannt ist.

image

Ändern in der Datenbank

Um nun an einzelnen Ski, wieder im Beispiel “Ski/1” heranzukommen, um mit diesem weiter arbeiten zu können bzw. um die Daten zu verändern ist folgender Code notwendig:

//einen Ski lesen und ändern
using (var session = ds.OpenSession())
{
    //lesen
    var myDbSki = session.Load<Ski>("Ski/1");
    
    //ändern
    myDbSki.VerleihpreisTag = 10.0m;

    //speichern
    session.Store(myDbSki);
    session.SaveChanges();
}

Also wieder eine Session öffnen, den Ski per Load<Ski>("<Id_des_Ski>”) laden, verändern und per Store() und SaveChanges() wieder in der Datenbank speichern.

Löschen eines Ski

Soll nun ein Ski wieder aus der Datenbank gelöscht werden, muss dieser zunächst gelesen werden, um eine Verknüpfung zu einer existierenden Instanz herzustellen.

//einen Ski löschen
using (var session = ds.OpenSession())
{
    session.Delete<Ski>(session.Load<Ski>("Ski/2"));
    session.SaveChanges();
}

Soweit erst einmal zu den Grundfunktionen von RavenDB. Im nächsten Post werde ich die Indexierung via Map/Reduce und die Abfrage mehrerer Instanzen vorstellen.

SQL CLR Integration – Test

Eine kurze Erweiterung zum letzten Artikel: siehe [hier]

Möchte ich die erstellte Funktion testen und debuggen, dann kann ich das auch direkt aus dem Visual Studio heraus. Dazu gibt es im erstellten Projekt eine bereits vorhandene Test.sql, die dazu dienen soll eigene Funktionen etc. auszuführen, zu testen und zu debuggen.

image

Mit Rechtsklick auf die Test.sql und “Set as default debug script” wird dieses Skript immer beim Debuggen ausgeführt.

image

Nun kann ich im Skript meine Funktion ausführen und ein paar Tests durchführen. Beispiel:

IF dbo.fDNSCheck('MyDNSName') = 1
PRINT 'OK'
IF dbo.fDNSCheck('NotMyDNSName') = 0
PRINT 'OK'  

Dazu muss auf dem SQL Server allerdings der Remote Debugger installiert sein (msvsmon.exe). Weitere Informationen dazu gibt es [hier]Außerdem müssen einige Berechtigungen als Voraussetzung vorhanden sein (siehe ebenso der Link). Sind alle Voraussetzungen vorhanden, dann kann per F5 das Skript gestartet werden, es können Haltepunkte im .NET Code gesetzt werden etc.

SQL CLR Integration - .NET Code im SQL Server

Der SQL Server bietet die Möglichkeit CLR-Code direkt auszuführen und somit erweiterte Funktionen zu erfüllen. Ein kleines Beispiel: Ich möchte prüfen, ob mein SQL Server auf einen bestimmten DNS-Namen im Netzwerk hört. Dazu ist mir mit Bordmitteln keine Möglichkeit bekannt.

Zunächst einmal muss die CLR-Integration aktiviert werden.

sp_configure 'clr_enable', 1
RECONFIGURE

Ergebnis ist:

Configuration option 'clr enabled' changed from 0 to 1. Run the RECONFIGURE statement to install.

Jetzt können wir loslegen. Ich erstelle im Visual Studio ein neues Datenbankprojekt und nenne es DNSChecker:

image

Im darauf folgenden Dialog muss eine Verbindung zu einer Datenbank hergestellt werden (die Datenbank, in der die Assembly letztendlich liegen soll und aus der sie ausgeführt wird).

image

Nachdem die Verbindung eingetragen ist, habe ich über einen Rechtsklick auf das Projekt und “Add” die Möglichkeit verschiedene Objekte zu erstellen:

image

Die einzelnen Möglichkeiten sind:

  • User-Defined Function – Eine Funktion mit Eingabeparametern und einem Ausgabeparameter
  • Stored Procedure – Ähnlich einer T-SQL Prozedur
  • Aggregate – Eine Aggregatfunktion, wie SUM(), AVG() etc.
  • Trigger – Ein CLR Trigger
  • User-Defined Type – Ein eigener “Datentyp”

Für das Beispiel beschränke ich mich auf das Erstellen einer Funktion, die mir nach Eingabe eines DNS-Namens zurückgibt, ob mein Server auf diesen Namen hört, oder nicht. Also => User-Defined Function

Nach Erstellen der Funktion erhalte ich einen bereits funktionsfähigen Rumpf mit einer Art “Hello World”.

public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString fDNSCheck()
{
// Put your code here
return new SqlString("Hello");
}
};

Würde ich die Funktion nun aufrufen würde ich ganz einfach “Hello” als Ergebnis erhalten. Ich möchte allerdings “wahr” oder “falsch” als Bit-Wert zurückgeben und benötige als Eingabeparameter noch einen string (den DNS-Namen)

public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlBoolean fDNSCheck(string sDnsName)
{
// Put your code here
return new SqlBoolean(false);
}
};

 

 

Nun fehlt noch die eigentliche Überprüfung, ob der Server auf den Namen hört.

[Microsoft.SqlServer.Server.SqlFunction]
public static SqlBoolean fDNSCheck(string sDnsName)
{
bool result = false;
try
{
//Get all ips for DNS-Name
IPHostEntry ip = Dns.GetHostEntry(sDnsName);
IPAddress[] IpA = ip.AddressList;
//check if a local ip for this name exists
foreach (IPAddress ipDNS in IpA)
{
foreach (IPAddress ipLocal in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (ipDNS.ToString() == ipLocal.ToString())
{
result = true;
}
}
}
}
catch (Exception ex)
{
result = false;
}
return new SqlBoolean(result);
}

Dazu iteriere ich über alle für den DNS-Namen gefundenen IP-Adressen und vergleiche mit allen auf dem Server gefundenen IP-Adressen. Wird eine Übereinstimmung gefunden wird “true” zurück gegeben. Nun ist die Funktion fertig und könnte arbeiten. Da die Funktion allerdings die Berechtigungsstufe “unsafe” benötigt (näheres siehe unter http://technet.microsoft.com/de-de/library/ms131071.aspx) muss die Datenbank in der diese laufen soll die Eigenschaft “TRUSTWORTHY ON” haben. Also…

ALTER DATABASE CLRTestDB SET TRUSTWORTHY ON

…und für die Assembly muss eingestellt werden, dass der Zugriff in der Stufe “unsafe” erfolgen soll (in den Projekteigenschaften).

image

Nun kann ich die Assembly per “Deploy” auf dem Server installieren. Führe ich die Funktion nun aus und mein Server hört z.B. auf den Namen “MyDNSName”, dann gibt die Funktion “1” für “wahr” zurück.

image

Dieses kleine Beispiel hat gezeigt, wie einfach es ist den SQL Server mit eigenen .NET Funktionen zu erweitern.

Visual Studio (C#) – Fehlende Using-Direktiven (halb)automatisch einfügen

Ein kleiner Tipp. Teilweise tippt man munter drauf los, ohne vorher die benötigten “using” Direktiven anzugeben. Visual Studio merkt das und unterstreicht die verwendete Klasse rot.

sqlcon

Nun kann man – entweder die using-Direktive manuell eingeben, oder – man drückt [STRG] + [.] und ein Kontextmenü klappt auf und die Direktive kann direkt automatisch eingebunden werden.

sqlcon2

Funktioniert mit Visual Studio 2008 und 2010 (ob es in 2005 schon ging, habe ich nicht getestet).

ASP .NET - Bild aus Datenbank abrufen und anzeigen

In einem anderem Beitrag [hier]habe ich bereits beschrieben, wie einfach es ist, ein Bild auf einer Webseite hochzuladen und mit wenigen Zeilen C#-Code in einer SQL Server Datenbank zu speichern. Interessant ist natürlich auch der Rückweg: Also wie kommt das Bild wieder aus der Datenbank heraus und kann im Browser angezeigt werden?

Auch das geht wieder in einfachen Schritten.

Schritt 1 – Eine ASPX-Seite zur Ausgabe eines Bildes

Um ein Bild aus der Datenbank zu lesen und im Browser anzuzeigen, erstelle ich eine neue Seite “GetPicture.aspx”. Diese Seite hat keinen eigentlichen Inhalt, sondern nur den Zweck ein Bild anhand des Namens (oder eines beliebigen anderen Parameters, z. B. einer ID) aus der Datenbank zu holen und an den Browser zu senden.

protected void Page_Load(object sender, EventArgs e)
{
//Variable für die Bilddaten
Byte[] myFile;
//der Dateiname aus dem QueryString
string myFileName = Request.QueryString["FileName"];
if (myFileName != null)
{
//Verbindung zum Server
SqlConnection con = 
new SqlConnection("Data Source=192.168.1.13;Initial Catalog=BildDB;User ID=xxx;Password=xxx");
//Insert Statement mit den entsprechenden Parametern
SqlCommand cmd = new SqlCommand("SELECT picture FROM pictures WHERE name = @name", con);
//Parameter für den Bildnamen
SqlParameter paramName = new SqlParameter("@name", System.Data.SqlDbType.VarChar);
paramName.Value = myFileName;
cmd.Parameters.Add(paramName);
//Daten lesen
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read() ) //im Beispiel interessiert nur der erste Satz
{
myFile = (Byte[])reader["picture"];
Response.ContentType = "image/jpeg"; //ContentType setzen
Response.BinaryWrite(myFile);        //Bild ausgeben
}
con.Close();
}      

Ruft man nun diese Seite mit einem in der Datenbank vorhandenen Bildnamen auf, dann wird das Bild direkt im Browser angezeigt.

image

Schritt 2 – Bilder in einem Gridview anzeigen

Um nun mehrere Bilder aus der Datenbank anzuzeigen kann bspw. ein GridView verwendet werden. Auf einer zusätzlichen Seite “Anzeigen.aspx” habe ich eine neue SQLDatasource und ein GridView erstellt.

        <!-- Die Datenquelle -->
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:BildDBConnectionString %>" 
SelectCommand="SELECT picture, name, addedat FROM [pictures]">
</asp:SqlDataSource>
<!-- Anzeige im Gridview -->
<asp:GridView ID="GridView1" runat="server" 
DataSourceID="SqlDataSource1" 
AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="name" HeaderText="name" SortExpression="name" />
<asp:BoundField DataField="addedat" HeaderText="addedat" 
SortExpression="addedat" />
<asp:ImageField DataImageUrlField="name" 
DataImageUrlFormatString="~/GetPicture.aspx?Filename={0}">
</asp:ImageField>
</Columns>
</asp:GridView>

Das “Besondere” an diesem GridView ist nun, dass es ein ImageField hat, welches direkt die in Schritt 1 erstellte ASPX-Seite aufruft und den aus der Datenbank gelieferten Dateinamen übergibt.

Das Endergebnis sieht dann so aus:

image 

Das Projekt (Visual Studio 2010) hängt zum Anschauen direkt hier am Beitrag. Fragen sind natürlich immer gern gesehen ;)

Bildverwaltung.zip (32,13 kb)