15 January 2008 .NET 3.5, C# 3.0, HowTo, LINQ, LINQ to SQL robert.muehsig Edit

LINQ to SQL ist Microsofts LINQ Provider für das Hauseigene Datenbanksystem SQL Server 2005 (und 2008 - und noch mehr?). Das Konzept von LINQ sollte man vorher bereits verstehen - hier eine Kurzeinführung:

LINQ? Was ist das?

In jeder Applikation arbeitet man mit Daten, Objekten und noch mehr Ansammlungen von Objekten. Externe Datenquellen (XML, Datenbanken) muss man über ihre jeweiligen Abfragesprachen ansprechen - SQL oder XPath. Objektcollections oder komplexe Objekte in C# 2.0 haben kein solches Abfragesystem gehabt - man musste über Foreach-Schleifen die Collections durchgehen und dann immer wieder mit dem Suchwort vergleichen.

Hier tritt LINQ ins Spiel - LINQ erlaubt es, .NET Objekte mit einem SQL ähnlichen Syntax zu durchsuchen, allerdings mit allen Vorteilen die Visual Studio und Objekte bieten: Einfaches Debugging möglich, man arbeitet mit direkten Objekten und die IntelliSense hilft natürlich auch kräftig. Am Ende einer LINQ Abfrage kann man direkt ein "var" Objekt erzeugen (was das ist, wurde hier besprochen). Da man nicht nur Objekte so durchsuchen kann, sondern auch andere Daten, zeigt z.B. der LINQ to XML oder der LINQ to SQL Provider.

Microsoft hat LINQ so gestaltet, dass viele solche Provider erstellt werden können, sodass es momentan z.B. folgene Provider in Entwicklung befinden:

Wir beschäftigen uns heute mit LINQ to SQL - dabei werden wir einmal nur in einer sehr einfachen Abfrage LINQ to SQL anschauen und später ein etwas komplexeres Mapping per Hand vornehmen.
Der LINQ to SQL Designer wird später behandelt!

Vorbereitung & Dokumente

Ich halte mich hier an das Hand on Lab von Microsoft, welches es hier kostenlos zum Runterladen gibt. Desweiteren benötigen wir die Northwind Datenbank - eine Installationsanleitung gibt es hier. Natürlich benötigen wir Visual Studio 2008 Express Edition. Visual Studio und das SQL Management Studio sollten im Administratormodus laufen (und bei meinem Demoprojekt muss hinterher der ConnectionString angepasst werden)

Erster Schritt mit LINQ to SQL

Als erstes benötigen wir die System.Data.Linq.dll, damit wir die entsprechenden Namespaces verwenden können.
Danach erstellen wir unser erstes Mapping (wie bereits oben erwähnt, werden wir hier ein manuelles Mapping vornehmen um das System besser zu verstehen).

Schauen wir uns mal die Tabellen der Northwind Datenbank an:

image

Wir wollen einfach eine kleine Konsolen-Applikation haben, welche die Kundendaten bearbeitet.

LINQ to SQL - Schritt 1: Das Mapping

Die Kundendaten stehen in der "Customers" Tabelle - daher legen wir eine "Customers" Klasse an:

image

Dabei verwenden wir den Namespace "System.Data.Linq.Mapping" in der "Customer.cs".
Das Mapping erfolgt über das Zuweisen von Attributen zu der Klasse (das Table Attribut) und den Properties (das Column Attribut).

Quellcode sagt meist mehr als tausend Wort:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq.Mapping;

namespace LinqToSqlTest
{
    [Table(Name = "Customers")]
    public class Customer
    {
        [Column(IsPrimaryKey = true)]
        public string CustomerID;

        [Column]
        public string Address { get; set; }

        private string _City;

        [Column(Storage = "_City")]
        public string City
        {
            get { return this._City; }
            set { this._City = value; }
        }
    }

}

Erklärung:

Das Mapping erfolgt durch die Zuweisungen der entsprechenen Attribute (Attribute-based Mapping) - beim Klassennamen "Customer" wird das Tabel Attribut verwendet - das "Name" dort verbindet die "Costumers" Tabelle mit dieser Klasse. Der Name kann auch weggelassen werden, wenn DB Tabellennamen == Klassenname (in unserem Fall fehlt ein "s" im Klassennamen).
Die einzelnen Spalten werden über das Column Attribut den Properties zugewiesen (MSDN Artikel: How to: Represent Columns as Class Members (LINQ to SQL)).

Das Mapping erfolgt hierbei wieder anhand des Namens der DB Spalte und des entsprechenden Properties. Falls dies abweicht, gibt es noch ein Name Property (wie bei dem Tabellen Attribut oben).
Standardmäßig wird dann der DB Wert in das Property geschrieben, welches zugewiesen wurde. Wenn man als Datenhalterobjekt z.B. ein privates Property verwenden möchte, kann man dies über das "Storage" Property zuweisen.

LINQ to SQL - Schritt 2: Die DB Abfrage

In unserer Program.cs wollen wir nun eine einfache Abfrage der Kunden einbauen.

Als erstes müssen wireinen DataContext aufbauen, welchen wir den ConnectionString übergeben. Danach veranlassen wir den Datacontext das Mapping über die GetTable Methode zu "starten":

DataContext db = new DataContext(@"Data Source=REMAN-NOTEBOOK\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True");
Table<Customer> Customers = db.GetTable<Customer>();

Um zu sehen, was für SQL Befehle ausgeführt werden, gibt es auch ein Log Property, wo wir unsere Console anheften:

db.Log = Console.Out;

Jetzt zur eigentlichen Abfrage mit LINQ (und der Ausgabe):

            var custs =
                from c in Customers
                where c.City == "London"
                select c;

            foreach (var cust in custs)
            {
                Console.WriteLine("ID={0}, City={1}, Address={2}", cust.CustomerID, cust.City, cust.Address);
            }

Mit der Abfrage werden alle Kunden aus London in "var custs" (das Konzept von "var" ist hier beschrieben) gespeichert.

Hinweis: Mit GetTable<...>() werden noch keine Daten vom SQL Server geholt - erst beim Zugriff auf Customers wird die Abfrage gemacht (Siehe Video).

Ergebnis:

image

Vollständiger Quellcode:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace LinqToSqlTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Use a standard connection string
            DataContext db = new DataContext(@"Data Source=REMAN-NOTEBOOK\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True");
            Table<Customer> Customers = db.GetTable<Customer>();

            db.Log = Console.Out;

            // Query for customers in London
            var custs =
                from c in Customers
                where c.City == "London"
                select c;

            foreach (var cust in custs)
            {
                Console.WriteLine("ID={0}, City={1}, Address={2}", cust.CustomerID, cust.City, cust.Address);
            }

            Console.ReadLine();
        }
    }
}

LINQ to SQL - Schritt 3: Änderung an der DB vornehmen - einen neuen Eintrag anfügen

Ein neuen Eintrag hinzufügen geht recht schnell:

            Customer newCostumer = new Customer();
            newCostumer.City = "Dresden";
            newCostumer.Address = "Riesaer Str. 5";
            newCostumer.CustomerID = "DDMMS";
            newCostumer.CompanyName = "T-Systems MMS";

            Customers.InsertOnSubmit(newCostumer);
            db.SubmitChanges();

Hierbei musste ich aber noch ein neues Property für den Costumer hinzufügen: CompanyName - weil dieser (ebenso wie CustomerID) NOT NULL sein muss:

image

Wie oben zu sehen ist, wird einfach ein neuer Customer angelegt und über Customers wird dieses Objekt an (die wir über den DataContext bekommen haben) die Methode "InsertOnSubmit" weitergereicht und dort vorgemerkt, beim "Submitten" in die DB eingetragen zu werden.

Das "Submitten" wird über "SubmitChanges" vom DataContext gestartet.

LINQ to SQL - Schritt 4: Änderung an der DB vornehmen - einen Eintrag editieren

Das Dateneditiere wird direkt an den Objekten vorgenommen:

            var upObjects = from test in Customers
                             where test.CompanyName == "T-Systems MMS"
                             select test;

            upObjects.First().Address = "Straße";
            db.SubmitChanges();

Hier erfolgt nur ein "SubmitChanges" und der erste gefundene Eintrag (über First()) wird entsprechend geändert.

LINQ to SQL - Schritt 5: Änderung an der DB vornehmen - einen Eintrag entfernen

Den eben angelegten Eintrag kann man auch wieder entfernen:

var delObjects = from test in Customers 
                             where test.CompanyName == "T-Systems MMS"
                             select test;

            Customers.DeleteOnSubmit(delObjects.First());
            db.SubmitChanges();

Hier holen wir uns einfach unser eben erstelltes Objekt wieder und nehmen wieder über die First() Methode das erste gefundene (da es sowieso nur einen Eintrag dort gibt) Ergebnis. Löschen kann man es einfach über "DeleteOnSubmit".

Der gesamte Source Code (download weiter unten):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace LinqToSqlTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Use a standard connection string
            DataContext db = new DataContext(@"Data Source=REMAN-NOTEBOOK\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True");
            Table<Customer> Customers = db.GetTable<Customer>();

            // Logging
            db.Log = Console.Out;

            // CREATE
            Customer newCostumer = new Customer();
            newCostumer.City = "Dresden";
            newCostumer.Address = "Riesaer Str. 5";
            newCostumer.CustomerID = "DDMMS";
            newCostumer.CompanyName = "T-Systems MMS";

            Customers.InsertOnSubmit(newCostumer);
            db.SubmitChanges();
            
            // READ
            var custs =
                from c in Customers
                where c.City == "Dresden"
                select c;

            // Debugging - Console.WriteLine
            foreach (var cust in custs)
            {
                Console.WriteLine("ID={0}, City={1}, Address={2}", cust.CustomerID, cust.City, cust.Address);
            }
            
            // UPDATE
            var upObjects = from test in Customers
                             where test.CompanyName == "T-Systems MMS"
                             select test;

            upObjects.First().Address = "Straße";
            db.SubmitChanges();

            // Debugging - Console.WriteLine
            foreach (var cust in custs)
            {
                Console.WriteLine("ID={0}, City={1}, Address={2}", cust.CustomerID, cust.City, cust.Address);
            }

            // DEL
            var delObjects = from test in Customers 
                             where test.CompanyName == "T-Systems MMS"
                             select test;

            Customers.DeleteOnSubmit(delObjects.First());
            db.SubmitChanges();
            
            Console.ReadLine();
        }
    }
}

Wichtiger Hinweis:

Die Anwendung läuft nur einmal durch, da die CustomerID eindeutig sein muss - aber das hier soll nur der Anfang sein um LINQ to SQL mehr zu verstehen.
Das die LINQ Abfragen hier sicherlich verbesserungswürdig sind, ist eine andere Sache (und wird sicherlich in einem anderen HowTo unterkommen).

Das Mapping erfolge hier sehr einfach über das manuelle dazu schreiben der Attribute - es gibt einen direkten LINQ to SQL Designer.

Empfehlung an alle die sich für LINQ to SQL interessieren:

Das HoL über LINQ to SQL, was ich bereits oben erwähnt hatte, geht noch auf andere Aspekte ein. Einige davon werde ich sicherlich selber noch in HowTos packen, allerdings bis dahin dient es als gute Anlaufstelle zu Themen wie:

  • LINQ to SQL Designer
  • Beziehungen zwischen einzelnen Tabellen modellieren
  • Streng-typisierte DataContext Objekte
  • Transaktionen (Sehr interessantes Thema!)
  • Mapping a stored procedure

Das Thema wird sicherlich bei mir noch häufiger anzutreffen sein (und das war heute mein erster richtiger Berührungspunkt mit LINQ to SQL - ich hoffe, dass das Konzept (und wie es intern tickt) etwas klarer ist ;) )

[ Download Demoanwendung ]


blog comments powered by Disqus