ITrace
This interface is used to mark a class to be a trace object.
Methods
Below is the list of methods.
Name | Description |
---|---|
BeforeExecution | Triggered before the actual database operation is being executed. |
AfterExecution | Triggered after the actual database operation has been executed. |
Note: The methods mentioned above has its corresponding Async
methods.
Use-Cases
If you woud like to see and trace the following.
- The actual SQL statements composed by the library.
- The parameter to be used before the execution.
- The actual result of the execution.
- The elapsed execution time.
Also, you can use this class to make an audit and cancel the operation before even the actual execution. Please visit the TraceLog, ResultTraceLog and CancellableTraceLog classes to see more information about the trace logging.
How to Implement?
You have to manually create a class that implements this interface.
public class MyCustomTrace : ITrace
{
public void BeforeExecution(CancellableTraceLog log)
{
// Some implementations here
}
public void AfterExecution<TResult>(ResultTraceLog<TResult> log)
{
// Some implementations here
}
}
With this, you can log the statement like below.
public class MyCustomTrace : ITrace
{
public void BeforeExecution(CancellableTraceLog log)
{
Console.WriteLine($"Before Execution: {log.SessionId}, {log.Key}, {log.Statement}");
}
public void AfterExecution<TResult>(ResultTraceLog<TResult> log)
{
Console.WriteLine($"After Execution: {log.SessionId}, {log.Key}, {log.Statement}");
}
}
Or you can even audit and cancel the operation like below.
public class MyCustomTrace : ITrace
{
public void BeforeExecution(CancellableTraceLog log)
{
var invalidActions = new [] { "INSERT", "UDPATE", "DELETE", "DROP", "ALTER", "EXECUTE" };
if (invalidActions.Any(action => log.Statement.ToUpper().Indexof(action) >= 0))
{
Console.WriteLine($"Before Execution: A suspicious statement has been passed (SQL = {log.Statement}).");
log.Cancel(true);
}
}
...
}
Usability
First, as a recommendation, create a factory class that returns the trace.
public static class TraceFactory
{
private static object _syncLock = new object();
private static ITrace _trace = null;
public static ITrace CreateTracer()
{
if (_trace == null)
{
lock (_syncLock)
{
if (_trace == null)
{
_trace = new MyCustomTrace();
}
}
}
return _trace;
}
}
Then, you can pass it in any extended fluent methods of the DbConnection
object like below.
using (var connection = new SqlConnection(connectionString))
{
var person = connection.Query<Person>(p => p.Id == 10045, trace: TraceFactory.CreateTracer()).FirstOrDefault();
}
Or by passing it on the constructor of the BaseRepository object.
public class PersonRepository : BaseRepository<Person, SqlConnection>
{
public PersonRepository(ISettings settings)
: base(settings.Value.ConnectionString, TraceFactory.CreateTracer())
{ }
}
using (var repository = new PersonRepository(new AppSettings()))
{
var person = repository.Query(p => p.Id == 10045).FirstOrDefault();
}
Or even to the constructor of DbRepository object.
public class DatabaseRepository : DbRepository<SqlConnection>
{
public DatabaseRepository(ISettings settings)
: base(settings.Value.ConnectionString, TraceFactory.CreateTracer())
{ }
}
using (var repository = new DatabaseRepository(new AppSettings()))
{
var person = repository.Query<Person>(p => p.Id == 10045).FirstOrDefault();
}
Always consider to only create a single trace object within the application and have it passed as a singleton object in any operations or repositories, irregardless of your preference. This is the reason why we had created the factory class above.