Enabling your application for profiling
To enable your application for profiling, you have to reference one of the interceptor assemblies:
- SD.Tools.OrmProfiler.Interceptor.NetFull.dll for applications which use .NET 4.5.2 or higher and not Entity Framework. This interceptor is compiled against .NET 4.5.2 and is async aware..
- SD.Tools.OrmProfiler.Interceptor.EFv6.dll for applications which at some point use Entity Framework v6.x. This interceptor is compiled against .NET 4.5.2, is async aware and references Entity Framework v6's dll which is available through nuget.
- SD.Tools.OrmProfiler.Interceptor.NetCore.dll for applications which use .NET Core 2.0 or higher. This interceptor is compiled against .NET Core 2.0.
If you're using ORM Profiler 1.x interceptors in your code today, you likely have to reference an interceptor with a different name from v2.0. Make sure you reference the right interceptor package: if you're using the 'SD.Tools.OrmProfiler.Interceptor' or the 'SD.Tools.OrmProfiler.Interceptor.NET45' interceptor from v1.x, use the 'SD.Tools.OrmProfiler.Interceptor.NetFull' from 2.x
NuGet
The interceptor assemblies are available on NuGet and it's recommended you use these interceptor assemblies instead of the locally installed ones:
- https://www.nuget.org/packages/SD.Tools.OrmProfiler.Interceptor.EFv6/
- https://www.nuget.org/packages/SD.Tools.OrmProfiler.Interceptor.NetFull/
- https://www.nuget.org/packages/SD.Tools.OrmProfiler.Interceptor.NetCore/
Locally installed interceptor assemblies
The interceptor assemblies are also installed with ORM Profiler, and they can be found in the ORM Profiler installation folder\Interceptor\Interceptor of choice folder.
We however recommend you use the NuGet variant
How to initialize the Interceptor
.NET Full
At startup of your application or service, call this method:
SD.Tools.OrmProfiler.Interceptor.InterceptorCore.Initialize("application name");
where application name can be anything, preferably a string which identifies the application, e.g. "The Main WCF Service". It's recommended that you don't specify an empty string, as the name is used to group messages together under the same application: ORM Profiler can profile multiple applications at once and it's key to distinguish information from application X from information from application Y.
Each application instance is assigned a unique ID, even though they might use the same name specified for application name. The unique ID is used to group the messages, however in the UI the name is used to group the data. If you have used the same name multiple times, it will be harder to determine which group of data belonged to which instance.
Where to call Initialize?
If your application consists of multiple parts, e.g. a client which accesses a WCF service, you have to reference the interceptor in the part which accesses the database, in the example of a client accessing a WCF service, you have to reference the interceptor in the WCF service.
The location where to call InterceptorCore.Initialize depends on the type of application you're profiling. Below is a handy guideline where to call Initialize for the various types of applications. In general, the InterceptorCore.Initialize method has to be called before any factory is created by a used ORM / data-access framework. Typically it's best to call the Initialize method at the start of the application.
-
Console application, Winforms Application, Windows Service:
Call InterceptorCore.Initialize from the Main method.- VB.NET Specific: If your application is a Winforms application, it's likely you use the application framework supplied by VB.NET. In that case you have to add an event handler to MyApplication.Startup (or override MyApplication.OnStartup in a partial class) and call InterceptorCore.Initialize() from there. If you don't have an ApplicationEvents.vb file yet, click the button 'View Application Events' on the 'Application' tab of the properties window of your Windows Forms project.
-
WPF application
Call InterceptorCore.Initialize from an override of OnStartup in the App code behind file. -
Webforms, ASP.NET MVC, ASP.NET Web application, WCF Service
Call InterceptorCore.Initialize from the Application_Start() method in the global.asax file -
Unit tests
Call InterceptorCore.Initialize in the testfixture setup method. This makes sure all tests are using the intercepted factories.
After InterceptorCore.Initialize has been called, the DbProviderFactories are wrapped and all database activity executed through ADO.NET by your application is intercepted (unless the ADO.NET objects are created directly, not through the DbProviderFactory system). As soon as you start your application, it will send messages with profiling information to the named pipe the client will listen to.
If you're using LLBLGen Pro on .NET Full and you're using the RuntimeConfiguration
class to configure the runtime for your application, be sure not to
specify a DbProviderFactory using AddDbProviderFactory
, as on .NET Full the DQE should obtain the factory from DbProviderFactories which are intercepted by ORM Profiler. If you do specify a DbProviderFactory using AddDbProviderFactory
on .NET Full, the intercepted factories in DbProviderFactories aren't used by the DQE and no query will be intercepted.
If you are required to use the RuntimeConfiguration
class while your code might run on .NET Full or .NET Standard, you have to specify the DbProviderFactory type as in the situation where your code runs on .NET Standard, it won't have a configured DbProviderFactories table. In that situation you have to use the way it's setup as described below in the #.NET Core 2.0+ section.
For .NET Full, the overload method to call is: (Since v2.0.3 this overload is present in the InterceptorCore for .NET Full.)
InterceptorCore.Initialize(string applicationName, string serverName, Type factoryTypeToWrap)
For .NET Core 2.0+ choose one of these overloads:
InterceptorCore.Initialize(string applicationName, Type factoryTypeToWrap)
// or
InterceptorCore.Initialize(string applicationName, string serverName, Type factoryTypeToWrap)
.NET Core 2.0+
At startup of your application, where a DbProviderFactory is used to initialize your data access technology, you have to use the InterceptorCore to wrap the DbProviderFactory with one provided by the interceptor so it can track the activity and how long each action takes. To do that, use the following method:
using SD.Tools.OrmProfiler.Interceptor;
//...
// example factory here is the SqlClient factory. You should provide the
// factory you're using on .NET Core for your database type, e.g.
// Oracle.ManagedDataAccess.Client.OracleClientFactory for Oracle
var wrappedType = InterceptorCore.Initialize("application name",
typeof(System.Data.SqlClient.SqlClientFactory));
// pass wrappedType now to your data access technology you're using instead of the
// type passed to Initialize();
For instance, if you're using LLBLGen Pro on .NET Core, you should do the following:
RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
c => c.SetTraceLevel(TraceLevel.Verbose)
.AddDbProviderFactory(InterceptorCore.Initialize("application name",
typeof(System.Data.SqlClient.SqlClientFactory)))
.SetDefaultCompatibilityLevel(SqlServerCompatibilityLevel.SqlServer2012));
here we pass the wrapped type we receive from Initialize to the RuntimeConfiguration method to configure the database query engine.
After this, the data access technology will use the factory type provided (and the static instance of the DbProviderFactory it contains) to produce instances for DbCommand, DbConnection and the like.
If your data access technology uses a DbConnection instance instead of a DbProviderFactory type, you can create one from the wrapped factory type by using:
var connection = wrappedType.Instance.CreateConnection();
// now pass 'connection' to your data access technology
Where to call Initialize?
Typically you're call Initialize at the point where you have to provide the DbProviderFactory type or the DbConnection instance to your data access technology, which is likely at startup of your application, before the first query.
Remote profiling
The above call assumes the client, the application which listens and records the profiling data created by your own application, is on the same machine. Initialize has an overload which allows you to specify a different machine. This is an SMB name (e.g. \\servername
), so the machine has to be on the same network.
As profiling data is considered sensitive data, it's not transportable over the internet. In that case, create a snapshot (profile the application) with the command line client (CliOrmProfiler.exe) locally on the server where the application is running and analyze the snapshot later on in the visual gui.
Which dlls to copy to your application folder?
If you have a folder for referenced assemblies in your application's solution and you reference the interceptor dll from there, you have to copy more dlls than just the interceptor dll. The following dlls have to be present for the interceptor to work: (you can just copy them from the ORM Profiler installation folder).
- The referenced interceptor dll (see above), which is either SD.Tools.OrmProfiler.Interceptor.NetFull.dll, SD.Tools.OrmProfiler.Interceptor.EFv6.dll or SD.Tools.OrmProfiler.Interceptor.NetCore.dll)
- SD.Tools.OrmProfiler.Shared.dll
- SD.Tools.BCLExtensions.dll
- SD.Tools.Algorithmia.dll
If you reference the interceptor dll from the ORM Profiler installation folder, Visual Studio will make sure the right dlls are copied to the application's bin folder.
.NET Full: Enabling / Disabling the interceptor from the application's config file.
If you have specified a call to the interceptor's Initialize method but want to disable it from the application's config file instead of re-compiling the application as a whole, you can do so by using the following config file setting in your application's app/web.config file:
<appSettings>
<add key="ORMProfilerEnabled" value="true"/>
</appSettings>
If you already have an appSettings section in your application's .config file, simply add the <add key... > line to that section. By specifiying true the interceptor's Initialize method will proceed as normal. By specifying false the Initialize method will be a no-op. If the setting is absent or misconfigured, the Initialize method will proceed as normal and perform the initialization.
TimeoutException messages during debugging
When you have added a call to InterceptorCore.Initialize in your application and you step through it in the debugger in Visual Studio, you might see messages in the Output window in Visual Studio that a TimeoutException
occurred, repeating itself every second. This happens when you didn't start a profiler client which listens to the named pipe.
The timeout comes from the Connect method of the named pipe client, called by the interceptor's thread in charge of connecting to the named pipe. Unfortunately, Microsoft designed this Connect method in such a way that it only returns after a timeout occurres or it connects, while it loops in a tight loop which consumes 100% of the available CPU cycles.
To avoid wasting resources on this busy-wait loop inside the .NET framework, the interceptor specifies a short interval (1ms) for the Connect method and repeats that every second, causing the Connect method to time-out, and thus raising this exception. It's harmless and you can safely ignore it. The interceptor catches the exception and swallows it at the Connect call so it will never reach your code.
Long string/byte array truncation
The interceptor will truncate long strings and byte arrays, to prevent the fact that megabytes of data are sent to the client. The truncation is done when a string or byte array is longer than 4096 characters or in the case of byte arrays, 4096 bytes.
DbCommandBuilder usage is not intercepted
If your code uses DbCommandBuilder derived classes (like SqlCommandBuilder), the commands created with this object aren't intercepted and aren't profiled. If you're using DbCommandBuilder code and want to use the profiler, please use the DbProviderFactory instead to create commands (or use the DbConnection.CreateCommand method).
Additional information for certain frameworks/circumstances
By default, calling the InterceptorCore.Initialize method is enough to make the application become a profiled application. However for some supported frameworks it might be you need to perform some minor extra steps in certain situations. These are described more in detail below. If the way your application uses the supported framework isn't described below, you can simply call Initialize and profiling should be enabled past that point.
Linq to Sql specific
Linq to Sql has a hard-coded way of creating ADO.NET objects. The interceptor currently doesn't intercept this hard-coded construction, however there's a workaround for making the profiler work with Linq to Sql:
InterceptorCore.Initialize("Linq to Sql");
var factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
var connection = factory.CreateConnection();
connection.ConnectionString = ConfigurationManager.ConnectionStrings\["Main.ConnectionString"\].ConnectionString;
using(var context = new NorthwindDataContext(connection))
{
// normal code.
}
It's preferable that you create your context instances in a central place so you can pass the connection in a single place, otherwise it can be a bit more work to get the profiler working with Linq to Sql.
Dapper.NET specific
Dapper.net doesn't create its own connections, you have to create one yourself. Normally you'd do it using a hard-coded method, but this won't use the DbProviderFactory and therefore it won't be intercepted. To make Dapper.NET code work with ORM Profiler, use the following helper method to obtain an open connection.
Be aware that the return-type is set to DbConnection. For factoryInvariantName you should use the invariant name for the factory of your ADO.NET provider, e.g. "System.Data.SqlClient" for SQL Server.
public static DbConnection GetOpenConnection(string connectionString)
{
var factory = DbProviderFactories.GetFactory(factoryInvariantName);
var connection = factory.CreateConnection();
connection.ConnectionString = connectionString;
return connection;
}
You can then use it in your own code, like:
using(var connection = GetOpenConnection(myConnectionString))
{
connection.Open();
var customers = connection.Query<Customer>(query, parameters...);
connection.Close();
//... consuming the returned data
}