Mehr von Jürgen Gutsch

Mehr von Jürgen Gutsch

Empfehlungen von Jürgen Gutsch

Blog-Empfehlungen von Jürgen Gutsch

Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Jürgen Gutsch

ASP.NET und mehr...

News

Performance Tipp: Eine DB Connection global für einen Page Request definieren.

Im Moment arbeite ich gerade an einer kleinen Communitiy Anwendung (ja klein, muss ja nicht immer groß sein) die standardmäßig eine Access Datenbank nutzt. Datenbankzugriffe sehen im Moment so aus, dass ich für jede Abfrage eine neue Datenbankverbindung öffne und anschließend wieder schließe. Eigentlich eine normale Prozedur:

using (OleDbConnection conn = new OleDbConnection(this.ConnectionString))
{
  string cmdText = "DELETE FROM [emails] WHERE [mailid]=?";
  using (OleDbCommand cmd = new OleDbCommand(cmdText, conn))
  {
    cmd.Parameters.Add("mailid", OleDbType.Guid).Value = ID;
    cmd.ExecuteNonQuery();
  }
}

Und ausreichend für einen einzelnen Zugriff. Meine Anwendung macht aber pro Page Request mindestens fünf Zugriffe; im Schnitt sind es sogar mehr.

In einem Thread auf MyCSharp, in dem es um zu schließende Connections beim Verlassen einer Website ging, wurde ich von Peter auf eine Diskussion auf Google Groups aufmerksam gemacht, in der es darum ging Datenbankverbindungen global zur Verfügung zu stellen. Stefan stellte dabei einen interessanter Ansatz vor:

Er definierte eine Klasse, die für das Öffnen und Schließen der Datenbankverbindung zuständig ist und die offne Verbindung hält. Diese Klasse wird in der Global.asax bei Application_BeginRequest instanziert, die Connection wird geöffnet und anschließend wird das erzeugte Objekt im aktuellen HttpContext gespeichert. Bei Application_EndRequest wird das Objekt aus dem HttpContext ausgelesen und die Verbindung geschlossen. Im Prinzip eine einfache und geniale Lösung. Die Connection kann jetzt überall in der Anwendung aus dem HttpContext geholt und genutzt werden.

Da ich wie erwähnt nach Optimierungsmöglichkeiten suchte, lag es nahe Stefans Vorschlag auszuprobieren, zumal mir langsam die Ideen für weitere Optimierungen beim Datenbankzugriff ausgegangen sind.

Die Klasse, die die Verbindung zur Datenbank hält, sieht bei mir folgendermaßen aus:

public class DB
{
  public string ConnectionString { get; set; } 
  private IDbConnection dbConnection;
  public IDbConnection DbConnection
  {
    get
    {
      if (dbConnection == null)
        dbConnection = new OleDbConnection(ConnectionString);
      if (dbConnection.State == ConnectionState.Closed 
            || dbConnection.State == ConnectionState.Broken)
        dbConnection.Open();
      return dbConnection;
    }
  }
  public DB(string ConnectionString)
  {
    this.ConnectionString = ConnectionString;
  } 
  public void Open()
  {
    dbConnection = new OleDbConnection(ConnectionString);
  } 
  public void Close()
  {
    if (dbConnection != null)
    {
      dbConnection.Close();
      dbConnection.Dispose();
    }
  }
}

Wichtig ist hier die Eigenschaft DbConnection. Diese instanziert die Datenbankverbindung erst wenn sie das erste mal benötigt wird. Falls keine Connection benötigt wird, enthält diese Klasse nichts anderes als einen Connection String. Das ist auch deswegen von Vorteil, da im IIS7 (IMHO nur im Integrated Pipeline Mode) und im Development Webserver des Visual Studio jeder Request (auch Bilder, CSS und JavaScripts) die Event Handler Application_BeginRequest und Application_EndRequest auslöst. Es wird also somit nicht bei jedem Request eine Datenbankverbindung geöffnet.

So sehen die Event Handler in der Global.asax aus:

void Application_BeginRequest(object sender, EventArgs e)
{
  DB db = new DB(ConfigurationManager.ConnectionStrings
        ["DefaultConnection"].ConnectionString);
  HttpContext.Current.Items["DB"] = db;

void Application_EndRequest(object sender, EventArgs e)
{
  DB db = HttpContext.Current.Items["DB"] as DB;
  if (db != null)
    db.Close();
}

In meine Basisklassen habe ich nun ebenfalls einen neue Eigenschaft eingefügt, welche mir die Datenbankverbindung aus dem HttpContext holt:

private DB db;
public IDbConnection DbConnection
{
  get
  {
    if (db == null)
      db = Context.Items["DB"] as DB;
    return db.DbConnection;
  }
}

Meine Datenbankabfragen kann ich somit nun um ein paar Zeilen erleichtern:

string cmdText = "DELETE FROM [emails] WHERE [mailid]=?"
using (OleDbCommand cmd = 
    new OleDbCommand(cmdText, this.DbConnection as OleDbConnection)) 

  cmd.Parameters.Add("mailid", OleDbType.Guid).Value = ID; 
  cmd.ExecuteNonQuery(); 
}

Nachdem ich das nun komplett in meine Anwendung integriert hatte, waren schon alleine beim Durchklicken eine deutliche Verbesserungen der Performance zu spüren.

Peter hatte in dem Thread zusätzlich den Vorschlag gemacht die Methode so weit zu verfeinern, dass nur Page Handler, bzw. Handler dessen Dateiendung "*.aspx" ist. die Klasse für die DB Verbindung instanzieren dürfen. Er geht sogar soweit, die Instanzierung auch für HttpHandler zu erlauben, die ein spezielles Marker Interface implementiert haben.

Das sieht dann so aus, dass erst ein Marker Interface definiert werden muss:

public interface IRequiresDbConnection{}

Die Implementierung des Interfaces:

public partial class myhandler : IHttpHandler, IRequiresDbConnection
{
[...]
}

Da die Handler bei Application_BeginRequest noch null sind, muss die Instanzierung unserer Klasse und die Abfrage auf den Handler, z. B: im Event Handler Application_PreRequestHandlerExecute erfolgen:

void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
  IHttpHandler handler = (sender as HttpApplication).Context.CurrentHandler;
  if (handler != null && (handler is Page || handler is IRequiresDbConnection))
  {
    DB db = new DB(ConfigurationManager.ConnectionStrings
        ["DefaultConnection"].ConnectionString);
    HttpContext.Current.Items["DB"] = db;
  }
}

Da ich die eigentliche Verbindung nur öffne, wenn sie das erste mal benötigt wird und im Prinzip jedes Webform und jeder Generic Handle allein wegen der Authentifizierung eine Datenbankverbindung benötigt, halte ich es in - meinem Fall - nicht für erforderlich Peters Vorschlag mit dem Marker Interface zu arbeiten.

Wie bereits erwähnt, ist in einer Anwendung mit vielen Datenbankzugriffen eine deutliche Performancesteigerung zu spüren, wenn pro Request nur eine Verbindung zur Datenbank geöffnet wird.

Danke euch beiden (Stefan und Peter) für den wertvollen Tipp :-)

Posted: Freitag, 16. Mai 2008 23:31 von Jürgen Gutsch

Kommentare

jolli sagte:

[quote]

zumal mir langsam die Ideen für weitere Optimierungen beim Datenbankzugriff ausgegangen sind.

[/quote]

wie wärs mit sql server express? :D access ist nicht für web applikationen geeignet. microsoft sagt das in diversen whitepapers selbst. bei bedarf kann ich dir nen link dazu posten. dh: immer wenns geht, sql server einsetzen.

# Mai 16, 2008 23:42

Jürgen Gutsch sagte:

Hallo Jolli,

Access ist durchaus für Webanwendungen geeignet, nur nicht für Anwendungen mit großem Zugriffszahlen (parallele Zugriffe) ;-)

Der eigentliche Grund gegen SQL Server Express, war der, dass ich bei meinem Webhosting Provider SQL Server Express nicht zum laufen gebracht habe. Jetzt ist die Anwendung halt mit einem DAL augestattet, der bei Bedarf bequem augetauscht werden kann. Es ist also möglich einen DAL zu schreiben, der auf SQL Server Express zugreifen kann, ohne die Anwendung an sich anfassen zu müssen.

# Mai 16, 2008 23:55

jan223 sagte:

Hallo,

du hättest das böse Wort das mit "Acc" anfängt und mit "ess" endet, nicht verwenden dürfen. :-)

Sicher ist dieses Dateiformat für die Datenhaltung für Webanwendungen geeignet! Nur in Zeiten von SQL Server Express ist es einfach nur überflüssig.

Jan

# Mai 17, 2008 23:35

Jürgen Gutsch sagte:

*G* so schlimm ist Access nun auch wieder nicht. Aber das ist eine andere Geschichte. ;-)

IMO bringt diese Methode auch mit SQL Server Express eine Performance steigerung.

# Mai 18, 2008 00:29

Andre Loker sagte:

Ich vermute, dass die Performance Probleme mit dem Sql Server dank Connection Pooling deutlich weniger bis nicht existent wären, kann da jetzt aber auch keine Vergleichszahlen nennen.

http://www.codeproject.com/KB/dotnet/ADONET_ConnectionPooling.aspx

Interessanterweise verfährt NHibernate standardmäßig sogar so, nach jeder Transaktion die Verbindung zu "schließen" und ansonsten auf das Connection Pooling zu setzen.

Grüße,

Andre

# Mai 18, 2008 10:01

KaiGloth sagte:

Hallo zusammen

wie schon angesprochen ist dieses Problem bei dem SQL-Server aufgrund des Connection-Poolings nicht vorhanden. Wir der SQL Server verwendet gilt meiner Meinung nach die Devise, die Verbindung so spät wie möglich zu öffen und so schnell wie möglich wieder zu schließen. Insbesondere bei Anwendungen mit hohen Zugriffszahlen ist das wichtig.

Gruß,

Kai

# Mai 23, 2008 09:27

Jürgen Gutsch sagte:

Hallo Kai, Hallo Andre,

Danke für Eure Kommentare, Ihr habt natürlich beide recht ;-)

@All: Eventuell hatte ich den Titel falsch gewählt, denn eigentlich ging es mir hierbei um die beschreibene Performancesteigerung mit MS Access. Ganz egal ob es jetzt sinnvoll ist Access zu nutzen oder nicht ;-)

# Mai 26, 2008 08:39
Anonyme Kommentare sind nicht zugelassen