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...

No comments: