Entity Framework 4 - Self Tracking Entities mit EFTracing- und EFCachingProvider - Unterstützung

Im Rahmen einer Präsentation mit Patrick Weibel mit dem Schwerpunkt ORM EF/NHibernate haben wir uns auch mit Tracing und Profiling auseinandergesetzt. Patrick hat eine Zusammenfassung in seinem Blog verfasst, sodass ich mir eine Zusammenfassung sparen kann. ;-)

Im Bezug auf NHibernate zeigte Patrick die Möglichkeiten mit dem NHProfiler auf. Meinen Schwerpunkt legte ich dabei mehr auf den SQL-Server Profiler, sowie dem EFProviderToolkit für das Entity Framework. Beim EFProviderToolkit kommt das Factory-Pattern zum Einsatz. Mit dessen Hilfe kann man sich zwischen die Datenbank und dem Entity Framework platzieren und spezifische Anpassungen vornehmen. Wie das genau funktioniert ist im Detail hier beschrieben.

Solange man mit SQL-Server arbeitet, leistet der SQL Server-Profiler für die Optimierungen gute Dienste und ist auch die beste Schnittstelle zum DBA, da dieser mit dem Tool vertraut sein sollte. Gerade bei Optimierungen auf Datenbankebene ist es von Vorteil, wenn der DBA in seiner gewohnten Umgebung mit einbezogen werden kann.

Der EFTracingProvider kann ebenfalls dazu genutzt werden, um die SQL-Befehle zu protokollieren. Das angelegte Log kann in Verbindung mit den Database Engine Tuning Advisor ebenfalls gute Dienste leisten.

Damit sich das EFProviderToolkit auch mit den Self Tracking Entities nutzen lässt, habe ich mir eine neue T4-Vorlage, basierend auf der bestehenden, erstellt. Hier besteht der Nachteil darin, dass ich alle Änderungen neuer Versionen nachführen muss. Evtl. werde ich das noch Überarbeiten oder zuküftig die Visual Studio Erweiterung nutzen, da so etwas zu viel Arbeit ausarten kann. ;-)

Folgende Grundvoraussetzungen müssen erfüllt sein, damit die Vorlage verwendet werden kann:

  • Zuerst müssen die Referenzen EFCachingProvider, EFProviderToolkit und EFTracingProvider gesetzt werden.
  • In der App.config bzw. Web.Config müssen die Provider hinterlegt werden, damit diese auch vom ObjectContext gefunden werden.
Abbildung 1 Notwendige Referenzen für das EFProviderToolkit
Abbildung 1
Anpassungen in *.config
  <system.data>
    <DbProviderFactories>
      <add name="EF Caching Data Provider" invariant="EFCachingProvider" description="Caching Provider Wrapper" type="EFCachingProvider.EFCachingProviderFactory, EFCachingProvider" />
      <add name="EF Tracing Data Provider" invariant="EFTracingProvider" description="Tracing Provider Wrapper" type="EFTracingProvider.EFTracingProviderFactory, EFTracingProvider" />
      <add name="EF Generic Provider Wrapper" invariant="EFProviderWrapper" description="Generic Provider Wrapper" type="EFProviderWrapperToolkit.EFProviderWrapperFactory, EFProviderWrapperToolkit" />
    </DbProviderFactories>
  </system.data>

Anschliessend kann die Vorlage verwendet werden, aber betrachten wir uns zuerst die Änderungen.

Bei den Using-Direktiven müssen die Provider hinzugefügt werden.

*.Context.tt Zeile 179
#region EFProviderToolkit
using EFCachingProvider;
using EFCachingProvider.Caching;
using EFProviderWrapperToolkit;
using EFTracingProvider;
#endregion

Bei den Konstruktoren muss die base - Definition angepasst werden, damit der Wrapper auch injiziert wird.

*.Context.tt Zeile 34
 public <#=code.Escape(container)#>()
        : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                    ConnectionString,
                    "EFTracingProvider",
                    "EFCachingProvider"
            ), ContainerName)
    {
        Initialize();
    }

    public <#=code.Escape(container)#>(string connectionString)
        : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                    connectionString,
                    "EFTracingProvider",
                    "EFCachingProvider"
            ), ContainerName)
    {
        Initialize();
    }

Danach werden die Anpassungen für das Logging und Caching (2nd level) hinzugefügt.

EFProvider-Erweiterungen in der *.Context.tt - Vorlage
<#	
// part of ef provider toolkit
	EndNamespace(namespaceName);
	fileManager.StartNewFile(Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".ProviderToolkit.cs");
	BeginNamespace(namespaceName, code);
#>
<#= Accessibility.ForType(container)#> partial class <#=code.Escape(container)#>
{
	private TextWriter logOutput;

    #region Tracing Extensions
	/// <summary>
	/// Gets or sets the global name of the log file of EFTracingProviderConfiguration.LogToFile.
	/// </summary> 
	/// <value>The full path and filename.</value>
	public static string LogToFile
	{
		get
		{
			return EFTracingProviderConfiguration.LogToFile;
		}
		
		set
		{
			EFTracingProviderConfiguration.LogToFile = value;
		}
	}

	/// <summary>
	/// Gets or sets a value indicating whether log to console of EFTracingProviderConfiguration.LogToFile.
	/// </summary> 
	/// <value></value>
	public static bool LogToConsole
	{
		get
		{
			return EFTracingProviderConfiguration.LogToConsole;
		}
		
		set
		{
			EFTracingProviderConfiguration.LogToConsole = value;
		}
	}
	
	/// <summary>
    /// Gets or sets the log action to be executed before and after a new connection.
    /// </summary>
    /// <value>The log action.</value>
	public static Action<CommandExecutionEventArgs> LogAction
	{
		get
		{
			return EFTracingProviderConfiguration.LogAction;
		}
		
		set
		{
			EFTracingProviderConfiguration.LogAction = value;
		}
	}
	
    private EFTracingConnection TracingConnection
    {
        get { return this.UnwrapConnection<EFTracingConnection>(); }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandExecuting
    {
        add { this.TracingConnection.CommandExecuting += value; }
        remove { this.TracingConnection.CommandExecuting -= value; }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandFinished
    {
        add { this.TracingConnection.CommandFinished += value; }
        remove { this.TracingConnection.CommandFinished -= value; }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandFailed
    {
        add { this.TracingConnection.CommandFailed += value; }
        remove { this.TracingConnection.CommandFailed -= value; }
    }

    private void AppendToLog(object sender, CommandExecutionEventArgs e)
    {
        if (this.logOutput != null)
        {
            this.logOutput.WriteLine(e.ToTraceString().TrimEnd());
            this.logOutput.WriteLine();
        }
    }
	
    public TextWriter Log
    {
        get { return this.logOutput; }
        set
        {
            if ((this.logOutput != null) != (value != null))
            {
                if (value == null)
                {
                    CommandExecuting -= AppendToLog;
                }
                else
                {
                    CommandExecuting += AppendToLog;
                }
            }

            this.logOutput = value;
        }
    }


    #endregion

    #region Caching Extensions

	/// <summary>
	/// Gets or sets the default ICache implementation for new connections.
	/// </summary>
	/// <value>The default cache.</value>
	public static ICache DefaultCache
	{
		get
		{
			return EFCachingProviderConfiguration.DefaultCache;
		}
		
		set
		{
			EFCachingProviderConfiguration.DefaultCache = value;
		}
	}
	
	/// <summary>
	/// Gets or sets the caching policy to be applied to new connections.
	/// </summary>
	/// <value>The caching policy.</value>
	public static CachingPolicy DefaultCachingPolicy
	{
		get
		{
			return EFCachingProviderConfiguration.DefaultCachingPolicy;
		}
		
		set
		{
			EFCachingProviderConfiguration.DefaultCachingPolicy = value;
		}
	}
	
    private EFCachingConnection CachingConnection
    {
        get { return this.UnwrapConnection<EFCachingConnection>(); }
    }

    public ICache Cache
    {
        get { return CachingConnection.Cache; }
        set { CachingConnection.Cache = value; }
    }

    public CachingPolicy CachingPolicy
    {
        get { return CachingConnection.CachingPolicy; }
        set { CachingConnection.CachingPolicy = value; }
    }

    #endregion
}
<#
	EndNamespace(namespaceName);
    fileManager.Process();
#>

Die neue Vorlage enthält bereits auch die Funktionalitäten für eine 2nd-Level Cache - Unterstützung.

Das ADO.NET-Team hat gestern einen ausführlichen Beitrag zu dem Provider-Toolkit veröffentlicht. In diesem wird auch auf eine Visual Studio-Erweiterung verwiesen, die sich flexibler nutzen lässt.

Es gibt nun 3 Möglichkeiten, die Tracing-Funktionalitäten zu verwenden. Auf config-Ebene kann diese global für jede Kontextinstanz verwendet werden, indem der Pfad der Log-Datei definiert wird:

Globale Konfiguration in Konfig-Datei
<appSettings> 
  <!-- append log messages to the specified file -->
  <add key="EFTracingProvider.logToFile" value="sqllog.txt" />
</appSettings>

Bei der Ausführung der Befehle werden die SQL-Abfragen in einer Log-Datei gespeichert.

Abbildung 2 Ausführung div. Anweisungen im Entity Framework
Abbildung 2
Abbildung 3 Log-Datei der SQL-Anweisungen
Abbildung 3

Die 2. Variante besteht darin, die statische Eigenschaft LogToFile des Kontext-Objekts zu benutzen.

Der nachfolgende Unittest veranschaulicht dies:

UnitTest für statische Eigenschaft LogToFile
namespace EFProviderToolkitTemplateTest
{
  using System;
  using System.Text;
  using System.Collections.Generic;
  using System.Linq;
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using System.IO;
  using EFProviderToolkitTemplate;

  [TestClass]
  public class EFTracingProviderTest
  {
    private string filePath;

    [TestInitialize]
    public void Initialize()
    {
      this.filePath = @"C:\Users\db\Desktop\EfSqlLogTest.txt";
    }

    [TestCleanup]
    public void CleanUp()
    {
      if (File.Exists(this.filePath))
      {
        File.Delete(this.filePath);
      }
    }

    [TestMethod]
    public void LogToFileStaticTest()
    {
      // Arrange
      ORMSamplesEntities.LogToFile = this.filePath;
      string sqlctx1, sqlctx2, fileContent;
      
      // Act
      using (var ctx = new ORMSamplesEntities())
      {
        sqlctx1 = ctx.Products.ToTraceString();
        var list = ctx.Products.ToList();
      }

      using (var ctx2 = new ORMSamplesEntities())
      {
        sqlctx2 = ctx2.Orders.ToTraceString();
        var list = ctx2.Orders.ToList();
      }

      fileContent = File.ReadAllText(this.filePath);
      
      // Assert
      Assert.IsTrue(fileContent.Contains(sqlctx2));
      Assert.IsTrue(fileContent.Contains(sqlctx1));
    }
  }
}

Auch mit dieser Variante werden die generierten SQL-Statements aller Instanzen geloggt.

Die 3. Möglichkeit besteht darin, für eine einzelne Instanz während der Entwicklung die SQL-Befehle zu loggen.

Unittest für Log-Eigenschaft
namespace EFProviderToolkitTemplateTest
{
  ...

  [TestClass]
  public class EFTracingProviderTest
  {
    ...
    [TestMethod]
    public void LogToFileContextTest()
    {
        // Arrange
        string sqlctx1, sqlctx2, fileContent;

        // Act
        using (TextWriter logFile = File.CreateText(this.filePath))
        {
          using (var ctx = new ORMSamplesEntities())
          {
            ctx.Log = logFile;
            sqlctx1 = ctx.Products.ToTraceString();
            var list = ctx.Products.ToList();
          }
        }

        using (var ctx2 = new ORMSamplesEntities())
        {
          sqlctx2 = ctx2.Orders.ToTraceString();
          var list = ctx2.Orders.ToList();
        }

        fileContent = File.ReadAllText(this.filePath);

        // Assert
        Assert.IsFalse(fileContent.Contains(sqlctx2));
        Assert.IsTrue(fileContent.Contains(sqlctx1));
    }
  }
}

Kommen wir zurück zum SQL-Server Profiler. In produktiven Umgebungen gibt es immer wieder mal Situationen, die den Einsatz verunmöglichen, sodass für die Analyse der Statements der EFTracingProvider ein sehr nützliches Hilfsmittel sein kann.

Die Konfigurationsvariante kann hierbei die notwendige Flexibilität gewährleisten.

  •  
  • 0 Kommentar(e)
  •  

Mein Kommentar

Ich möchte über jeden weiteren Kommentar in diesem Post benachrichtigt werden.

Zurück

Translate this page

Kategorien