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.png
    Source Code veröffentlichen – aber bitte mit Lizenz

    Seit es den Blog gibt wird auch meist der gesamte Demo Source Code mit veröffentlicht. Das Ganze hatte ich am Anfang noch als .zip verteilt, später lag es mal auf Google Code und nun liegen alle Samples und sonstige Sachen auf GitHub. Beim letzten User Group Treffen in Zürich mit dem Titel “Open Source: Get […]

  • Fix: Cannot convert from ‘CConnectProxy::_ComMapClass *’ to ‘AddInDesignerObjects::IDTExtensibility2 *’

    Mal einen etwas esoterischer Blogpost, welcher auftaucht wenn man zu viel mit Office Addins rumspielt. Der Fehler passiert beim Bauen von C++ Projekten, welchen diesen Typ benötigen. Lösung (auf 64bit Systemen): C:\Program Files (x86)\Common Files\DESIGNER>regsvr32 MSADDNDR.DLL And Rebuild. Meine lieben Kollegen hatte mir dies schon mehrfach gesagt, allerdings hatte ich es immer wieder vergessen Das […]

  • Gegen das Gesetz verstoßen: X Jahre Haft. Gegen die Terms of Use verstoßen: Bann auf Lebenszeit. Danke Google & co.

    Bei fast allen Diensten die man im Internet nutzen kann muss man den “Terms of use” zustimmen. Völlig logisch dass da natürlich drin steht was erlaubt und was nicht. Wenn man gegen diese Regelungen verstößt hat das Unternehmen natürlich das Recht etwas dagegen zu unternehmen. In der heutigen Welt beherrschen einige wenige Unternehmen die digitale […]

  • image.png
    RSS Feed samt Kommentaranzahl und andere nicht Standard Elemente mit dem SyndicationFeed auslesen

    Jetzt mal ein Blogpost ohne ein fancy NuGet Package: Seit .NET 3.5 gibt es die SyndicationFeed Klasse. Eine schon etwas ältere API, reicht aber aus um Atom bzw. RSS Feeds zu lesen. In diversen RSS Feeds gibt es aber Erweiterungen, welche man natürlich auch auslesen möchte. So gibt WordPress z.B. auch die Anzahl der geposteten […]

  • image.png
    ASP.NET Bundling & Fontawesome

    Vor einer halben Ewigkeit hatte ich mal geschrieben wie man Fontawesome in ASP.NET nutzen kann. Aufgrund des Alters des Post und einem Kommentar ob man denn Fontawesome auch in das ASP.NET Bundling Framework integrieren kann möchte ich nur ein kurzes Update schreiben. TL;DR: NuGet Package “Fontawesome” runterladen und Pfad in der Bundleconfig angeben. Kurze Schritte […]

Amazon Shop

Facebook