Zur Zeit wird gefiltert nach: ef1
Filter zurücksetzen

Entity Framework 4 und der 2nd-Level Cache

Von Haus aus bringt das Entity Framework keine erweiterte Cache-Funktionalität mit. Wer für Projekte solche Szenarien benötigt, kann aber auf die EFProviderWrappers zurückgreifen. In früheren Beiträgen habe ich aus diesem Toolkit der Möglichkeiten des EFTracingProviders beschrieben. Vor allem gefällt mir hier, dass ich das generierte SQL an spezifische Vorstellungen anpassen kann. Es gibt DBA's die sind in dieser Hinsicht wenig kompromissbereit.

Es lassen sich durch dieses Toolkit auch eigene Provider realisieren.

Mit dem EFCachingProvider besteht die Möglichkeit, dem Entity Framework einen 2nd-Level Cache zu verpassen. So kann es auch weiter zu NHibernate aufschliessen.

Cachevarianten die zur Verfügung stehen sind:

  • InMemoryCache (einfache Variante, die nicht sehr leistungsfähig ist)
  • Der ASP.NET Cache
  • und Velocity (AppFabric)

Gerade mit Velocity steht dann ein sehr mächtiges Caching zur Verfügung. Haufig werden jedoch Probleme mit dem Caching berichtet, wenn View-Generation benutzt wird, da dass Caching mit den Hashes nicht umgehen kann. Dieses Phänomen konnte ich nicht reproduzieren, es liegt evtl. daran, dass ich diese Views mittels T4 und dem zur Verfügungen stehenden EntityViewGenerator erstellen lasse.

Mit der CachingPolicy besteht zudem die Möglichkeit, dass ich definieren kann, ob einzelne Entitäten oder alle zwischengespeichert werden sollen, es ist jedoch nicht möglich, eine Art "partitioniertes" Caching auf einzelne Entitäten eines Sets zu definieren.

Damit das Caching funktioniert, muss der entsprechende Wrapper um die normale Verbindung "gewickelt" werden, das geschieht beim Konstruktor mit folgender Helperklasse:

Provider-Wrapper C#
public ORMSamplesEntities()
            : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                        ConnectionString,
                        "EFTracingProvider",
                        "EFCachingProvider"
                ), ContainerName)
        {
            Initialize();
        }

In diesem Szenario wird die Verbindung mit dem Tracing- und Caching-Provider umhüllt. In meinem Context habe ich zudem 2 statische Eigenschaften angelegt, die den Zugriff auf die Einstellungen an die Basisklasse weiterleiten:

Eigenschaften für die Cache-Initialisierung C#
public static ICache DefaultCache
{
  get
  {
    return EFCachingProviderConfiguration.DefaultCache;
  }
    		
  set
  {
    EFCachingProviderConfiguration.DefaultCache = value;
  }
}    	
    	
public static CachingPolicy DefaultCachingPolicy
{
  get
  {
    return EFCachingProviderConfiguration.DefaultCachingPolicy;
  }
    		
  set
  {
    EFCachingProviderConfiguration.DefaultCachingPolicy = value;
  }
}

Ein interessantes Detail, diese Eigenschaften müssen vor der Erstellung einer Instanz des Kontexts gesetzt werden, da mit diesen Angaben die Wrapper in den Konstruktoren gefüttert werden.

Machen wir ein kleines Testbeispiel mithilfe des SQL-Profilers und einem Unit-Test. Es soll 4-mal die gleiche Order geladen werden. Mit den Standardmöglichkeiten des Entity Frameworks wird dann die gleiche Abfrage 4-mal an den Datenbankserver gesendet.

Test ohne Cache C#
    [TestMethod]
    public void NonCachingProviderTest()
    {
      // Arrange
      var ctx = new ORMSamplesEntities();

      // Act
      var result = (from o in ctx.Orders.Include("OrderItems")
                   where o.Id == 1
                   select o).ToList();
      
      var result2 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();

      var result3 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();

      var result4 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();
    
      // Assert
      // see Sql-Profiler log ;-)
    }

Im SQL-Profiler wurden während diesem Test 4 Abfragen mit dem exakt gleichen Inhalt protokolliert.

Abbildung 1
Abbildung 1 Protokoll der Abfragen die bei der Ausführung der Unit-Tests verursacht worden sind.

Führen wir den Test erneut aus, diesmal jedoch mit aktivierten 2nd-Level Cache und dem InMemory-Cache.

Test mit Cache C#
    [TestMethod]
    public void CachingProviderInMemoryTest()
    {
      ORMSamplesEntities.DefaultCache = new InMemoryCache();
      ORMSamplesEntities.DefaultCachingPolicy = CachingPolicy.CacheAll;

      // Arrange
      var ctx = new ORMSamplesEntities();

      // Act
      var result = (from o in ctx.Orders.Include("OrderItems")
                    where o.Id == 1
                    select o).ToList();

      var result2 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();

      var result3 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();

      var result4 = (from o in ctx.Orders.Include("OrderItems")
                     where o.Id == 1
                     select o).ToList();

      InMemoryCache cache = ORMSamplesEntities.DefaultCache as InMemoryCache;
      // Assert
      Assert.AreEqual(3, cache.CacheHits);
      Assert.AreEqual(1, cache.CacheMisses);
      Assert.AreEqual(0, cache.CacheItemInvalidations);
      Assert.AreEqual(1, cache.CacheItemAdds);

      // see Sql-Profiler log
    }

Der SQL-Profiler hat nun während der Ausführung nur eine Abfrage protokolliert. Beim 2., 3. und 4. Aufruf wurden die Linq-Abfragen aus dem Cache bedient.

Abbildung 2
Abbildung 2 Log SQL Server Profiler während des Test. Die 4 Linq-Abfragen führen nur noch zu einer Abfrage an den Datenbankserver.

Die Provider-Wrapper sind nicht nur ein "nettes Spielzeug", sondern auch sehr hilfreich, da diese fehlende Funktionalitäten für das Entity Framework bereitstellen.

Ich gehe stark davon aus, das mit der nächsten Version - ich rede hier jetzt nicht von der EF CTP 4 - diese Funktionalitäten fester Bestandteil des Entity Frameworks werden.

Zurück

Entity Framework 4 – Query Rewrite mit EFTracingProvider simulieren

Mit dem EFTracingProviderToolkit gibt es neben der Protokollierung der SQL-Befehle noch eine weitere interessante Möglichkeit das SQL anzupassen. Es ist damit beispielsweise denkbar eine Art Dictionary für schlecht generierte SQL-Befehle aufzubauen, die durch effizientere Abfragen ersetzt werden.

Möglich macht dies das Providermodell und das Event CommandExecuting, mit dessen Hilfe das generierte SQL zu Tage gefördert wird. Innerhalb des Events wird dieses über die Eigenschaft Command der CommandExecutionEventArgs im Lese- und Schreibzugriff bereitgestellt.

Probieren wir es an folgendem Beispiel:

Abbildung 1
Abbildung 1 Beispielmodell

Hier soll jetzt die Aufgabe darin bestehen, zu allen Bestellungen die Anzahl der Bestellpositionen und das Total zu ermitteln.

Die Linq-Abfrage dazu:

C# (Linq)
var resultList = from o in ctx.Orders.Include("OrderItems")
                        select new
                        {
                          Id = o.Id,
                          AnzahlItems = o.OrderItems.Count,
                          Total = o.OrderItems.Sum(s => s.Price * s.Quantity)
                        };

Für diese Abfrage wird folgendes SQL-Statement erstellt:

SQL (EF-Generiert)
SELECT 
[Project1].[Id] AS [Id], 
[Project1].[C1] AS [C1], 
(SELECT 
	SUM([Filter2].[A1]) AS [A1]
	FROM ( SELECT 
		[Extent3].[Price] * [Extent3].[Quantity] AS [A1]
		FROM [QuerySchema].[OrderItem] AS [Extent3]
		WHERE [Project1].[Id] = [Extent3].[OrderId]
	)  AS [Filter2]) AS [C2] -- Sub-Select
FROM ( SELECT 
	[Extent1].[Id] AS [Id], 
	(SELECT 
		COUNT(1) AS [A1]
		FROM [QuerySchema].[OrderItem] AS [Extent2]
		WHERE [Extent1].[Id] = [Extent2].[OrderId]) AS [C1]
	FROM [QuerySchema].[Order] AS [Extent1]
)  AS [Project1]

Dieses Statement verwendet ein Sub-Select für die Bildung der Summe für das Total. Handgeschrieben könnte es in etwa so aussehen:

SQL
SELECT
    o.Id
    , COUNT(oi.Id) as C1 -- AnzahlItems
    , SUM(oi.Price * oi.Quantity) as C2 --Total
FROM QuerySchema.[Order] o
INNER JOIN QuerySchema.OrderItem oi ON oi.OrderId = o.Id
GROUP BY
o.Id

Werden die beiden Abfragen im Ausführungsplan gegenübergestellt, so ist die handgeschriebene Abfrage auch etwas weniger ressourcenhungrig.

Abbildung 2
Abbildung 2 Ausführungsplan der Abfragen in Relation

Mit dem EFTracingProvider wird es möglich, das SQL auszutauschen. So kann mithilfe eines Dictionary das bestehende SQL durch das effizientere ersetzt werden. Quasi kann so das Query Rewrite simuliert werden.

In diesem Beispiel kann im Kontext des Event CommandExecuting abonniert werden. Der Codeausschnitt dafür sieht so aus:

C# (Codeausschnitt)
   [TestMethod]
    public void ChangeGeneratedSqlTest()
    {
      // Arrange
      string fileContent;
      string efGeneratedSqlString;
      var ctx = new ORMSamplesEntities();
      ctx.CommandExecuting += ctx_CommandExecuting;
      
      this.queryRewriteSqlString = @"SELECT
	            o.Id
	            , COUNT(oi.Id) as C1 -- AnzahlItems
	            , SUM(oi.Price * oi.Quantity) as C2 --Total
            FROM QuerySchema.[Order] o
            INNER JOIN QuerySchema.OrderItem oi ON oi.OrderId = o.Id
            GROUP BY
            o.Id";

      // Act
      // ctx_CommandExecuting
      var resultList = from o in ctx.Orders.Include("OrderItems")
                        select new
                        {
                          Id = o.Id,
                          AnzahlItems = o.OrderItems.Count,
                          Total = o.OrderItems.Sum(s => s.Price * s.Quantity)
                        };

      efGeneratedSqlString = (resultList as ObjectQuery).ToTraceString();
      // ctx_CommandExecuting
      resultList.ToList();

      fileContent = File.ReadAllText(this.filePath);

      // Assert
      Assert.IsTrue(fileContent.Contains(this.queryRewriteSqlString));
      Assert.IsFalse(fileContent.Contains(efGeneratedSqlString));
      ctx.Dispose();
    }

    private void ctx_CommandExecuting(object sender, CommandExecutionEventArgs e)
    {
      e.Command.CommandText = this.queryRewriteSqlString;
    }

Mit diesem Event sind aber auch andere Szenarien denkbar.

Ein Hinweis zum Schluss, dieses Beispiel rechtfertigt in meinen Augen nicht unbedingt, das SQL zu ersetzen, es soll nur die Möglichkeit des simulierten Query Rewrites mit dem EFTracingProvider aufzeigen. Zum Einsatz kann es auch bei kompromisslosen Richtlinien kommen, zum Beispiel wenn in einer OLTP-Umgebung der Einsatz von Sub-Selects nicht erlaubt ist. Dann ist diese Variante effizienter als der Austausch des O/R-Mappers in einem fortgeschrittenen Projektstatus, nur wegen ein paar einzelner Statements.

Alternativ kann dafür aber auch ExecuteStoreQuery bzw. eine gespeicherte Prozedur verwendet werden. Es muss jedoch dabei berücksichtigt werden, dass diese beiden Varianten nicht mit dem Laden der Objekthierarchien klarkommen und durch das Nachladen das Select N+1-Problem auftreten kann.

Zurück

Entity Framework 4 – SQL-Optimierung mit Query Rewrite

Query Rewrite ist eigentlich eine Funktionalität von Oracle in Verbindung mit materialisierten Sichten. Mit dem SQL-Server funktioniert das in Verbindung mit indexierten Sichten nur, wenn die Enterprise- oder Developer-Version verwendet wird.

Die Funktionsweise ist denkbar einfach, wenn eine Anwendung Code mit vielen Joins an die Datenbank sendet, kann mit Hilfe einer materialsierten/indexierten Sicht diese optimiert werden. Der Vorteil dieses Features, es können Optimierungen vorgenommen werden, ohne den Code der zugrunde liegenden Applikation anpassen zu müssen.

Am Beispiel eines TPT-Szenarios probiere ich aus, wie sich Query Rewrite mit dem Entity Framework nutzen lässt. Dazu zuerst eine handgeschriebene SQL-Abfrage.

SQL 
SELECT     
	ProductTPT.Product.ProductId
	, ProductTPT.Product.Name
	, ProductTPT.Product.Description
	, ProductTPT.Book.ISBN10
	, ProductTPT.Book.ISBN13
	, ProductTPT.Book.LanguageCd
	, ProductTPT.Book.Pages
	, ProductTPT.EBook.EBookId
	, ProductTPT.EBook.Filename
FROM ProductTPT.Product 
INNER JOIN ProductTPT.Book ON ProductTPT.Product.ProductId = ProductTPT.Book.BookId 
INNER JOIN ProductTPT.EBook ON ProductTPT.Book.BookId = ProductTPT.EBook.EBookId

Bildlich gesehen stellt die Abfrage folgendes Modell dar.

Abbildung 1
Abbildung 1 Modell zum besseren Verständnis

Wenn die Abfrage ausgeführt wird, zeigt der Ausführungsplan den Ablauf in folgender Form:

Abbildung 2
Abbildung 2 Ausführungsplan der Join-Abfrage

Es kann Situationen geben, da führen Join-Abfragen aus der Anwendung heraus zu Performanzeinbussen. Bei Standard-Software besteht dann häufig das Problem, dass diese Abfragen innerhalb der Anwendung nicht optimiert werden können. Hier greift ein DBA häufig auf indexierte Sichten zurück.

Beim SQL-Server muss dazu eine Sicht mit Schemabinding und ein passender gruppierter Index angelegt werden.

SQL (Indexierte View)
CREATE VIEW [dbo].[vwEBook]
WITH SCHEMABINDING
AS
SELECT     
	ProductTPT.Product.ProductId
	, ProductTPT.Product.Name
	, ProductTPT.Product.Description
	, ProductTPT.Book.ISBN10
	, ProductTPT.Book.ISBN13
	, ProductTPT.Book.LanguageCd
	, ProductTPT.Book.Pages
	, ProductTPT.EBook.EBookId
	, ProductTPT.EBook.Filename
FROM ProductTPT.Product 
INNER JOIN ProductTPT.Book ON ProductTPT.Product.ProductId = ProductTPT.Book.BookId 
INNER JOIN ProductTPT.EBook ON ProductTPT.Book.BookId = ProductTPT.EBook.EBookId

Interessant bei dieser indexierten View ist der Abfrageplan, da gibt es nun nicht mehr viel zu sehen:

Abbildung 3
Abbildung 3 Abfrageplan der indexierten Sicht

Intern wird die Sicht wie eine Art Tabelle verwaltet, dadurch entfällt der Join-Aufwand. In der Enterprise- und Developer-Edition des SQL-Servers steht automatisch die "Query Rewrite" – Funktionalität zur Verfügung. Einfach gesagt, wenn der Optimizer merkt, dass eine Join-Abfrage mit einer passenden indexierten Sicht schneller abgearbeitet werden kann, dann wird ein "Query Rewrite" ausgeführt und stattdessen die indexierte Sicht verwendet.

Der folgende Ablaufplan verdeutlicht dies:

Abbildung 4
Abbildung 4 Veranschaulichung von Query Rewrite

Mit diesem Ausflug ist Query Rewrite erklärt. Es gibt aber auch Grenzen dieser Funktionalität, diese werden schnell bei Abfragen mit Inline-Views erreicht, so wie es das Entity Framework auch fabriziert. Betrachten wir nun, was für SQL das Entity Framework an die Datenbank sendet.

Die Abfrage in der Anwendung

C# (Linq-Abfrage)
var ebookList = ctx.Products.OfType<EBook>().ToList();

führt zu folgender SQL-Abfrage:

SQL (Entity Framework)
SELECT 
'0X0X0X' AS [C1], 
[Extent1].[EBookId] AS [EBookId], 
[Extent2].[Name] AS [Name], 
[Extent2].[Description] AS [Description], 
[Extent3].[ISBN10] AS [ISBN10], 
[Extent3].[ISBN13] AS [ISBN13], 
[Extent3].[LanguageCd] AS [LanguageCd], 
[Extent3].[Pages] AS [Pages], 
[Extent1].[Filename] AS [Filename]
FROM   [ProductTPT].[EBook] AS [Extent1]
INNER JOIN [ProductTPT].[Product] AS [Extent2] ON [Extent1].[EBookId] = [Extent2].[ProductId]
INNER JOIN [ProductTPT].[Book] AS [Extent3] ON [Extent1].[EBookId] = [Extent3].[BookId]

Der Unterschied ist nicht sehr gross, lediglich die Spalte C1 unterscheidet sich im groben und ganzen von den anderen Abfragen. Wenn wir diese im Ausführungsplan betrachten, ergibt sich dabei folgendes Bild:

Abbildung 5
Abbildung 5 Übersicht des Abfrageplans der 3 SQL-Statements (Manuell, Sicht, EF-SQL)

Der Unterschied, die Spalte C1 erzeugt zusätzlich einen Compute Scalar-Task, der jedoch keine Kosten verursacht. Der Optimizer merkt, dass er mit der indexierten Sicht die Anfrage viel schneller abarbeiten kann und verwendet diese.

Dieses Beispiel verdeutlicht, dass auch die Datenbank selbst gute Dienste bei der Optimierung leisten kann, sie müssen nur genutzt werden.

Zurück

Entity Framework 4 – Ein neuer Workaround für 1 : 1 – Mapping

Mit EF 4 geht schon einiges viel leichter von der Hand, aber ein paar Dinge funktionieren auch nicht immer, wie man es sich in der Theorie vorstellt bzw. von der ersten Version her gewohnt ist.

Nun wollte ich ein 1 : 1 Mapping im Designer definieren, bei dem der Fremdschlüssel nicht der Primary-Key ist. Die Fehlermeldung, die mich hier begrüsste, lautet so:

Error 113: Multiplicity is not valid in Role 'PersonDetail' in relationship 'FK_PersonDetail_Person'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be *.

Wer auf der grünen Wiese anfängt, dem kann dieses Problem egal sein, da die Datenbankstruktur nicht vorgegeben ist und die Tabellen entsprechend angelegt werden können.

Bei bestehenden Datenbanken kommt es noch häufig vor, das es einen Primärschlüssel auf der Tabelle gibt und einen Fremdschlüssel mit einem Unique Constraint, der die Beziehung abbildet.

Abbildung 1
Abbildung 1 Beispiel-Modell eines 1 : 1 Mapping bei Legacy-Datenbanken

Wenn nun das Modell erstellt und die globale Eigenschaft „Include foreign key columns in the model“ abgewählt wird, dann kann die Beziehung von 1 : * in 1 : 1 abgeändert werden, die Validierung ist erfolgreich und die Funktionalität ist gegeben. Ist diese Checkbox aktiviert, dann funktioniert die Änderung der Beziehung in 1 : 1 nicht mehr.

Abbildung 2
Abbildung 2 Fehlerfreie Modellierung ohne Fremdschlüssel-Eigenschaften
Abbildung 3
Abbildung 3 Validierungsfehler, wenn die Fremschlüssel-Eigenschaft PersonId existiert

Der Grund für dieses Verhalten liegt in den Anpassungen, die an der Version 4.0 vorgenommen worden sind. Wenn die Fremdschlüssel nicht mit in das Modell einbezogen werden, dann werden die Beziehungen über die Navigations-Eigenschaften abgebildet. Dies ist der Standard in EF 3.5 und funktioniert auch in EF 4.0 ohne die Fremdschlüssel-Eigenschaften.

Wenn die Fremdschlüssel-Eigenschaften im Modell abgebildet werden, dann wird eine Beziehung über die skalaren Eigenschaften hergestellt. Durch diese Änderung wird bspw. das unidirektionale Mapping möglich.

Kritiker werden aber hier erneut den Daumen drauflegen und mit der Persistence Ignorance argumentieren und Gerüchten zufolge wird bereits an einer Lösung gearbeitet. ;-)

Bis dahin kann mit ein wenig Codierungsaufwand, die 1 : 1 – Beziehung simuliert werden, da dieser aus meiner Sicht den geringsten Realisierungsaufwand darstellt. Es müssen nur die Include-Anweisungen mit dieser Entität im Auge behalten werden, damit der Workaround später einfacher entfernt werden kann, falls das Verhalten wie in der Vorversion realisiert werden sollte.

Notwendige Schritte für den Workaround:

  • Hinzufügen der Entitäten auf den Modelldesigner
  • Den Getter und Setter für die PersonDetails – Eigenschaft auf private setzen (nach aussen verbergen)
  • Partial-Klasse hinzufügen, die eine Eigenschaft bereitstellt, die nur ein PersonDetail zurückgibt

Der Code der Partial-Klasse sieht dann bspw. so aus:

C# (Person.WorkaroundOneToOne.cs)

namespace OneToOneEF4
{
  using System;
  using System.Linq;

  public partial class Person
  {
    public PersonDetail PersonDetail
    {
      set
      {
        if (this.PersonDetails.Count > 0)
        {
          this.PersonDetails.RemoveAt(0);
        }

        this.PersonDetails.Add(value);
      }

      get
      {
        return this.PersonDetails.FirstOrDefault();
      }
    }
  }
}

Nachteilig ist, dass in diesem Fall Model und Code nicht synchron sind und diese Eigenschaft in Linq-Abfragen nicht angesprochen werden kann.

Zurück

Entity Framework – Performanz mit Compiled Query

Um im Entity Framework die Performanz verbessern zu können, besteht seit EF 1 die Möglichkeit der kompilierten Abfrage. Dieser Ansatz lohnt sich bei komplexen Abfragen, da der Expression Tree nicht jedes Mal abgearbeitet wird, um daraus das SQL-Statement zu generieren. Alternativ dazu kann auch eSQL oder im EF 4 ExecuteStoreQuery genutzt werden. Was mit dieser Funktionalität nicht erreicht werden kann, die Datenbank – SQL Server oder Oracle – kann die Abfrage nicht schneller oder besser ausführen.

Zum Beispiel könnte eine Abfrage so aussehen:

C#
public List<Mandator> GetMandatorsWithBusinessUnits()
{
    int minCount = 10;

    var result = from m in this.Mandators.Include("BusinessUnits")
                 where m.BusinessUnits.Count > minCount
                 select m;

    return result.ToList();
}

In meinem Beispiel sieht das SQL-Statement dazu folgendermassen aus:

T-SQL
exec sp_executesql N'SELECT 
[Project2].[maAutoID] AS [maAutoID], 
[Project2].[maName] AS [maName], 
[Project2].[maSubDirectory] AS [maSubDirectory], 
[Project2].[maBKBeforeRBK] AS [maBKBeforeRBK], 
[Project2].[maBKPercent] AS [maBKPercent], 
[Project2].[maImprint] AS [maImprint], 
[Project2].[maPhone] AS [maPhone], 
[Project2].[maStreet] AS [maStreet], 
[Project2].[maPOBox] AS [maPOBox], 
[Project2].[maPC] AS [maPC], 
[Project2].[maCity] AS [maCity], 
[Project2].[maURL] AS [maURL], 
[Project2].[maVendorAccount] AS [maVendorAccount], 
[Project2].[maAccountingCostCenter1] AS [maAccountingCostCenter1], 
[Project2].[maAccountingCostCenter2] AS [maAccountingCostCenter2], 
[Project2].[C1] AS [C1], 
[Project2].[buAutoID] AS [buAutoID], 
[Project2].[buMandatorAutoID] AS [buMandatorAutoID], 
[Project2].[buName] AS [buName], 
[Project2].[buInactive] AS [buInactive]
FROM ( SELECT 
	[Project1].[maAutoID] AS [maAutoID], 
	[Project1].[maName] AS [maName], 
	[Project1].[maSubDirectory] AS [maSubDirectory], 
	[Project1].[maBKBeforeRBK] AS [maBKBeforeRBK], 
	[Project1].[maBKPercent] AS [maBKPercent], 
	[Project1].[maImprint] AS [maImprint], 
	[Project1].[maPhone] AS [maPhone], 
	[Project1].[maStreet] AS [maStreet], 
	[Project1].[maPOBox] AS [maPOBox], 
	[Project1].[maPC] AS [maPC], 
	[Project1].[maCity] AS [maCity], 
	[Project1].[maURL] AS [maURL], 
	[Project1].[maVendorAccount] AS [maVendorAccount], 
	[Project1].[maAccountingCostCenter1] AS [maAccountingCostCenter1], 
	[Project1].[maAccountingCostCenter2] AS [maAccountingCostCenter2], 
	[Extent3].[buAutoID] AS [buAutoID], 
	[Extent3].[buMandatorAutoID] AS [buMandatorAutoID], 
	[Extent3].[buName] AS [buName], 
	[Extent3].[buInactive] AS [buInactive], 
	CASE WHEN ([Extent3].[buAutoID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
	FROM   (SELECT 
		[Extent1].[maAutoID] AS [maAutoID], 
		[Extent1].[maName] AS [maName], 
		[Extent1].[maSubDirectory] AS [maSubDirectory], 
		[Extent1].[maBKBeforeRBK] AS [maBKBeforeRBK], 
		[Extent1].[maBKPercent] AS [maBKPercent], 
		[Extent1].[maImprint] AS [maImprint], 
		[Extent1].[maPhone] AS [maPhone], 
		[Extent1].[maStreet] AS [maStreet], 
		[Extent1].[maPOBox] AS [maPOBox], 
		[Extent1].[maPC] AS [maPC], 
		[Extent1].[maCity] AS [maCity], 
		[Extent1].[maURL] AS [maURL], 
		[Extent1].[maVendorAccount] AS [maVendorAccount], 
		[Extent1].[maAccountingCostCenter1] AS [maAccountingCostCenter1], 
		[Extent1].[maAccountingCostCenter2] AS [maAccountingCostCenter2], 
		(SELECT 
			COUNT(1) AS [A1]
			FROM [dbo].[tbBusinessUnit] AS [Extent2]
			WHERE [Extent1].[maAutoID] = [Extent2].[buMandatorAutoID]) AS [C1]
		FROM [dbo].[tbMandator] AS [Extent1] ) AS [Project1]
	LEFT OUTER JOIN [dbo].[tbBusinessUnit] AS [Extent3] ON [Project1].[maAutoID] = [Extent3].[buMandatorAutoID]
	WHERE [Project1].[C1] > @p__linq__0
)  AS [Project2]
ORDER BY [Project2].[maAutoID] ASC, [Project2].[C1] ASC',N'@p__linq__0 int',@p__linq__0=10

Wenn Abfragen in dieser Form sehr häufig ausgeführt werden, kann der Einsatz der kompilierten Abfragen etwas bringen.

Der Code erfordert eine Anpassung, damit der Expression Tree nur einmal ausgewertet wird. Hierbei gefällt mir der Aufwand nicht, da es negative Auswirkungen auf die Lesbarkeit des Codes hat. 

C#
private static Func<ContentEntities, int, IQueryable<Mandator>> COMPILEDMANDATOR;

public List<Mandator> GetMandatorsWithBusinessUnitsCompiled()
{
    int minCount = 10;

    if (COMPILEDMANDATOR == null)
    {
        COMPILEDMANDATOR = CompiledQuery.Compile<ContentEntities, int, IQueryable<Mandator>>(
            (ctx, minCountParameter) => from m in ctx.Mandators.Include("BusinessUnits")
                        where m.BusinessUnits.Count > minCountParameter
                        select m);
    }

    return COMPILEDMANDATOR.Invoke(this, minCount).ToList();
}

Diese Form der Abfrage braucht ein geübtes Auge, jedoch wird das Ziel erreicht, wenn die statische Variable deklariert ist, wird der Expression Tree nicht mehr ausgewertet.

Wie bereits erwähnt, kann dieses Feature keine Verbesserungen auf Seite der Datenbank bewirken, wenn das Problem also beim generierten SQL-Statement liegt, wird dieses Feature keine Verbesserung bringen.

Zurück

Translate this page

Kategorien

  • [-].NET Development (215)
  • [-]Datenbank (26)
  • HTML (1)
  • Konfiguration (12)
  • Mind Map (10)
  • Off-topic (9)
  • Open Source (3)
  • Qualität (7)
  • Sharepoint (6)
  • Sicherheit (2)

Archiv

Social Bookmarking

Bookmark bei: Mr. Wong Bookmark bei: Webnews Bookmark bei: Icio Bookmark bei: Oneview Bookmark bei: Linkarena Bookmark bei: Favoriten Bookmark bei: Seekxl Bookmark bei: Favit Bookmark bei: Social Bookmarking Tool Bookmark bei: Power Oldie Bookmark bei: Bookmarks.cc Bookmark bei: Newskick Bookmark bei: Newsider Bookmark bei: Linksilo Bookmark bei: Readster Bookmark bei: Folkd Bookmark bei: Yigg Bookmark bei: Digg Bookmark bei: Del.icio.us Bookmark bei: Reddit Bookmark bei: Simpy Bookmark bei: StumbleUpon Bookmark bei: Slashdot Bookmark bei: Netscape Bookmark bei: Furl Bookmark bei: Yahoo Bookmark bei: Spurl Bookmark bei: Google Bookmark bei: Blinklist Bookmark bei: Blogmarks Bookmark bei: Diigo Bookmark bei: Technorati Bookmark bei: Newsvine Bookmark bei: Blinkbits Bookmark bei: Ma.Gnolia Bookmark bei: Smarking Bookmark bei: Netvouz Information