Skip to content

Instantly share code, notes, and snippets.

@Vivelin
Created April 4, 2018 13:51
Show Gist options
  • Save Vivelin/29eddb80a43c22ca093c5d998f875bc0 to your computer and use it in GitHub Desktop.
Save Vivelin/29eddb80a43c22ca093c5d998f875bc0 to your computer and use it in GitHub Desktop.
An Entity Framework IDbInterceptor that suppresses execution of insert and update statements and instead writes them to a TextWriter.
using System;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.SqlClient;
using System.IO;
/// <summary>
/// Represents an interceptor that writes insert and update statements to a
/// <see cref="TextWriter"/> instead of executing them against a database.
/// </summary>
public class TextWriterDbInterceptor : IDbCommandInterceptor
{
private DbConnection currentConnection;
/// <summary>
/// Initializes a new instance of the <see cref="TextWriterDbInterceptor"/>
/// class with the specified writer.
/// </summary>
/// <param name="writer">
/// A <see cref="TextWriter"/> that receives intercepted statements.
/// </param>
public TextWriterDbInterceptor(TextWriter writer)
{
Writer = writer;
}
/// <summary>
/// Gets a <see cref="TextWriter"/> that receives intercepted statements.
/// </summary>
public TextWriter Writer { get; }
/// <summary>
/// Gets or sets the database connection for the current command.
/// </summary>
protected DbConnection Connection
{
get { return currentConnection; }
set
{
if (value != currentConnection)
{
currentConnection = value;
OnConnectionChanged();
}
}
}
/// <summary>
/// This method is called after a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteNonQuery"/> or one of its
/// async counterparts is made. The result used by Entity Framework can be
/// changed by setting <see cref="P:System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext`1.Result"/>.
/// </summary>
/// <remarks>
/// For async operations this method is not called until after the async task
/// has completed or failed.
/// </remarks>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}
/// <summary>
/// This method is called before a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteNonQuery"/> or one of its
/// async counterparts is made.
/// </summary>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
if (!IsSelectStatement(command))
{
WriteCommand(command);
// In order to suppress execution, we also need to set a non-zero result;
// otherwise, Entity Framework will throw an exception which will prevent
// us from intercepting the other changes
interceptionContext.SuppressExecution();
interceptionContext.Result = -1;
}
}
/// <summary>
/// This method is called after a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteReader(System.Data.CommandBehavior)"/>
/// or one of its async counterparts is made. The result used by Entity
/// Framework can be changed by setting <see cref="P:System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext`1.Result"/>.
/// </summary>
/// <remarks>
/// For async operations this method is not called until after the async task
/// has completed or failed.
/// </remarks>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}
/// <summary>
/// This method is called before a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteReader(System.Data.CommandBehavior)"/>
/// or one of its async counterparts is made.
/// </summary>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (!IsSelectStatement(command))
{
// This is weird...
WriteCommand(command);
interceptionContext.SuppressExecution();
}
}
/// <summary>
/// This method is called after a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteScalar"/> or one of its async
/// counterparts is made. The result used by Entity Framework can be changed
/// by setting <see cref="P:System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext`1.Result"/>.
/// </summary>
/// <remarks>
/// For async operations this method is not called until after the async task
/// has completed or failed.
/// </remarks>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}
/// <summary>
/// This method is called before a call to <see
/// cref="M:System.Data.Common.DbCommand.ExecuteScalar"/> or one of its async
/// counterparts is made.
/// </summary>
/// <param name="command">The command being executed.</param>
/// <param name="interceptionContext">
/// Contextual information associated with the call.
/// </param>
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
if (!IsSelectStatement(command))
{
WriteCommand(command);
interceptionContext.SuppressExecution();
}
}
/// <summary>
/// Occurs when the <see cref="Connection"/> property has changed.
/// </summary>
protected virtual void OnConnectionChanged()
{
Writer.WriteLine("-- {0}", Connection.DataSource);
Writer.WriteLine("USE [{0}]", Connection.Database);
Writer.WriteLine("GO");
Writer.WriteLine("");
}
/// <summary>
/// Writes an SQL statement to the current writer.
/// </summary>
/// <param name="command">A command that represents the SQL statement.</param>
protected virtual void WriteCommand(DbCommand command)
{
Connection = command.Connection;
foreach (SqlParameter parameter in command.Parameters)
{
WriteSqlParameter(parameter);
}
Writer.WriteLine(command.CommandText);
Writer.WriteLine("GO");
Writer.WriteLine();
}
/// <summary>
/// Writes an SQL parameter declaration to the current writer.
/// </summary>
/// <param name="parameter">An SQL parameter to write.</param>
protected virtual void WriteSqlParameter(SqlParameter parameter)
{
Writer.WriteLine("DECLARE {0} {1} {2} = {3}",
FormatName(parameter),
parameter.SqlDbType.ToString(),
FormatDataSize(parameter),
FormatSqlValue(parameter));
}
private static string FormatDataSize(SqlParameter parameter)
{
if (parameter.Size == 0)
return "";
if (parameter.Size == -1)
return "(MAX)";
return $"({parameter.Size})";
}
private static string FormatName(SqlParameter parameter)
{
if (parameter.ParameterName.StartsWith("@"))
return parameter.ParameterName;
return $"@{parameter.ParameterName}";
}
private static string FormatSqlValue(SqlParameter parameter)
{
var value = Convert.ToString(parameter.SqlValue);
return $"'{value.Replace("'", "''")}'";
}
private static bool IsSelectStatement(DbCommand command)
{
return command.CommandText.StartsWith("select", StringComparison.OrdinalIgnoreCase);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment