Troubleshooting and debugging

Sometimes you want deep insight in what's really going on inside the runtime libraries, what SQL was generated and in what order did the queries get executed, and not only during development, but also after deployment, when an application in production doesn't do what it should do.

LLBLGen Pro provides a fine-grained, robust tracing facility using System.Diagnostics.Trace to emit information which can then be picked up by System.Diagnostics.TraceListener derived classes, the so called trace listeners. See for more information the System.Diagnostics.Trace class for more information.

Info

Tracing doesn't affect performance when all switches are switched off (the default) or not defined in the .config file. Though when a switch is turned on, the tracing code will affect performance. Use the right switches for your tracing needs and leave them off in production code or don't specify them at all, unless troubleshooting has to take place.

Tip

The traceswitch for the DQEs is a static (shared) switch as code shared among all DQE's is using the switch as well. This means that if you're using simultaneously the DQEs of multiple databases, and you switch on tracing for one of the DQE's and actively switch tracing off for the other DQEs, tracing won't work as the switch will be turned off.

Traceswitches have to be static/shared as the .NET tracing framework requires that. To avoid switching off the traceswitch of the DQE you want tracing to be enabled, enable tracing of all DQEs used in your application.

Conventions

LLBLGen Pro's generated code offers two categories of tracing: info level and verbose level, and a variety of trace switches to switch tracing on or off. When a trace switch is switched on, the runtime libraries will produce trace output, depending on which switch you've turned on and to which level: verbose or info. This way you can keep the trace output small and still keep an eye on what's going on behind the scenes.

A trace listener can be defined in the .config file or in code. If no trace listener is defined, and the application is started in debug mode from within Visual Studio (using F5), the tracing output will be delivered to the output window of Visual Studio.

This way, you can use the tracing functionality during development without leaving the editor. .NET comes with some basic trace listeners, for example for writing the trace information to a file or to the event log. You can also write trace listeners yourself. For more information see the online MSDN help link mentioned above.

Every method which has tracing functionality added, has a "Method Enter" and a "Method Exit" message. Most of the time these are logged at the info level, though for methods which are called a lot, they're defined for the verbose level, so to see them, you have to switch on the particular trace switch at level '4' (verbose).

You can use these two messages for formatting/searching the trace output. When a method has multiple overloads, the number of arguments is specified with a number, for example CreateDeleteDQ(3).

At the verbose level, LLBLGen Pro will report additional information, if applicable. For example the entity field information, parameter values and other data which is only required when all information has to be retrieved. At the verbose level, all info level tracing is also provided.

All trace switches have to be defined in the .config file, in a system.Diagnostics tag, which has to be placed inside the configuration tag. You don't have to define all switches, you can omit any switch definition if you'd like.

When a switch isn't defined, it's considered turned off and no trace information is produced which is tied to that switch. The following snippet shows all available trace switches. These switches are described in detail in the following sections. The snippet below defines some switches with the value '3' (info level, switched on), '4' (verbose level, switched on) or '0' (switched off).

<system.diagnostics>
    <switches>
        <add name="SqlServerDQE" value="3" />
        <add name="AccessDQE" value="4" />
        <add name="OracleDQE" value="4" />
        <add name="FirebirdDQE" value="4" />
        <add name="MySqlDQE" value="4" />
        <add name="DB2DQE" value="4" />
        <add name="PostgreSqlDQE" value="4" />
        <add name="ORMGeneral" value="0" />
        <add name="ORMQueryExecution" value="0" />
        <add name="ORMStateManagement" value="0" />
        <add name="ORMPersistenceExecution" value="3" />
        <add name="ORMPlainSQLQueryExecution" value="3" />
        <add name="LinqExpressionHandler" value="3" />
    </switches>
</system.diagnostics>

Netstandard 2.0+

If you're using the Netstandard 2.0+ build of the runtime, there's no .config file support and you should use the RuntimeConfiguration system to configure trace switches.

ILogger and trace output

For routing trace information to the ILogger<T> instance used in your application, you need a small bridge class between the trace listener and the ILogger instance. For how to do that, please see this article on CodeProject: Use Trace and TraceSource in .NET Core Logging

Setting up a trace listener in code

To wire a trace listener to a tracer in code, you can do so with a simple one line of code. The code below adds a new text based trace listener to the set of listeners and will emit all received messages sent to tracers to Console.Out. This is the same in .NET Framework and .Netcore/.Net 5+

Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));

Dynamic Query Engine tracing

To enable Dynamic Query Engine (DQE) tracing, you can for each different DQE switch it on to level 3 (info) or level 4 (verbose). To do that, you have to make sure the switch for the DQE of the database you want trace information for is defined in the .config file, as shown in the previous section. The following table shows the switches per database type.

DQE / database Trace switch
SqlServer SqlServerDQE
MS Access AccessDQE
Oracle (ODP.NET/Microsoft Oracle Provider) OracleDQE
Firebird FirebirdDQE
MySQL MySqlDQE
IBM DB2 DB2DQE
PostgreSql PostgreSqlDQE

For all DQE's the levels 3 (info) and 4 (verbose) are supported.

ORM Support classes tracing

The ORM Support classes offer three different switches to trace different functionality. These switches are described below:

  • ORMGeneral switch for general code, like a collection Add method, as well as dependency injection.
  • ORMQueryExecution switch for which SQL queries are executed, with which parameters and which values the parameters had.
  • ORMStateManagement switch for state management, like CheckForRefetch in SelfServicing
  • ORMPersistenceExecution switch for persistence logic execution code, like FetchEntity. This is the switch you'll probably use the most, in combination with a DQE switch to follow the complete call all the way to the database.

These switches offer also the two levels supported: 3 for info level and 4 for verbose level.

Linq to LLBLGen Pro tracing

The Linq to LLBLGen Pro provider also supports tracing. It has two levels: Info level tracing (3), which is recommended if this tracer is used, and Verbose level tracing (4). Info level tracing emits the expression tree into the output in textual form. This is useful to get more information about what was exactly passed to the Linq provider.

Verbose level tracing is really verbose: it emits a complete visit trace of all methods called during the handling of the linq query. In general this isn't recommended as it eats a lot of performance and also emits a lot of output. Only use level 4 for this tracer if you absolutely have to.

Plain SQL API tracing

For monitoring the Plain SQL API queries separately, e.g. to see whether developers cut corners and embed values into the SQL statement instead of using parameters, you can use the tracer ORMPlainSQLQueryExecution. . This tracer has one level, info (3), which logs the query with parameter values onto the trace output. Enabling this tracer will log all plain SQL queries for all databases onto the single tracer. If you have enabled ORMPersistenceExecution as well, the plain SQL queries will be traced on that tracer as well. The ORMPlainSQLQueryExecution is usable to filter out the plain SQL queries separately into a separate output for easy analysis.

Debugger Visualizers

Visual Studio offers the ability to enable visualizer objects for given types during debugging sessions called Debugger visualizers. If you've already done some debugging inside Visual Studio you've seen some of them already: the string debugger visualizer for example is one of the debugger visualizers shipped with Visual Studio which can be activated when the execution is paused and you hover over a type with the mouse and click the magnifyer glass icon.

For several types in the LLBLGen Pro generated code and runtime library, Debugger Visualizers are developed so debugging code using LLBLGen Pro generated code is easier.

Installation

Copy the SD.LLBLGen.Pro.DebugVisualizersXXYY.dll from the folder Frameworks\LLBLGen ProRuntimeLibraries\DebuggerVisualizersxxyy to the folder: My Documents\Visual Studio xxyy\Visualizers

Also copy the ORMSupportClasses dll and the DQE dll of your choice to that folder. 'xxyy' is the Visual Studio year number of the Visual Studio version you're using. LLBLGen Pro ships debug visualizers for 2015, 2017 and 2019

You need to restart Visual Studio to make them available to you. In a debug session, when you hit a breakpoint, you can hover your mouse over a variable of a type of any of the supported types below and you'll see a magnifyer glass which allows you to open the debug visualizer for that type with the data in that instance.

Debug visualizers included

The following debug visualizers are included:

Predicate/PredicateExpression The predicate / Predicate expression visualizer visualizes the predicate as a WHERE clause. It will use a pseudo DQE and pseudo DB specific creator to create a string which is displayed in a viewer. Also displayed are the parameters of the complete filter and the values of these parameters.

EntityCollection (Selfservicing/adapter) Simple form with a DataGrid set to readonly/AllowNavigation set to false (to avoid lazy loading) for SelfServicing entity collections and with AllowNavigation set to true for Adapter, which displays the collection using normal databinding. The form also shows a textbox with the type the collection is set for, which is typically the type the factory produces.

RelationCollection / Relationpredicatebucket Similar to PredicateExpression, though it will show the actual SQL generated. The RelationPredicateBucket visualizer has a tab control with both a visualizer for the PredicateExpression as for the RelationCollection.

SortExpression Simple visualizer which shows the sort expression in a textbox, similar to the predicate expression visualizer

GroupByCollection Simple viewer which views the grouped fields, one on each line and the HAVING clause as a normal predicate expression.

PrefetchPath The visualizer for prefetch paths displays the path as nodes in a tree. To handle polymorphic paths, the path element is represented by a node, and all nodes added to that path element are added as nodes. The visualizer uses reflection to get a hold on the EntityType type definition to produce proper names for the node destinations/types to fetch.

Expression The visualizer for the expression is similar to the PredicateVisualizer: it shows the expression without beautification and the parameters.