Showing posts with label LINQ. Show all posts
Showing posts with label LINQ. Show all posts

2008-08-31

Mit LINQ to SQL die Anzahl der Datenbankzugriffe verringern

In diesem Beitrag werden zwei LINQ-Abfragen (LINQ-Queries) vergliechen, das gleiche Resultat zurück liefern aber unterschiedlich auf die Daten zugreifen. "Viele Wege führen nach Rom."

Das folgende Beispiel basiert auf SQL Server 2005 und AdventureWorks-Datenbank (Database). Für die Daten werden die Datenbanktabellen Contact und Employee eingesetzt.
image 
Die Datenbanktabellen Employee und Contact sind über die Felder Employee.ContactID und Contact.ContactID miteinander verknüpft. Mithilfe dieser Verknüpfung können wir von einem Employee-Record aus den Contact-Record ermitteln und diese ausgeben.

Das LINQ to SQL-Designer Tool erkennt die Verknüpfung automatisch, wenn wir die beiden Tabelle mittels Drag & Drop auf die Designer-Oberfläche ziehen. Die Verknüpfung zwischen den beiden Tabellen wird von dem LINQ to SQL-Designer Tool generierten Code (siehe AdventureWorks.designer.cs-File) wie folgt dargestellt:

[Association(Name="Contact_Employee", Storage="_Contact", ThisKey="ContactID", OtherKey="ContactID", IsForeignKey=true)]
public Contact Contact
{
get
{
return this._Contact.Entity;
}
set
{
...
}
}
Dieser Verknüpfung ermöglicht uns auf die Contact-Daten zuzugreifen, ohne das wir innerhalb der LINQ-Abfrage (LINQ-Query) auf die Contact-Tabelle refferenzieren müssen. Der Zugriff auf die Contact-Daten erfolgt über das Objekt emp (item.emp.Contact.FirstName), den wir in unseren select definiert haben (select new {emp}).

using System;
using System.Linq;

namespace LinqToSqlRecursiveSample0010
{
internal class Program
{
private static void Main(string[] args)
{
var db = new AdventureWorksDataContext();
var qry = from emp in db.Employees
select new {emp};

Console.WriteLine(String.Format("{0, 10}", "EmployeeID") + "|" +
String.Format("{0, 10}", "ContactID") + "|" +
String.Format("{0, -30}", "Title") + "|" +
String.Format("{0, -15}", "FirstName") + "|" +
String.Format("{0, -10}", "MiddleName") + "|" +
String.Format("{0, -15}", "LastName"));

foreach (var item in qry)
{
Console.WriteLine(String.Format("{0, 10}", item.emp.EmployeeID) + "|" +
String.Format("{0, 10}", item.emp.ContactID) + "|" +
String.Format("{0, -30}", item.emp.Title) + "|" +
String.Format("{0, -15}", item.emp.Contact.FirstName) + "|" +
String.Format("{0, -10}", item.emp.Contact.MiddleName) + "|" +
String.Format("{0, -15}", item.emp.Contact.LastName));
}
}
}
}
Als Resultat erhalten wir die folgende Liste. image 
Wenn wir aber im Hintergrund die Datenbankzugriffe auswerten, stellen wir fest, dass die Applikation für jeden Contact-Record auf die Datenbank zugreift und die Daten aus der Datenbank ermittelt. Die Analyse mit dem SQL Server Profiler-Tool zeigt die Zugriffe wie folgt:image Alle Employee-Daten werden mit einem SELECT-Statement aus der Datenbank (Database) gelesen.

SELECT [t0].[EmployeeID],
[t0].[NationalIDNumber],
[t0].[ContactID],
[t0].[LoginID],
[t0].[ManagerID],
[t0].[Title],
[t0].[BirthDate],
[t0].[MaritalStatus],
[t0].[Gender],
[t0].[HireDate],
[t0].[SalariedFlag],
[t0].[VacationHours],
[t0].[SickLeaveHours],
[t0].[CurrentFlag],
[t0].[rowguid],
[t0].[ModifiedDate]
FROM [HumanResources].[Employee] AS [t0]

Die Contact-Daten werden dann pro Employee-Record aus der Datenbank gelesen (siehe unten - @p0=1209 ist die ContactID).

exec sp_executesql N'SELECT [t0].[ContactID],
[t0].[NameStyle],
[t0].[Title],
[t0].[FirstName],
[t0].[MiddleName],
[t0].[LastName],
[t0].[Suffix],
[t0].[EmailAddress],
[t0].[EmailPromotion],
[t0].[Phone],
[t0].[PasswordHash],
[t0].[PasswordSalt],
[t0].[AdditionalContactInfo],
[t0].[rowguid],
[t0].[ModifiedDate]
FROM [Person].[Contact] AS [t0]
WHERE [t0].[ContactID] = @p0'
,N'@p0 int',@p0=1209

Wenn wir unseren LINQ-Query mit einem join wie folgt erweitern (join con in db.Contacts on emp.ContactID equals con.ContactID) und nicht nur die Employee-Daten sondern auch die Contact-Daten in unser select einschliessen (select new {emp, con}), werden wir feststellen, dass die Contact-Daten nicht mehr einzeln aus der Datenbank gelesen, sondern die Contact-Daten mit Employee-Daten zusammen mit einem SELECT-Statement aus der Datenbank ermittelt werden.

using System;
using System.Linq;

namespace LinqToSqlRecursiveSample0010
{
internal class Program
{
private static void Main(string[] args)
{
var db = new AdventureWorksDataContext();
var qry = from emp in db.Employees
join con in db.Contacts on emp.ContactID equals con.ContactID
select new {emp, con};

Console.WriteLine(String.Format("{0, 10}", "EmployeeID") + "|" +
String.Format("{0, 10}", "ContactID") + "|" +
String.Format("{0, -30}", "Title") + "|" +
String.Format("{0, -15}", "FirstName") + "|" +
String.Format("{0, -10}", "MiddleName") + "|" +
String.Format("{0, -15}", "LastName"));

foreach (var item in qry)
{
Console.WriteLine(String.Format("{0, 10}", item.emp.EmployeeID) + "|" +
String.Format("{0, 10}", item.emp.ContactID) + "|" +
String.Format("{0, -30}", item.emp.Title) + "|" +
String.Format("{0, -15}", item.con.FirstName) + "|" +
String.Format("{0, -10}", item.con.MiddleName) + "|" +
String.Format("{0, -15}", item.con.LastName));
}
}
}
}

Als Resultat erhalten wir weiterhin die gleichen Daten:

image 


Und im SQL Server Profiler-Tool sehen wir, dass die Contact-Daten nicht mehr einzeln ermittelt werden:image

Vom LINQ to SQL generierte SQL-Statement sieht wie folgt aus:

SELECT [t0].[EmployeeID],
[t0].[NationalIDNumber],
[t0].[ContactID],
[t0].[LoginID],
[t0].[ManagerID],
[t0].[Title],
[t0].[BirthDate],
[t0].[MaritalStatus],
[t0].[Gender],
[t0].[HireDate],
[t0].[SalariedFlag],
[t0].[VacationHours],
[t0].[SickLeaveHours],
[t0].[CurrentFlag],
[t0].[rowguid],
[t0].[ModifiedDate],
[t1].[ContactID] AS [ContactID2],
[t1].[NameStyle],
[t1].[Title] AS [Title2],
[t1].[FirstName],
[t1].[MiddleName],
[t1].[LastName],
[t1].[Suffix],
[t1].[EmailAddress],
[t1].[EmailPromotion],
[t1].[Phone],
[t1].[PasswordHash],
[t1].[PasswordSalt],
[t1].[AdditionalContactInfo],
[t1].[rowguid] AS [rowguid2],
[t1].[ModifiedDate] AS [ModifiedDate2]
FROM [HumanResources].[Employee] AS [t0]
INNER JOIN [Person].[Contact] AS [t1] ON [t0].[ContactID] = [t1].[ContactID]

Viel Spass...

2008-05-11

Die let-Klausel (clause) in LINQ

Für die Erstellung einer temporären Variable innerhalb einer Query Expression kann das Schlüsselwort let verwendet werden. Mithilfe einer Formel wird an diese Variable ein neuer Wert aus bestehenden Daten zugewiesen. Die Variable ist dann innerhalb der Query Expression weiterverwendet und bei Bedarf im Select-Block ausgegeben werden.

Die Lesbarkeit des Codes wird durch die Verwendung von Schlüsselwort let gesteigert und der Code bleibt übersichtlich.

In dem folgenden Beispiel werden mithilfe des let-Schlüsselwortes zwei Variablen (DistributorPrice und ProductColorIsBlack) erstellt.

Die Variable DistributorPrice beinhaltet den Wert des Händlerpreises. Dieser Preis wird mithilfe des Faktors 0.75 vom Listenpreis berechnet.
let DistributorPrice = (p.ListPrice*Convert.ToDecimal(0.75))

Die Variable ProductColorIsBlack beinhaltet den Wert, ob die Produktfarbe Schwarz ist.
let ProductColorIsBlack = (p.Color != null && p.Color == "Black")

Innerhalb des Select-Blocks werden die beiden Variablen ausgegeben.
select new
{
ProductCategoryName = pc.Name,
ProductSubCategoryName = psc.Name,
ProductName = p.Name,
p.ListPrice,
DistributorPrice,
ProductColorIsBlack
};

Der gesamte Beispiel Code ist wie folgt aufgelistet:

using System;
using System.Linq;

namespace LinqLetSample0001
{
internal class Program
{
private static void Main(string[] args)
{
var db
= new AdventureWorksDataContext();

var qry
= from pc in db.ProductCategories
join psc
in db.ProductSubcategories on pc.ProductCategoryID equals psc.ProductCategoryID
join p
in db.Products on psc.ProductSubcategoryID equals p.ProductSubcategoryID
let DistributorPrice
= (p.ListPrice * Convert.ToDecimal(0.75))
let ProductColorIsBlack
= (p.Color != null && p.Color == "Black")
select
new
{
ProductCategoryName
= pc.Name,
ProductSubCategoryName
= psc.Name,
ProductName
= p.Name,
p.ListPrice,
DistributorPrice,
ProductColorIsBlack
};

foreach (var item in qry)
{
Console.WriteLine(
"Product Category : " + item.ProductCategoryName);
Console.WriteLine(
"Product Sub Category : " + item.ProductCategoryName);
Console.WriteLine(
"Product : " + item.ProductName);
Console.WriteLine(
"Product Distributor Price: " + item.DistributorPrice);
Console.WriteLine(
"Product List Price : " + item.ListPrice);
Console.WriteLine(
"Product Color is Black : " + item.ProductColorIsBlack);
Console.WriteLine();
}

Console.ReadLine();
}
}
}


Das komplette Visual Studio 2008 Projekt kann unter folgende URL heruntergeladen werden: LinqLetSample0001.zip







2008-04-05

First und FirstOrDefault Operatoren

Mit beiden Operatoren kann das erste Element aus einer Liste ermittelt werden.

Die Operatoren verhalten sich anders, wenn der Abfrage ein leeres Resultat zurück liefert. Der Operator First feuert eine InvalidOperationException. FirstOrDefault Operator liefert einen NULL-Wert zurück.

In dem folgenden Beispiel wird die AdventureWorks-Datenbank als Datenquelle verwendet. Es wird versucht, mittels folgende Abfrage die Produkt zu ermitteln, welche an die Produktekategorie (ProductCategory) Bikes und Produktesub-Kategorie (ProductSubcategory) Mountain Bikes zugeortned sind. Als letzte Bedingung wird der Listenpris (ListPrice) kleiner als 500 vernwendet.

AdventureWorksDataContext db = new AdventureWorksDataContext();
IQueryable<Product> qry = from pc in db.ProductCategories
join psc in db.ProductSubcategories on pc.ProductCategoryID equals psc.ProductCategoryID
join p in db.Products on psc.ProductSubcategoryID equals p.ProductSubcategoryID
orderby p.ListPrice
where pc.Name == "Bikes" && psc.Name == "Mountain Bikes" && p.ListPrice < 500
select p;


Die obige Abfrage liefert keine Produkte zurück, weil in der Datenbanktabelle Product kein Produkt die WHERE-Bedingung entspricht.



Wenn jetzt versucht wird, mit dem Operator First den ersten Produkt zu ermitteln, wird eine Exception gefeuert.



image



Dagegen, wenn der Operator FirstOrDefault verwendet wird, wird als Resultat einen NULL-Wert zurück geliefert.



image



Meine Empfehlung ist, den FirstOrDefault Operator verwenden, wenn das Resultat nicht immer einen Wert zurück liefert.

2008-03-05

LINQ Präsentation, Zürich am 04. März 2008

Vielen Dank für die zahlreiche Teilnahme.

Die Präsentation und die Beispiele können mit folgenden Link heruntergeladen werden.

http://cid-0cea571d70dafee5.skydrive.live.com/self.aspx/%c3%96ffentlich/LinqPraesi20080304

Für die Fragen, findet Ihr meine E-Mail Adresse in der PDF-Datei.

2008-03-01

Umfage: Werden Sie LINQ einsetzen?

Die Resultate sind wie folgt:
Ja:________________________________________________ 20 (42%)
Ja. Ich verwende LINQ bereits in meinen Projekten._ 24 (51%)
Nein:_______________________________________________ 3 ( 6%)
Nein. Ich sehe keinen Mehrwert._____________________ 0 ( 0%)

2008-02-07

Referat - LINQ und LINQ to DataSet

Zürich, 4. März 08

17.45 - 18.15 Uhr Anmeldung & kleiner Apéro
18.15 - 19.15 Uhr Einführung in LINQ und LINQ to DataSet - Özgür Aytekin

19:30 - 20:15 Uhr How does algebraic artifact "Monads" powers LINQ? Or how to control complexity? - Paul Abraham

Anmeldung unter: http://www.digicomp.ch/sitpug

2008-01-24

Databinding und LINQ to DataSet - AsDataView-Variante

Die AsDataView-Methode erstellt aus einem DataTable oder EnumerableRowCollection<T> einen DataView-Objekt. Durch die Verwendung der AsDataView-Methode kann eine Zweiweg-Datenbindung realisiert werden. In einer Zweiweg-Datenbindung können die Daten nicht nur aus dem Quellenobjekt gelesen und in einem User Control dargestellt sondern, die veränderten Daten im User Control werden an das Quellenobjekt weitergegeben.

Auch in diesem Beispiel werden die Daten mithilfe eines SqlDataAdapter-Objektes aus der Datenbank ermittelt und in ein DataSet gefüllt. Die Selektion der Daten erfolgt mit einer einfachen LINQ-Abfrage und das Resultat wird in die Variable qry weitergegeben. Diesmal wird an die DataSource-Eigenschaft der beiden DataGridView-Controls das Resultat der AsDataView-Methode zugewiesen.

private void btnAsDataView_Click(object sender, EventArgs e)
{
string selectSql = @"SELECT [Person].[CountryRegion].[CountryRegionCode],
[Person].[CountryRegion].[Name],
[Person].[CountryRegion].[ModifiedDate]
FROM [Person].[CountryRegion]
";

SqlConnection sqlCon
= new SqlConnection(@"Data Source=.;
Initial Catalog=AdventureWorks;
Integrated Security=True
");

SqlDataAdapter sqlDa
= new SqlDataAdapter();
sqlDa.SelectCommand
= new SqlCommand(selectSql, sqlCon);

DataSet adventureWorksDS
= new DataSet("AdventureWorks");
sqlDa.Fill(adventureWorksDS,
"CountryRegion");

var qry
= from cr
in adventureWorksDS.Tables["CountryRegion"].AsEnumerable()
select cr;

this.dgvDatabindAsDataView.DataSource = qry.AsDataView();
this.dgvDatabindAsDataViewChanges.DataSource = qry.AsDataView();
}


Wenn ein Eintrag in einem DataGridView geändert wird, wird dieser Änderung mithilfe der Datenbindung an das Datenquellen-Objekt weitergeleitet und der entsprechende Eintrag im Datenquellen-Objekt wird auch geändert.

In der folgende Abbildung sind zwei DataGridView-Controls zu sehen. Die beiden Controls verwenden gleiches Objekt als Datenquelle und sie sind mit Datenbindung an das Datenquellen-Objekt gebunden. Die Änderung im linken DataGridView-Control verursacht eine Änderung im Datenquellenobjekt und dieser Änderung wird dann automatisch an den rechten DataGridView-Control weitergegeben. Dieser Aktion war mit der CopyToDataTable-Variante nicht möglich, weil die CopyToDataTable-Methode keine Zweiweg-Datenbindung unterstützt.


clip_image002

Databinding und LINQ to DataSet - CopyToDataTable-Variante

Der Hauptverwendungszweck der CopyToDataTable-Methode ist nicht die Datenbindung sondern, die Daten in ein anderes DataTable-Objekt zu transferieren. Die drei Überladungen der CopyToDataTable-Methode sind wie folgt beschrieben:

Die erste Überladung gibt ein DataTable-Objekt zurück, in dem das Quelle-Objekt in ein DataTable-Objekt umgewandelt wird.

CopyToDataTable<T>(this System.Collections.Generic.IEnumerable<T>)
Die zweite Überladung übergibt die Inhalte des Quellenobjektes in ein anderes DataTable-Objekt. Mit dem LoadOption-Parameter wird festgelegt, wie die Werte von Datenquelle auf die vorhandenen Zeilen verwendet werden sollen.
CopyToDataTable<T>(this System.Collections.Generic.IEnumerable<T>,
System.Data.DataTable,
System.Data.LoadOption)
Die dritte Überladung ist ähnlich wie die zweite Überladung. Zusätzlich kann ein Delegat definiert werden, welches beim Auftreten eines Fehlers während des Übergabeprozesses aufgerufen werden kann.
CopyToDataTable<T>(this System.Collections.Generic.IEnumerable<T>,
System.Data.DataTable,
System.Data.LoadOption,
System.Data.FillErrorEventHandler)
Mit der ersten Überladung zurückgeliefertes DataTable-Objekt kann mit einem DataGridView-Control verknüpft und die Daten in diesem DataGridView dargestellt werden.

Im folgenden Beispiel werden die Daten mithilfe eines SqlDataAdapter-Objektes aus der Datenbank ermittelt und in ein DataSet gefüllt. Eine einfache LINQ-Abfrage selektiert die Daten und das Resultat wird in die Variable qry zurückgegeben. Das Resultat wird mithilfe der CopyToDataTable-Methode in ein DataTable-Objekt konvertiert und an die DataSource-Eigenschaft eines DataGridView-Controls zugewiesen. Mit dieser Zuweisung werden die Daten automatisch im DataGridView-Control dargestellt.

private void btnCopyToDataTable_Click(object sender, EventArgs e)
{
string selectSql = @"SELECT [Person].[CountryRegion].[CountryRegionCode],
[Person].[CountryRegion].[Name],
[Person].[CountryRegion].[ModifiedDate]
FROM [Person].[CountryRegion]
";

SqlConnection sqlCon
= new SqlConnection(@"Data Source=.;
Initial Catalog=AdventureWorks;
Integrated Security=True
");

SqlDataAdapter sqlDa
= new SqlDataAdapter();
sqlDa.SelectCommand
= new SqlCommand(selectSql, sqlCon);

DataSet adventureWorksDS
= new DataSet("AdventureWorks");
sqlDa.Fill(adventureWorksDS,
"CountryRegion");

var qry
= from cr
in adventureWorksDS.Tables["CountryRegion"].AsEnumerable()
select cr;

this.dgvDatabindCopyToDataTable.DataSource = qry.CopyToDataTable();
this.dgvDatabindCopyToDataTableChanges.DataSource = qry.CopyToDataTable();
}
Beim obigen Beispielcode wurde ein Einweg Datenbindung implementiert. Wenn die Daten im DataGridView geändert werden, werden die Änderungen nicht an das Datencontainer-Objekt zurückgegeben. In der folgende Abbildung sind zwei DataGridView-Controls, die ihre Daten über die CopyToDataTable-Methode beziehen. Die Änderung eines Eintrages im linken DataGridView-Control wird nicht direkt an das rechte DataGridView-Control reflektiert.

clip_image002

Die Änderungen können über einen Umweg, wie im folgenden Beispielcode, mit Hilfe der GetChanges-Methode ermittelt werden.

private void btnCopyToDataTableChanges_Click(object sender, EventArgs e)
{
DataTable dt
= (DataTable)this.dgvDatabindCopyToDataTable.DataSource;
this.dgvDatabindCopyToDataTableChanges.DataSource = dt.GetChanges();
}

Mit folgenden Beispielcode können die geänderte Einträge ermittelt und in dem rechten DataGridView-Control angezeigt werden.

clip_image002[12]

Databinding und LINQ to DataSet

DataSet-Objekte werden in den meisten Fällen für die Databinding (Datenbindung) verwendet. Als Datencontainer-Objekt eingesetzte DataSet-Objekte beinhalten die Daten, die mithilfe der Datenbindung innerhalb der Benutzeroberfläche dargestellt werden können. Für die Darstellung dieser Daten ohne Datenbindung-Unterstützung können Entwickler diese Daten aus dem DataSet lesen und in einzelne User Controls weitergeben. Dabei müssen sie sicherstellen, wenn die Daten im Datencontainer-Objekt verändert sind, dass die Inhalte der User Controls aktualisiert werden. Wenn die Inhalte von User Controls verändert sind, müssen die Daten wieder in das Datencontainer-Objekt weitergegeben werden.

Mit Datenbindung können die User Controls mit Datencontainer (zum Beispiel: mit einem DataSet-Objekt) verknüpft und die dargestellten Daten automatisch aktualisiert werden, wenn die Daten im Datencontainer Objekt geändert werden. Auch der Retourweg, wenn die Daten im User Control geändert werden, werden die Daten im Datencontainer-Objekt automatisch aktualisiert.

Für die Datenbindung-Unterstützung bietet LINQ to DataSet die Methoden CopyToDataTable und AsDataView an. Die Methode CopyToDataTable bietet nur Einweg Datenbindung und mit der AsDataView-Methode können Zweiweg Datenbindung realisiert werden. Beim Einweg Datenbindung werden die Daten aus dem Datencontainer-Objekt gelesen und die Daten werden im User Control aktualisiert. Die Zweiweg Variante bietet die Möglichkeit, die Daten im User Control und Datencontainer synchron zu halten.

2008-01-23

Abfragen der Daten mit LINQ to DataSet

Nach dem die Daten aus der Datenbank ermittelt und in ein DataSet-Objekt geladen sind, können diese Daten mit LINQ-Abfragen ausgewertet werden. Für die LINQ-Abfrage stehen die Standard Query Operatoren zur Verfügung. Auch beim Abfragen der Daten gibt es unterschiede, ob ein typisiertes oder untypisiertes DataSet als Datenquelle verwendet wird.

Die AsEnumerable-Methode wird bei einem DataTable-Objekt eines untypisierten DataSet-Objektes verwendet, um das DataTable-Objekt LINQ fähig zu machen. Die Methode Field<T> kommt beim untypisierten DataSet-Objekten aus ins Spiel, wenn der Wert eines Feldes ermittelt oder mit einem anderen Wert verglichen wird.

Der folgende Beispielcode zeigt, wie ein untypisiertes DataSet mithilfe einer LINQ-Abfrage gefiltert werden kann:

private void btnFilterUntyped_Click(object sender, EventArgs e)
{
string selectSql = @"SELECT [Person].[CountryRegion].[CountryRegionCode],
[Person].[CountryRegion].[Name],
[Person].[CountryRegion].[ModifiedDate]
FROM [Person].[CountryRegion]
";

SqlConnection sqlCon
= new SqlConnection(@"Data Source=.;
Initial Catalog=AdventureWorks;
Integrated Security=True
");

SqlDataAdapter sqlDa
= new SqlDataAdapter();
sqlDa.SelectCommand
= new SqlCommand(selectSql, sqlCon);

DataSet adventureWorksDS
= new DataSet("AdventureWorks");
sqlDa.Fill(adventureWorksDS,
"CountryRegion");

var qry
= from cr
in adventureWorksDS.Tables["CountryRegion"].AsEnumerable()
where cr.Field<string>("CountryRegionCode") == "CH"
select cr;

this.dgvLinqToDataSet.DataSource = qry.CopyToDataTable();
}

Die Datenquelle CountryRegion DataTable-Objekt wird mit der AsEnumerable Methode LINQ fähig gemacht.


var qry = from cr in adventureWorksDS.Tables["CountryRegion"].AsEnumerable()

Die Field<T>-Methode ermittelt den Wert des Feldes und hilft das DBNull.Value-Problem zu lösen. Wenn ein Feld in der Datenbank den Null-Wert besitzt, liefert das Feld eines DataTable-Objektes einen DBNull.Value-Wert zurück. Dieser DBNull.Value-Wert entspricht nicht dem Null-Wert des .NET Frameworks und muss speziell behandelt werden. Mit der Field<T>-Methode wird sichergestellt, dass ein DBNull.Value in einen Null-Wert umgewandelt wird.


where cr.Field<string>("CountryRegionCode") == "CH"

Die Auswertung vom typisierten DataSet-Objekt ist einfacher, weil das DataTable-Objekt bereits den IEnumerable<T>-Interface implementiert hat und die Felder direkt ansprechbar sind. In diesem Fall benötigen wir die Methoden AsEnumerable und Field nicht, wie der untenstehende Beispielcode das auch zeigt:


private void btnFilterTyped_Click(object sender, EventArgs e)
{
string selectSql = @"SELECT [Person].[CountryRegion].[CountryRegionCode],
[Person].[CountryRegion].[Name],
[Person].[CountryRegion].[ModifiedDate]
FROM [Person].[CountryRegion]
";

SqlConnection sqlCon
= new SqlConnection(@"Data Source=.;
Initial Catalog=AdventureWorks;
Integrated Security=True
");

SqlDataAdapter sqlDa
= new SqlDataAdapter();
sqlDa.SelectCommand
= new SqlCommand(selectSql, sqlCon);

AdventureWorksTypedDS adventureWorksDS
= new AdventureWorksTypedDS();
sqlDa.Fill(adventureWorksDS.CountryRegion);

var qry
= from cr in adventureWorksDS.CountryRegion
where cr.CountryRegionCode == "CH"
select cr;

this.dgvLinqToDataSet.DataSource = qry;
}


Die Unterschiede der beiden oben beschrieben Abfragen sind in der untenstehenden Tabelle hervorgehoben, damit die Schreibweisen vergleichbar sind.

Untypisiertes DataSet-Objekt


var qry = from cr in adventureWorksDS.Tables["CountryRegion"].AsEnumerable()

where cr.Field<string>("CountryRegionCode") == "CH"

select cr;

this.dgvLinqToDataSet.DataSource = qry.CopyToDataTable();

Typisiertes DataSet-Objekt


var qry = from cr in adventureWorksDS.CountryRegion

where cr.CountryRegionCode == "CH"

select cr;

2008-01-20

DataSets in Visual Studio 2008 und .NET 3.5

Im .NET Framework 3.5 wurde die DataTable-Klasse erweitert, damit das DataTable-Objekt als Datenquelle innerhalb einer LINQ-Abfrage eingesetzt werden kann. In dieser Version erbt die DataTable-Klasse die neue TypedTableBase<T>-Klasse und wird mit dieser Erweiterung LINQ fähig. Die TypedTableBase<T>-Klasse implementiert den IEnumerable<T>-Interface, welches die Voraussetzung ist, wenn ein Objekt als Datenquelle in einer LINQ-Abfrage verwendet werden kann.

07_001

Die TypedTableBase<T>-Klasse wird nur bei typisierten DataSets verwendet, welche mithilfe des Xsd.exe Tools oder mit Visual Studio Designer erstellt wurden. Bei einem untypisierten DataSet kann die AsEnumerable-Methode des gewünschten DataTable-Objektes dazu verwendet werden, wenn das DataTable-Objekt als Datenquelle innerhalb einer LINQ-Abfrage verwendet werden soll.

2008-01-19

Auffüllen eines DataSet mit einem DataAdapter

Ein DataSet-Objekt kann mit Hilfe eines DataAdapter-Objektes mit Daten aus einer relationaler Datenbank gefüllt werden. Dieser Möglichkeit ist die meist verbreitete Variante, wie die Daten in ein DataSet weitergegeben werden können.

An die SelectCommand-Eigenschaft des DataAdapter-Objektes wird die SQL-Anweisung für das Selektieren der Daten aus der Datenbanktabelle zugewiesen. Die Verbindung zur Datenbank wir mit einem SqlConnection-Objekt sichergestellt. Nach dem die zwei Objekte definiert sind, können die Daten mit der Fill-Methode des DataAdapter-Objektes in das DataSet-Objekt gefüllt werden.

Das untenstehende Beispielcode zeigt, wie die Daten der CountryRegion-Datenbanktabelle mit Hilfe eines DataAdapter-Objektes in ein untypisiertes DataSet-Objekt gefüllt wird:

DataSet _AdventureWorksDS;
...
private void btnLoadWithDataAdapter_Click(object sender, EventArgs e)
{
string selectSql = @"SELECT [Person].[CountryRegion].[CountryRegionCode],
[Person].[CountryRegion].[Name],
[Person].[CountryRegion].[ModifiedDate]
FROM [Person].[CountryRegion]
";

SqlConnection sqlCon
= new SqlConnection(@"Data Source=.;
Initial Catalog=AdventureWorks;
Integrated Security=True
");

SqlDataAdapter sqlDa
= new SqlDataAdapter();
sqlDa.SelectCommand
= new SqlCommand(selectSql, sqlCon);

_AdventureWorksDS
= new DataSet("AdventureWorks");
sqlDa.Fill(_AdventureWorksDS,
"CountryRegion");

this.dgvDataAdapter.DataSource = _AdventureWorksDS.Tables["CountryRegion”];
}

2008-01-16

System.Data.DataSetExtensions.dll

Die System.DataSetExtensions.dll Assemlby ist für die Funktionalitäten von LINQ to DataSet notwendig. In dieser Assembly sind die Query Operatoren und Klassen für die DataSet-Objekte vorhanden. Mit Hilfe dieser Klassen und Methoden ist es möglich, LINQ-Abfragen für die DataSet-Objekte zu erstellen und auszuführen. Damit die nötigen Klassen zur Verfügung stehen, muss die System.Data.DataSetExtensions.dll Assemlby vom Visual Studio Projekt aus referenziert sein. Wenn der Namespace System.Data mit using System.Data; bereits importiert ist, stehen auch die Klassen der System.Data.DataSetExtensions.dll Assembly im Programmcode zur Verfügung, weil die Klassen der System.Data.DataSetExtensions auch den System.Data als Namespace besitzen.

Die wichtigsten Klassen der System.Data.DataSetExtesions.dll sind die Folgenden:

· System.Data.DataRowExtensions

· System.Data.DataTableExtensions

In der DataRowExtensions-Klasse finden Sie den Field<T>-Operator. Der Field<T>-Operator kann für die Ermittlung eines Wertes aus einem DataColumn von einem DataRow-Objekt verwendet werden.

Die Methoden AsDataView, AsEnumerable und CopyToDataTable finden Sie in der Klasse DataTableExtensions. Mit der AsDataView Methode können Sie den Resultat einer LINQ-Abfrage in einem DataView-Objekt umwandeln und als Datenquelle für ein DataGridView Control verwenden. Bei einem untypisierten DataSet-Objekt ermöglicht die Methode AsEnumerable die Verwendung von LINQ to Objects-Operatoren, wenn Sie bei einem DataTable-Objekt dieser Methode aufrufen.

Die folgende Codezeile zeigt, ein Beispiel, wie die AsDataView-Methode verwendet werden kann:

AdventureWorksDataSet ds = new AdventureWorksDataSet();
...
dataGridView1.DataSource
= ds.ProductCategory.AsDataView();


In dem folgenden Beispiel wird das ProductCategory DataTable-Objekt für die LINQ-Abfrage als Datenquelle benutzt. Der Aufruf der AsEnumerable-Methode ist in diesem Fall nicht nötig, weil ein typisiertes DataSet als Datenquelle verwendet wird.


AdventureWorks ds1 = new AdventureWorks();
...
var qry1
= from pc in ds1.ProductCategory
where pc.ProductCategoryID == 1
select
new
{
pc.ProductCategoryID,
pc.Name
};


Bei einem untypisierten DataSet kann die obige LINQ-Abfrage wie folgt aussehen:


DataSet ds2 = new DataSet("AdventureWorks");
DataTable productCategory
= ds2.Tables["ProductCategory"];

var qry2
= from pc in productCategory.AsEnumerable()
where pc.Field<int>("ProductCategoryID") == 1
select
new
{
ProductCategoryID
= pc.Field<int>("ProductCategoryID"),
Name
= pc.Field<string>("Name")
};

2008-01-14

LINQ to DataSet

DataSets sind sehr nützliche Elemente für die Datenhaltung im Speicher. Aus der Datenbank gelesenen Daten werden komplett in das DataSet-Objekt eingelesen und stehen für die Weiterverarbeitung innerhalb der Anwendung zur Verfügung.

Die DataSets können in zwei Kategorien, untypisierte und typisierte DataSets, unterteil werden. Beim untypisierten DataSets werden die Tabellen- und Feldnamen als String-Werte mitgegeben. Im typisierten DataSets stehen die Tabellen und Felder als Eigenschaft des jeweiligen Objektes zur Verfügung und bieten Entwickler bessere Intellisense Unterstützung während der Entwicklung.

DataSets können für die Datenhaltung im Speicher verwendet werden. Die Daten werden einmal aus der Datenbank geladen, stehen sie für die Anwendung als Objekte zur Verfügung. Die Unterstützung vom DataSet-Objekten innerhalb des .NET Frameworks vereinfacht die Implementierung von Datenbasierten Anwendung.

Ein DataSet kann mit den Daten von unterschiedlichen Datenquellen gefüllt werden. Inhalt einer XML-Datei oder die Daten einer relationalen Datenbank können in ein DataSet-Objekt übergeben und innerhalb der Anwendung weiter verwendet werden. Die Serialisierung eines DataSets ermöglicht zusätzlich, einen DataSet-Objekt als einen persistenten Datenspeicher zu benutzen. Die Daten können serialisiert und auf einen Datenträger als eine Datei gespeichert werden. Zu einem späteren Zeitpunkt kann der Inhalt dieser Datei wieder in das DataSet-Objekt geladen werden.

Es gibt mehrere Möglichkeiten, wie ein DataSet-Objekt mit Daten gefüllt werden kann. Die meist eingesetzte Variante ist die mit einem DataAdapter die Daten aus der Datenbank ermitteln und in das DataSet-Objekt füllen. Die Daten können mit einem DataAdapter nicht nur gelesen sonder auch wieder an die Datenbank weitergegeben. Bei dieser Variante können SQL-Anweisungen und Stored Procedures für die Ermittlung der Daten oder Weitergabe der Daten an die Datenbank verwendet werden.

Die Find, RowFilter und Select Methoden der DataSet-Klasse können für die Auswertung der Daten innerhalb eines DataSet-Objektes eingesetzt werden. Für die Auswertung dieser Daten wird ein SQL ähnliche Abfrage geschrieben, die als String-Objekt in den Programmcode integriert wird. Die Auswertungsmöglichkeiten mit diesen Methoden sind beschränkt und als String-Objekt vorhandene Abfragen können während des Kompiliervorganges vom Kompiler nicht auf allfällige Fehler geprüft werden.

LINQ to DataSet ermöglicht LINQ-Abfragen für die DataSet-Objekte zu schreiben. Dabei können Entwickler die Standard Query Operatoren für die Auswertung der Daten verwenden. Die Standard Query Operatoren bieten gegenüber dem bisherigen Auswertungsmöglichkeiten vom DataSets viel mehr Funktionalitäten und vor allem vereinfacht das Schreiben von Abfragen. Durch die LINQ-Abfragen müssen Entwickler keine neue Abfragesprache lernen und können die Abfragen mit einer .NET Sprache wie C# oder Visual Basic .NET schreiben. So können die Abfragen vom Kompiler auf Fehler geprüft werden.

Der folgende Codeabschnitt zeigt ein Beispiel, wie ein DataSet-Objekt mit einer LINQ-Abfrage ausgewertet kann.

AdventureWorks ds = new AdventureWorks();

var qry
= from pc in ds.ProductCategory
join psc
in ds.ProductSubcategory
on pc.ProductCategoryID equals psc.ProductCategoryID
select
new
{
pc.ProductCategoryID,
pc.Name,
psc.ProductSubcategoryID,
ProductSubCatName
= psc.Name
};

Wie in diesem Beispiel ersichtlich, unterscheidet sich dieser LINQ-Abfrage von den anderen LINQ-Abfragen nicht. Das gibt Entwickler, auch für die Auswertung von DataSet-Objekten einheitliche Abfragen zu schreiben.

Für den Einsatz von LINQ to DataSets wird der System.Data.DataSetExtensions.dll Assembly benötigt. Die neue Assembly war notwendig, damit die Erweiterte Methoden auch für die DataSets implementiert werden konnten.

2007-12-25

Programmatisch OrderBy oder OrderByDescending bestimmen

Jan Welker hat mich angefragt, wie er programmatisch die Sortierung innerhalb einer LINQ-Abfrage bestimmen kann.

In dem folgenden Beispiel wird gezeigt, wie Sie die Sortierung in einer LINQ-Abfrage programmatisch bestimmen können.

using System;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string orderByDirection = "asc";

var source
= new DataClasses1DataContext().CountryRegions;
IQueryable query
= source.AsQueryable();

Expression
<Func<CountryRegion, string>> lambda = x => x.CountryRegionCode;
LambdaExpression expression
= lambda;

Type[] typeArgs
= new Type[] { query.ElementType, expression.Body.Type };

var mc
= Expression.Call(typeof(Queryable), orderByDirection == "asc" ? "OrderBy" : "OrderByDescending", typeArgs, query.Expression, expression);

IQueryable orderedQuery
= query.Provider.CreateQuery(mc);
}
}
}

Die Variable orderByDirection bestimmt in diesem Fall die Sortierreihenfolge. Mit dem Wert „asc“ wird das Resultat aufsteigend sortiert. Wenn der Wert anders als „asc“ ist, wird die Sortierung absteigend sein.


string orderByDirection = "asc";



Als Datenquelle wird die Datenbanktabelle CountryRegion der AdventureWorks-Datenbank verwendet.


var source = new DataClasses1DataContext().CountryRegions;


IQueryable query = source.AsQueryable();



Mit folgender Zeile definieren wir, welches Feld für die Sortierung verwendet werden soll. Das Feld CountryRegionCode wird in diesem Fall unser Sortierungsfeld sein.


Expression<Func<CountryRegion, string>> lambda = x => x.CountryRegionCode;



Im folgenden definieren wir einen MethodCallExpression.


var mc = Expression.Call(typeof(Queryable), orderByDirection == "asc" ? "OrderBy" : "OrderByDescending", typeArgs, query.Expression, expression);



Mit einer einfachen Abfrage wird bestimmt, welcher Operator für die Sortierung verwendet werden soll. Wenn orderByDirection den Wert „asc“ hat, wird der Operator OrderBy für die Sortierung verwendet. Sonst wird der Operator OrderByDescending für die Sortierung verwendet.


orderByDirection == "asc" ? "OrderBy" : "OrderByDescending"



Als Letztes erstellen wir unsere Abfrage mithilfe der CreateQuery-Methode in dem wir die Variable mc als Parameter für die CreateQuery-Methode definieren.


IQueryable orderedQuery = query.Provider.CreateQuery(mc);



Wenn Sie Interesse an dynamische LINQ-Abfragen haben, kann ich Euch folgenden Text sehr empfehlen.


How to: Use Expression Trees to Build Dynamic Queries


Nochmals danke an Jan Welker wegen seine Anfrage.