Unter einem Interceptor versteht man im Allgemeinen eine Komponente, die Aktionen eines Frameworks abfängt und vor, nach bzw. anstatt diesen Aktionen eigene Aktionen zur Ausführung bringt. Bei den Command-Interceptoren, die ab Entity Framework 6 zur Verfügung stehen, handelt es sich um Interceptoren, die das Abfangen von Anweisungen, die zur Datenbank gesendet werden, erlauben. Um einen solchen Interceptor zu implementieren, stellt der Entwickler eine Realisierung des Interfaces IDbCommandInterceptor zur Verfügung. Dieses Interface weist sechs Methoden auf, die Entity Framework jeweils vor bzw. nach dem Ausführen einer SQL-Anweisung zur Ausführung bringt:
NonQueryExecuting: Wird ausgeführt, bevor Entity Framework eine Anweisung, die keine Ergebnismenge retourniert, zur Datenbank sendet. Beispiele hierfür sind INSERT-, UPDATE- oder DELETE-Anweisungen.
NonQueryExecuted: Wird ausgeführt, nachdem Entity Framework eine Anweisung, die keine Ergebnismenge retourniert, zur Datenbank gesendet und diese die Anweisung ausgeführt hat. Innerhalb dieser Methode liegt bereits die Anzahl der Zeilen, die von dieser Anweisung betroffen waren, vor.
ReaderExecuting: Wird ausgeführt, bevor Entity Framework eine Anweisung, die eine Ergebnismenge retourniert, zur Datenbank sendet. Ein Beispiel hierfür ist eine SELECT-Anweisung.
ReaderExecuted: Wird ausgeführt, nachdem Entity Framework eine Anweisung, die keine Ergebnismenge retourniert, zur Datenbank gesendet und diese die Anweisung ausgeführt hat. Innerhalb dieser Methode liegt bereits die abgefragte Ergebnismenge vor.
ScalarExecuting: Wird ausgeführt, bevor Entity Framework eine Anweisung, die einen einzelnen (skalaren) Wert retourniert, zur Datenbank sendet (z. B. SELECT COUNT(*) FROM …)
ScalarExecuted: Wird ausgeführt, nachdem Entity Framework eine Anweisung, die einen einzelnen (skalaren) Wert retourniert, zur Datenbank gesendet und diese die Anweisung ausgeführt hat. Innerhalb dieser Methode liegt der abgefragte Wert bereits vor.
Sämtliche Methoden, welche von IDbCommandInterceptor vorgegeben werden, bekommen von Entity Framework zwei Parameter übergeben: Ein DbCommand, welche die jeweilige SQL-Anweisung repräsentiert und ein Objekt vom Typ DbCommandInterceptionContext, welches Informationen über die Abfrage enthält. Unter diesen Informationen befinden sich das Ergebnis der SQL-Anweisung sowie eine eventuelle Exception, die im Zuge der Ausführung ausgelöst wurde. Setzt ein Command-Interceptor diese Eigenschaften vor der Ausführung der damit assoziierten SQL-Anweisung, führt Entity Framework diese SQL-Anweisung nicht aus, sondern behandelt den vom Interceptor gesetzten Wert als Ergebnis dieser Anweisung bzw. als Exception, im Rahmen der Ausführung dieser Anweisung aufgetreten ist. Auf diese Weise lassen sich zum Beispiel Caching-Mechanismen realisieren.
In den Interceptor-Methoden, die Entity Framework nach dem Ausführen einer SQL-Anweisung anstößt, hat der Entwickler über den DbCommandInterceptionContext Zugriff auf das Ergebnis der Abfrage bzw. auf eine eventuell ausgelöste Exception. Auch hier kann er diese Werte setzen. Dies hat zur Folge, dass der Aufrufer nicht mit dem eigentlichen Ergebnis sondern mit den vom Interceptor gesetzten Werten konfrontiert wird.
Das nachfolgende Listing zeigt ein Beispiel für einen einfachen Command-Interceptor. Dieser gibt die SQL-Anweisung, deren Ausführung ansteht bzw. gerade abgeschlossen wurde, sowie ausgewählte Informationen, die der DbCommandInterceptionContext parat hält, auf der Konsole aus. Im Falle von NonQueryExecuting wird als Ergebnis der Wert 42 definiert, indem der Entwickler die Eigenschaft Result setzt. Dies hat zur Folge, dass Entity Framework die mit diesem Methoden-Aufruf assoziierte SQL-Anweisung nicht zur Ausführung bringt, sowie davon ausgeht, dass das Ergebnis dieser Anweisung 42 ist. Da Entity Framework die Methode NonQueryExecuting vor der Ausführung von DML-Anweisungen, wie INSERT, UPDATE oder DELETE, anstößt, wird dieser Wert als Anzahl der von der Anweisung betroffenen Zeilen gewertet.
Der Datentyp der hier verwendeten Eigenschaft Result wird durch den Typparameter von DbCommandInterceptionContext bestimmt. Im Fall von NonQueryExecuting und NonQueryExecuted handelt es sich dabei, wie im betrachteten Beispiel gezeigt, um einen int, der die Anzahl der betroffenen Zeilen repräsentiert. Bei ReaderExecuting und ReaderExecuted ist Result hingegen vom Typ DbDataReader. Dieser repräsentiert die Ergebnismenge der Abfrage. Bei ScalarExecuting und ScalarExecuted repräsentiert Result den abgefragten Wert und weist deswegen den Datentyp Object auf.
Die im betrachteten Beispiel auskommentierten Zeilen deuten weitere interessante Eigenschaften von DbCommandInterceptionContext hin: Bei DbContexts handelt es sich um eine Auflistung mit den betroffenen DbContext-Instanzen. In der Regel hat diese Liste genau einen einzigen Eintrag. Exception repräsentiert eine eventuell aufgetretene Exception. Da sowohl die Eigenschaft Result als auch die Eigenschaft Exception vom Interceptor verändert werden können, bieten OriginalResult und OriginalException Zugriff auf das eigentliche Ergebnis bzw. auf die eigentliche Exception. Die Eigenschaft IsExecutionSuppressed, welche nur gelesen werden kann, gibt an, ob die Ausführung der SQL-Anweisung übersprungen wird. Dies ist, wie schon erwähnt dann der Fall, wenn der Interceptor das Ergebnis oder die Exception vor der Ausführung der SQL-Anweisung direkt setzt.
public class CustomCommandInterceptor: IDbCommandInterceptor {
public void NonQueryExecuting(
DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Console.WriteLine("NonQueryExecuting");
Console.WriteLine(command.CommandText);
// command.Connection
interceptionContext.Result = 42;
// interceptionContext.DbContexts
// interceptionContext.Exception
// = new Exception("Es ist doch schon Feierabend!");
// interceptionContext.OriginalException
// interceptionContext.OriginalResult
// interceptionContext.IsExecutionSuppressed
Console.WriteLine("");
}
public void NonQueryExecuted(
DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Console.WriteLine("NonQueryExecuted");
Console.WriteLine(command.CommandText);
Console.WriteLine("Result: " + interceptionContext.Result);
Console.WriteLine("");
}
public void ReaderExecuting(
DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Console.WriteLine("ReaderExecuting");
Console.WriteLine(command.CommandText);
Console.WriteLine("");
}
public void ReaderExecuted(
DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Console.WriteLine("ReaderExecuted");
Console.WriteLine(command.CommandText);
Console.WriteLine("");
}
public void ScalarExecuting(
DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Console.WriteLine("ScalarExecuting");
Console.WriteLine(command.CommandText);
Console.WriteLine("");
}
public void ScalarExecuted(
DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Console.WriteLine("ScalarExecuted");
Console.WriteLine(command.CommandText);
Console.WriteLine("");
}
}
mit Entity Framework einen Command-Interceptor verwendet, muss der Entwickler diesen zuvor registrieren. Dies geschieht innerhalb der Konfigurations-Klasse mit der Methode AddInterceptor.
public class CustomDbConfiguration : DbConfiguration
{
public CustomDbConfiguration()
{
AddInterceptor(new CustomCommandInterceptor());
}
}