Blog Home  Home Feed your aggregator (RSS 2.0)  
artiso Blog - LINQ
Neues rund um's Thema .Net
 
 Friday, November 16, 2007

Ich habe bei mir eine Liste die wiederum eine Liste mit Unterobjekten enthält. Ich möchte nun daraus ein Element der Unterliste mit einer bestimmten ID selektieren. Eine klare Sache für LINQ!

Als Leitfaden habe ich auf 101 LINQ Samples mir folgendes Beispiel rausgesucht:

   1: public void Linq15() {
   2:     List customers = GetCustomerList();
   3:  
   4:     var orders =
   5:         from c in customers,
   6:                 o in c.Orders
   7:         where o.Total < 500.00M
   8:         select new {c.CustomerID, o.OrderID, o.Total};
   9:  
  10:     ObjectDumper.Write(orders);
  11: }

 

Das sieht ganz eifach aus, hat bei mir aber absolut nicht funktioniert. Ich konnte das schon nicht sauber eingeben, da er in Zeile 6 für c keine Intellisense-Unterstützung geboten hat. Das Ganze konnte ich dann lösen, indem ich das etwas umgestellt habe, auf das Beispiel oben übertragen sieht meine Lösung so aus:

   1: public void Linq15() {
   2:     List customers = GetCustomerList();
   3:  
   4:     var orders =
   5:         from c in customers
   6:                from o in c.Orders
   7:         where o.Total < 500.00M
   8:         select new {c.CustomerID, o.OrderID, o.Total};
   9:  
  10:     ObjectDumper.Write(orders);
  11: }

 

Ich habe einfach in Zeile 6 statt des Kommas in der vorherigen Zeile nochmals ein from eingebaut. So funktioniert es bei mir nun, wie ich mir das gewünscht habe.

Friday, November 16, 2007 5:57:01 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Monday, November 12, 2007

LINQ erweitert ICollections, IEnumerable etc. mit einigen Extension Methods wie z.B. ToList() was sehr hilfreich ist. Ich hatte gerade das Problem, dass diese Extensions nicht im Intellisense angezeigt wurden. Nach einigem Suchen habe ich festgestellt, das lag daren, dass ich kein using System.Linq drin hatte, sondern nur auf System.Data.Linq und System.Data.Linq.Mapping. Nachdem ich das using ergänzt habe, funktionierte das problemlos.

Monday, November 12, 2007 6:31:35 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Thursday, October 25, 2007

Auf dieser Seite sind jede Menge Beispiele zum Thema LINQ thematisch geordnet aufgelistet. Hier findet man wirklich für viele Einsatzgebiete entsprechende Beispiele. Wirklich empfehlenswert! 

101 LINQ Samples

Thursday, October 25, 2007 8:30:39 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [1]    |   | 
 Tuesday, September 18, 2007

Ich habe in einer Anwendung ein Objekt, das über ca. 30 List<double> Properties verfügt. Dieses Objekt möchte ich nun mit LINQ to SQL in die Datenbank schreiben. Natürlich unterstützt LINQ 1:n-Beziehungen in Objekten aber in der Datenbank müsste ich dann für jedes List-Property eine eigene Tabelle anlegen und für jedes Property einen eigenen Typ definieren. Das schien mir doch sehr umständlich.

Ich habe dann nach einer Möglichkeit gesucht, die List-Elemente etwas effizieter zu speichern, am einfachsten in einem Feld je Liste, da ich die für die Abfrage eh nicht brauche. Aber wie kann ich die Ausgabe von LINQ in die Datenbank steuern? Wie kann ich erreichen, dass LINQ meine Daten entsprechend formatiert?

Googeln brachte keine vernünftigen Ergebnisse und sonst bin ich auch nicht fündig geworden, aber dann kam die Idee (manchmal ist es schneller erst nachzudenken bevor man googelt, aber das vergisst man oft ;-)). Die Grundidee war, für jede Liste noch ein zusätzliches Property anlegen, das die Daten im Getter und Setter entsprechend formatiert. Die Hoffnung war, wenn ich dieses Property mit einem Column-Attribut versehe, wird LINQ den formatierten Inhalt schreiben und beim Lesen wieder zurückformatieren.

public List<string> Sales { get; set; }
[Column(Name="Sales", DbType = "nvarchar(4000)")]
private string SalesString
{
    get
    {
        return string.Join("|", Sales.ToArray());
    }
    set
    {
        if (value != null)
            Sales = value.Split('|').ToList();
    }
}

 

Ich habe einfach eine Text-Spalte in der Datenbank verwendet und die einzelnen List-Elemente mit einem Trennzeichen verbunden (der Einfachkeit halber habe ich mit einer List of Strings gearbeitet. Für die Tests ist das ausreichend und sollte auf beliebige andere Typen übertragbar sein).

Einen kleinen Stolperstein gibt es noch. Beim Ausführen erhielt ich folgende Meldung:

image

LINQ verwendet Optimistic Locking für das Schreiben und hat wohl Probleme, wenn der Inhalt des Property im Getter verändert wird. Abhilfe schafft hier das zusätzliche Attribut UpdateCheck, also sollte das Attribut zu dem Property so aussehen:

[Column(Name="Sales", DbType = "nvarchar(4000)", UpdateCheck=UpdateCheck.Never)]

 

Hier sind natürlich auch noch andere Möglichkeiten der Datenrepräsentation möglich. Hier mal ein Beispiel mit XML-Serialisierung und diesesmal auch mit Doubl-Werten.

public List<double> Sales { get; set; }
[Column(Name="Sales", DbType = "xml", UpdateCheck=UpdateCheck.Never)]
private string SalesString
{
    get
    {                
        UTF8Encoding encoding = new UTF8Encoding();
        MemoryStream ms = new MemoryStream();
        XmlSerializer xmlSer = new XmlSerializer(typeof(List<double>));
        xmlSer.Serialize(ms, Sales);
        return encoding.GetString(ms.ToArray());
    }
    set
    {
        if (value != null)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            MemoryStream ms = new MemoryStream(encoding.GetBytes(value));
            XmlSerializer xmlSer = new XmlSerializer(typeof(List<double>));
            Sales = (List<double>)xmlSer.Deserialize(ms);
        }
    }
}

 

Dank an Bernhard Gojer für den Lösungsansatz.

Tuesday, September 18, 2007 11:09:29 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Monday, September 17, 2007

Nutzt man LINQ in einer klassischen 2-tier Anwendung, dann funktioniert das wunderbar. Nur leider ist das eher ein Auslaufmodell. Bei modernen Anwendungen kommt häufig ein 3-tier Ansatz vor (SOA etc.). An einer kleinen WCF-Anwendung möchti ich das Problem kurz schildern:

Eine kleine Anwendung soll Daten darstellen und bearbeiten können. nehmen wir einfach mal eine Adress-Verwaltung. Die Anwendung soll service-orientiert augebaut sein, d.h. es gibt einen zetralen Service, der die Schnittstelle zur Datenbank implementiert.

Soll nun eine Liste der Adressen geladen werden, ruft der Client auf dem Service eine entsprechende Methode auf. Diese Methode nutzt nun LINQ um die Adressen aus der Datenbank zu lesen und als List<cAddress> zurückzuliefern. Diese kann der Client nun darstellen. So weit, so gut. Aber was passiert jetzt, wenn eine Adresse bearbeitet wird und diese wieder gespeichert werden soll. Der Client ruft eine entsprechende Methode auf dem Service auf und übergibt das neue Adress-Objekt. Durch die Übertragung zum Client und wieder zurück muss das Objekt serialisiert und wieder deserialisiert werden. Dadurch stellt es eine komplett andere Instanz dar, wenn es wieder beim Service ankommt und hat daher keine Verbindung mehr zum DataContext (vorausgesetzt dieser wäre überhaupt noch vorhanden, dazu müsste der in einer Session aufbewahrt werden, was recht problematisch ist). Wie bekommt man nun das neue Adress-Objekt in die Datenbank?

Die von Microsoft vorgschlagene Vorgehensweise geht über die Attach-Methode. Dabei erzeugt man einfach einen neuen DataContext und fügt das bearbeitete Objekt diesem hinzu. Das Problem dabei ist das Change-Tracking des DataContext. Dieser möchte nämlich wissen, was sich an dem Objekt geändert hat. Führt man nur einen Attach durch, dann bewirkt das gar nichts. Man kann entweder das gesamte Objekt als bearbeitet deklarieren, so wie ich das in diesem Post demonstriert habe, oder man muss ein Referenz-Objekt mit angeben, gegen das das Objekt evrglichen werden kann. Eine weitere Möglichkeit ist auch noch die Zuweisung aller geänderten Eigenschaften nach dem Attach, da ab diesem Zeitpunkt das Change-Tracking alle Änderungen registriert. Das ist alles nicht so schön.

Mit einem kleinen Trick kan man sich hier die Sache etwas einfacher machen, auch wenn das nicht gerade sauber ist. Man kann das Objekt einfach löschen und dann neu hinzufügen. Das angenehme ist, dass der DataContext das recht intelligent handhabt und intern aus dem Delete and Insert ein Update macht. Der Code sieht dann ungefähr so aus:

public void UpdateCompany(LINQTest.Contracts.DataContracts.cContactCompany company)
{
    cContactDataContext db = new cContactDataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=LINQ;Persist Security Info=True;");

    cContactCompany OrigCompany = db.Companies.Single<cContactCompany>(c => c.ID == company.ID);
    if (OrigCompany != null)
    {
        db.Companies.Remove(OrigCompany);
    }
    db.Companies.Add(company);
    db.SubmitChanges();
}

 

Auch wenn das, wie gesagt nicht besonders schön ist, ich konnte noch keine negativen "Nebenwirkungen" feststellen, der Code macht genau das, was ich von ihm erwarte und das auch noch mit relativ schönem SQL, wie mir der SQL-Profiler anzeigt. Erst wird mal ein SELECT gemacht um zu sehen, ob das Element schon vorhanden ist (diesen Punkt kann man sicher noch optimieren):

exec sp_executesql N'SELECT [t0].[ID], [t0].[CompanyName], [t0].[CompanyAddress], [t0].[CompanyTelephone]
FROM [tblCompanies] AS [t0]
WHERE [t0].[ID] = @p0',N'@p0 int',@p0=2

 

Dann wird der Update ausgeführt

exec sp_executesql N'UPDATE [tblCompanies]
SET [CompanyName] = @p1
WHERE [ID] = @p0',N'@p0 int,@p1 nvarchar(12)',@p0=2,@p1=N'Test-Company'

 

Das sieht doch eigentlich sehr schön aus.

Problematisch wird die Sache nun, wenn das Objekt Unterelemente enthält, also z.B. Ansprechpartner. Hier gibt es mit dem Attach keine saubere Lösung undauch der Delete and Insert Ansatz versagt hier zunächst. Es wird einfach für alle untergeordneten Elemente ein Insert ausgeführt, egal ob diese bereits existieren oder nicht. Das Ganze kann man lösen, indem man auch die Unterelemente zunächst löscht.

public void UpdateCompany(LINQTest.Contracts.DataContracts.cContactCompany company)
{
    cContactDataContext db = new cContactDataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=LINQ;Persist Security Info=True;User ID=sa;Password=admin");

    cContactCompany OrigCompany = db.Companies.Single<cContactCompany>(c => c.ID == company.ID);
    if (OrigCompany != null)
    {
        db.Companies.Remove(OrigCompany);

        foreach (cContactPerson p in OrigCompany.Persons)
        {
            db.Persons.Remove(p);
        }
    }
    db.Companies.Add(company);
    db.SubmitChanges();
}

 

Auch hier wird wieder ein schönes Update daraus gebaut.

Also so richtig toll finde ich das Ganze noch nicht. Hier bleibt zu hoffen, dass Microsoft an dieser Stelle schnellstens noch nachbessert. Das sieht im Moment aber eher schlecht aus, wie man hier und hier lesen kann. Vielleicht hat schon jemand von euch hier Erfahrungen gesammelt. Über einen Kommentar würde ich mich freuen, auch über "Bedenken" bezgl. des Delete and Insert Ansatzes. 

Monday, September 17, 2007 7:27:14 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Friday, August 17, 2007

Wie man an meinen letzten Posts sieht, beschäftige ich mich momentan sehr intensiv mit LINQ to SQL. Eine wichtige Lektion habe ich gerade eben gelernt. Folgende Ausgangssituation:

Ich habe eine ASP.Net Anwendung. In einem DAL lese ich per LINQ ein Objekt aus der Datenbank und schreibe das dann in eine Session-Variable. Der Anwender bekommt das Objekt an der Oberfläche angezeigt und kann es verändern. Nun möchte ich das Objekt wieder in die Datenbank zurückschreiben. Fast alle Tutorials zu LINQ beschreiben, wie einfach es ist, Daten via LINQ zu schreiben. Aber dort wird in einer einzigen Funktion erst gelesen, dann werden die Daten verändert und dann wieder zurückgeschrieben. Das tritt ja aber in der Praxis eher selten auf.

Also habe ich mich gefragt, was LINQ tut, wenn ich den DataContext einfach neu instanziere und dann einfach mein Objekt per Add an die Tabelle hinzufüge. Na ja,wie zu erwarten, hat LINQ gemeldet, dass der entsprechende Primary Key bereits existiert. Also was tun? Den gesamten DataContext in die Session legen? Dass das keine gute Idee ist, habe ich ziemlich schnell rausgefunden. Der DataContext skaliert sehr schlecht, da er ein eingebautes Change-Tracking, Identity Tracking und sonst noch ein paar Nettigkeiten hat, die sich aber in einer Web_Anwendung recht eklig verhalten können.

Nach einigem Suchen bin ich dann drauf gekommen, dass es auch noch eine Attach-Methode gibt. Damit konnte ich mein Problem schön lösen. Das Ergebnis für das Schreiben sieht nun so aus:

public void SaveProductVersion(cProductVersion _ProductVersion)
{
    DataContext dataContextProducts = new DataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=ValuePlanner_2008;Integrated Security=True");

    if (_ProductVersion.ID == 0)
    {
        dataContextProducts.GetTable<cProductVersion>().Add(_ProductVersion);
    }
    else
    {
        dataContextProducts.GetTable<cProductVersion>().Attach(_ProductVersion, true);
    }
    dataContextProducts.SubmitChanges();
}

 

Hier wird abgefragt, ob es eine neue Produktversion ist (ID == 0). Wenn ja, dann füge ich die per Add hinzu (die ID wird von LINQ automatisch vergeben). Bei einem bestehenden Projekt verwende ich die Attach-Methode. Ganz einfach, wenn mans weiss. Wichtig ist noch, dass alle Properties des Objektes das Attribut UpdateCheck=none benötigen.

Friday, August 17, 2007 7:53:05 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Thursday, August 16, 2007

Wenn man mit LINQ to SQL arbeitet, dann steht man sehr schnell vor dem Problem, dass man sehen möchte, wass denn LINQ mit der Datenbank so treibt. Der SQL Profiler kann zwar die abgesetzten SQL-Statements anzeigen, die zurückgegebenen Ergebnisse muss mann dann aber von Hand abfragen. Viel eleganter geht es mit dem LINQ to SQL Debug Visualizer von Scott Guthrie. Damit kann man die Abfrage im Debug-Modus von Visual Studio 2008 einfach anzeigen lassen:

Mit dem Execute-Button kann man die Abfrage einfach ausführen und das Ergebnis anzeigen lassen.

Ein echt cooles Tool. Download und eine Installationsanleitung gibt es hier.

LINQ to SQL Debug Visualizer - ScottGu's Blog

Thursday, August 16, 2007 9:53:05 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Saturday, July 28, 2007

Scott Gutherie hat in seinem Blog eine interessante Artikel-Serie zu LINQ to SQL

Data Access Improvements with LINQ to SQL

LINQ to SQL is a built-in OR/M (object relational mapper) in .NET 3.5.  It enables you to model relational databases using a .NET object model.  You can then query the database using LINQ, as well as update/insert/delete data from it.  LINQ to SQL fully supports transactions, views, and stored procedures.  It also provides an easy way to integrate business logic and validation rules into your data model.  Below are some of the articles I've written that explore how to use it:

I'll be adding several more articles to my series above in the weeks ahead.  I think you'll find that LINQ to SQL makes it dramatically easier to build much cleaner data models, and write much cleaner data code.

Saturday, July 28, 2007 3:39:55 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |  |   | 
Copyright © 2008 Thomas. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.
Pick a theme: