HowTo: O/R Mapper LINQ to SQL – Einführung & einfaches manuelles Mapping

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 ]

Wenn dir der Blogpost gefallen hat, dann hinterlasse doch einen Kommentar. Wenn du auf dem Laufenden bleiben willst, abonniere unseren RSS Feed oder folge uns auf Twitter.

About the author

Written by

Hi, ich bin Robert Mühsig und bin Webentwickler und beschäftige mich mit Web-Frameworks auf dem Microsoft Web Stack und scheue mich auch nicht vor Javascript. Der Blog begann als "Problemsammelstelle und Lösungshilfe" und seitdem schreibe ich hier alles auf. Seit 2008 bin ich Microsoft MVP für ASP.NET. Treffen kann man mich online via Twitter (@robert0muehsig) oder hier.

23 Responses

  1. Ich beginne gerade mit LINQ, bzw. ich starte mit OR Mapping und mir schwirrt ein bisschen der Kopf. Wenn man eigene Klassen als Member hat, relationen abbilden muss und das ganze auch noch auf MySQL … ist das sicher verständlich.

    Durch diesen Artikel sehe ich aber schon ein wenig klarer. Gibt es schon gue Bücher dazu? Ich habe nur “Introducing MS LINQ” von Pialorsi und das bringt mich nicht sehr weit. Basiert ja auch noch auf der Beta.

    Was mich noch brennend interessiert ist das Plugin für den Sourcecode! Ich poste auch ab und an Code und da ist sowas natürlich der Hit!

    Vielen Dank für diesen Beitrag!

    Reply
  2. Hallo Michael,
    erstmal zu dem Plugin – ich schreibe die Posts mit dem Windows Live Writer und verwende dann noch dieses Plugin:
    http://www.live-writer.de/project/WindowsLiveWriterCodeFormatPlugin.aspx
    Dann hab ich noch etwas bei mir auf den Server die CSS entsprechend angepasst, damit die Scrollbars bei überlangen Source Code Blöcken kommen.

    Gute Bücher sind mir über LINQ (neben deinem erwähnten) noch nicht bekannt.

    LINQ mit MySQL ist wahrscheinlich doch noch recht schwierig – später soll sowas durch LINQ to Entities gegeben sein (das müsste das Schlagwort ADO.NET Entity Framework sein) oder LINQ to Dataset. Ein direktes Mapping wie hier (oder mit dem Designer) ist mit MySQL leider noch nicht möglich.
    Wenn es um LINQ to SQL geht, kannst du auch bei Scott Guthries Blog nachschauen:
    http://weblogs.asp.net/scottgu/archive/tags/LINQ/default.aspx

    Wie wäre es mit einem anderen O/R Mapper für dein MySQL Problem – SubSonic oder NHibernate z.B.? Dazu soll es später auch LINQ Provider geben. Der Projektleiter von SubSonic ist seit kurzem auch für Microsoft zuständig und hat daher starkes interesse die neuen Microsoft Technologien mit SubSonic zu verbinden.

    Viele Grüße,
    Robert

    Reply
  3. Danke für die Hinweise, ich habe mich die letzten Tage noch ein wenig schlau gemacht. Für MySQL gibt es einen Linq-to-SQL Provider (code2code.net/DB_Linq)und das Projekt scheint auch aktiv zu sein.

    Reply
  4. Sehe ich es richtig, dass mal LINQ nur zusammen mit VS 2008 einsetzen kann? Bzw. speziell die ein “var” Objekt-Konstruktion nich unter VS 2005 funktioniert?

    mfg. klaus.

    Reply
  5. LINQ, “var” etc. sind alles .NET 3.5 Features, welche nur mit VS 2008 genutzt werden können. Unter VS 2005 gibt es keine Möglichkeit diese so zu nutzen.

    Reply
  6. ich kann ohne Fehlermeldung Änderungen an der DB vornehmen.
    Wenn ich eine LINQ Abfrage nach dem Create und SubmitChanges ausführe bekomme ich als Ergebnis sogar meinen kurz zuvor eingefügten Record. Allerdings sehe ich ihn nicht in der DB Tabelle wenn ich mir die Tabellendaten anzeigen lasse.
    Liegt es daran das mir nur der SQLServer Express zu Verfügung steht? oder daran das ich die automatische generierte O/R Mapper Klasse verwende und keine selbst erstellt habe wie im obigen Beispiel?

    Reply
  7. Das liegt an meinem etwas ungünstig gewählten Beispiel. Wenn man im Consolen Projekt eine DB hinzufügt, wird diese beim Kompilieren in das Bin\Debug Verzeichnis kopiert.
    Sobald man aber nun im VS die DB anschaut, sieht man natürlich nur die Ausgangstabelle – weil die DB in der die Daten drin stehen im Bin\Debug Verzeichnis liegen. Wenn man die Consolenanwendung wieder startet, wird die bereits bestehende DB im Bin\Debug Ordner gelöscht.

    Besser wäre das Beispiel gewesen, hätte ich eine ASP.NET Seite gemacht – da gibts solche Probleme nicht.

    Wenn du die DB hinterher anschauen willst, dann verweise auf die DB in dem Bin\Debug Ordner und stelle bei den Properties ein, dass diese nicht jedes mal kopiert wird.

    Reply
  8. Gibt es irgendeine Möglichkeit das Datacontext Log File auch in einen Windows Project auszugeben?
    die Msgbox und Debug.Print funktionieren nicht weil das Log File nicht in System.String umzuwandeln ist.

    Reply
  9. Gibts nicht noch Debug.Trace oder sowas in der Art? Dann wird das direkt im VS ausgegeben.

    Reply
  10. debug.write funktioniert

    Reply
  11. naja funktionieren muss ich relativieren-es gibt kein Fehler aus, aber auch kein Log.
    Debug.Trace finde ich nicht!

    Reply
  12. Hallo,habe es auch mit anderen Datenbanken probiert. Es funktioniert (zum auslesen) auch mit einer einfachen oleDB…Dim conn As New OleDbConnection(ConfigurationManager.ConnectionStrings(table).ConnectionString)Dim c As New DataContext(conn)da ja oledbconnection von dbconnection abstammt, scheint es kein problem zu sein. 

    Reply
  13. Hallo,
    erstmal ein dickes Lob für den tollen Artikel!
    Ich hab bei mir allerdings einen Fehler den ich mir einfach nicht erklären kann. Ich will einen neuen Datensatz hinzufügen, doch ich krieg folgende Meldung:
    "ERROR [07002] [Microsoft][ODBC Microsoft Access Driver] 5 Parameter wurden erwartet, aber es wurden zu wenig Parameter übergeben."
    Doch wenn ich mir das Log von LINQ angucke dann sind alle Parameter vorhanden. Hier der Befehl der erzeugt wird:

    INSERT INTO [Lecturer]([lid], [firstname], [lastname], [token], [pw])
    VALUES (@p0, @p1, @p2, @p3, @p4)
    – @p0: Input String (Size = 0; Prec = 0; Scale = 0) [4]
    – @p1: Input String (Size = 5; Prec = 0; Scale = 0) [Marek]
    – @p2: Input String (Size = 6; Prec = 0; Scale = 0) [Cwolek]
    – @p3: Input String (Size = 8; Prec = 0; Scale = 0) [pba1f7cw]
    – @p4: Input String (Size = 4; Prec = 0; Scale = 0) [Test]

    Kann mir jemand sagen was nun der Fehler sein soll? Liegt es vielleicht daran dass die DB bei der Spalte lid den Feldtypen AutoWert hat?
    Oder liegt es daran dass es sich bei der DB um ACCESS handelt?
    Also aus der DB lesen kann ich, ich kann nur keine Manipulation machen :(

    Brauche dringend Hilfe!

    Gruß
    Marek

    Reply
  14. Hallo,

    auch von mir ein dickes Lob für den Artikel! Ich suche nach der einfachsten Möglichkeit dem User eine Auswahl der DataContext MappingSource, aus der app.config, über eine ComboBox anzubieten.Alles was ich bisher habe ist ganz schön kompliziert….

    Heiko

    Reply
  15. Hallo,
    ich habe auch eine auch eine Frage. Ist es möglich, nachträglich meiner Customer-Klasse noch ein zusätzliches Property zu hinzuzufügen und diese Änderung dann autom. in der DB nachzuziehen (also eine neue Spalte zu erzeugen) ohne die Tabelle zu löschen?

    Danke

    Flo

    Reply
  16. Hallo Florian,

    einfach in der Datenbank die Spalte anlegen und anschließend das Linq2SQL-Model per Update aktualisieren. Schon hast du deine neue Property…

    Reply
  17. Hallo Ken,
    kann es evtl. sein, dass Du Linq to SQL und das Entity Framework durcheinanderbringst? Beim EF kann man das so machen. Leider gibt es bei meinem DBML-Modell keine Updatefunktionalität. Oder hab ich die irgendwie übersehen?

    FLO

    Reply
  18. Mh. Ich glaube das geht nur beim Entity Framework bzw. man muss in der Designer Klasse rumschreiben.

    Reply
  19. Ich war der Meinung, dass geht auch bei Linq2Sql. Aber du hast Recht – Update gibts nicht :-(

    Reply

Comment on this post

Letzte Posts

  • image_thumb.png
    NuGet Package Restore & Build Server wie z.B. AppVeyor

    NuGet ist ja mittlerweile weit verbreitet, aber eine Frage stellt sich natürlich immer noch: Checkt man die NuGet Packages ein oder nicht? In meinem kleinen Side-Projekt, welches auf GitHub liegt und ich über AppVeyor auch bauen lasse nutze ich das Package Restore Feature von NuGet, d.h. in meinem Repository befindet sich kein NuGet Package mehr, […]

  • image.png
    Microsoft Account Login via ASP.NET Identity

    Der Microsoft Account ist die zentrale Identifikationsstelle in der “Consumer-Microsoft-Welt”, allerdings ist das Einbinden eben dieser in die eigene Applikation eher schwierig. Das “Live SDK” ist nun unter dem OneDrive Dev Center zu finden und ganz professionell wurden auch alle Links zum alten Live SDK damit unbrauchbar gemacht. Beim Microsoft Account ist es auch unmöglich […]

  • image.png
    Zeitgesteuerte Azure WebJobs – so einfach kann Azure sein

    Das noch in Entwicklung befindliche Azure WebJob SDK bietet einige coole Features zum Verarbeiten und Bereitstellen von Daten. Bekanntes Beispiel ist das Sample welches auf eine Azure Queue lauscht und sobald ein Item da vorhanden ist anfängt dies zu verarbeiten. Szenario: Zeitgesteuerte Aktivitäten – ohne Queue und co. Mein Szenario war allerdings wesentlich trivialer: Ich […]

  • image.png
    Get Involved in OSS! Ja, aber wie geht das denn mit GitHub?

    Auch im .NET Lager gibt es Bewegung im OSS Bereich und es gibt verschiedene Arten wie man bei einem Open Source Projekt “Contributed”. Was zählt alles zu “Contribution”? Unter “Contribution” läuft eigentlich alles – ob es Fragen/Probleme zu dem Projekt via Issues ist oder Dokumentation nachreicht oder ob man darüber bloggt oder das Projekt vorstellt. […]

  • HowTo: Web.config samt eigener ConfigSection zur Laufzeit ändern

    In dem HowTo geht es darum wie man die Web.config zur Laufzeit ändert und was es dabei zu beachten gilt. Das ganze klappt auch mit komplexeren ConfigSections. Eigene ConfigSection? Vor einer ganzen Weile habe ich mal über das Erstellen einer eigenen ConfigSection geschrieben – im Grunde nutzen wir jetzt fast dieselbe Config. Zur Laufzeit? Startet […]

Amazon Shop

Facebook