EF6.TagWith 1.3.0-preview1

This is a prerelease version of EF6.TagWith.
dotnet add package EF6.TagWith --version 1.3.0-preview1
NuGet\Install-Package EF6.TagWith -Version 1.3.0-preview1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EF6.TagWith" Version="1.3.0-preview1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EF6.TagWith --version 1.3.0-preview1
#r "nuget: EF6.TagWith, 1.3.0-preview1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install EF6.TagWith as a Cake Addin
#addin nuget:?package=EF6.TagWith&version=1.3.0-preview1&prerelease

// Install EF6.TagWith as a Cake Tool
#tool nuget:?package=EF6.TagWith&version=1.3.0-preview1&prerelease

EF6-TagWith

Tag your Entity Framework 6 LINQ queries, and see these tags in the SQL commands sent to the database. This will allow you to easily identify queries while using tools like SQL Profiler or Azure's performance and diagnostic features.

(Yes, it is a simple implementation of Entity Framework Core's query tags feature.)

Usage (SQL Server)

  1. Install the NuGet package in your project.

    PM> install-package EF6.TagWith
    
  2. Add the query interceptor. The easiest way is just to add the following code somewhere in your application startup code:

    DbInterception.Add(new QueryTaggerInterceptor(new SqlServerTagger()));
    

    Starting at version 1.2, you may also use the TagWith.Initialize() helper method as follows:

    TagWith.Initialize<SqlServerTagger>();
    

    This method is the recommended option because it allows you to specify some tagging options, as explained below.

  3. Use the TagWith() extension to tag your queries:

    var query = context.Friends
        .OrderByDescending(friend => friend.Age)
        .Take(10)
        .Select(friend => new { 
            FriendName = friend.Name, 
            friend.Age, 
            CountryName = friend.Country.Name }
        )
        .TagWith("Get top 10 older friends with country");
    
  4. By default, the query sent to the database will be as follows:

    -- Get top 10 older friends with country
    
    SELECT TOP(@__p_0) [friend].[Name] AS [FriendName], [friend].[Age], 
                       [friend.Country].[Name] AS [CountryName]
    FROM [Friends] AS [friend]
    LEFT JOIN [Countries] AS [friend.Country] 
         ON [friend].[CountryId] = [friend.Country].[Id]
    ORDER BY [friend].[Age] DESC
    

How this works

The TagWith() extension method adds a special predicate to the query, so it can be easily identified in the final SQL.

Later on, just before sending the SQL command to the database, we use EF 6 interceptors to extract this predicate and insert the tag as a comment, just using a bit of string wizardry.

Prefix vs Infix

By default, TagWith inserts the tags before the SQL command. However, there are some query tracing tools, such as SQL Query Store or Azure SQL Performance Monitor, that remove initial comments so the tags are not shown. For these cases, we can force the tags (comments) to be inserted after the SELECT command.

For example, the above SQL command would look like this:

SELECT -- Get top 10 older friends with country

TOP(@__p_0) [friend].[Name] AS [FriendName], [friend].[Age], 
                    [friend.Country].[Name] AS [CountryName]
FROM [Friends] AS [friend]
LEFT JOIN [Countries] AS [friend.Country] 
        ON [friend].[CountryId] = [friend.Country].[Id]
ORDER BY [friend].[Age] DESC

The "infix" mode must be specified during the component initialization this way:

TagWith.Initialize<SqlServerTagger>(
    new TaggingOptions() { 
        TagMode = TagMode.Infix 
    });

Predicate expression

By default, the predicate included in the query specification when you use the method TagWith() looks like this:

// LINQ query:
var query = _ctx.Friends.TagWith("My friends").Where(f => f.Id < 10);
Console.WriteLine(query.ToString());

// Output:
SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Country_Id] AS [Country_Id]
    FROM [dbo].[Friends] AS [Extent1]
    WHERE (N'!__tag!' = N'My friends') AND ([Extent1].[Id] < 10)

Later on, just before the query is sent to the database, the predicate will be removed and the tag will be added as a comment.

As suggested by d-a-s, the problem with this approach is that if you try to get the SQL using ToString(), as seen above, and try to execute it against the database, you'll never get any results because the predicate is always evaluated as false.

Starting at version 1.3.0, you can change this behaviour by specifying a different predicate expression during the component initialization.

TagWith.Initialize<SqlServerTagger>(
    new TaggingOptions() { 
        PredicateExpression = PredicateExpression.NotEquals
    });

In this case, the query specification will be as follows, so you could run it against the database because the inserted predicate will be always true (note the distinct '<>' operator usage):

// LINQ query:
var query = _ctx.Friends.TagWith("My friends").Where(f => f.Id < 10);
Console.WriteLine(query.ToString());

// Output:
SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Country_Id] AS [Country_Id]
    FROM [dbo].[Friends] AS [Extent1]
    WHERE (N'!__tag!' <> N'My friends') AND ([Extent1].[Id] < 10)

Another tagging options

Apart from using TagWith() in the query, you may also use the extension method TagWithSource(), that will include automatically the caller member name and source code file, like in the following example:

// Initialization:
TagWith.Initialize<SqlServerTagger>();

// Query:
private List<Friend> GetFriends()
{
    var friends = ctx.Friends.OrderBy(f => f.Name).Take(10).TagWithSource("Getting friends").ToList();
    return friends;
}

Then, the SQL query sent to the database will be as follows:

-- Getting friends - GetFriends - C:\repos\TagWithDemo\FriendRepository.cs:20
SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[Name] AS [Name], 
    [Limit1].[Country_Id] AS [Country_Id]
    FROM ( SELECT TOP (10) [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name],
           [Extent1].[Country_Id] AS [Country_Id]
           FROM [dbo].[Friends] AS [Extent1]
           ORDER BY [Extent1].[Name] ASC
    )  AS [Limit1]
    
    ORDER BY [Limit1].[Name] ASC

.NET versions supported

EF6.TagWith should work with any .NET Framework version higher than 4.0, as well as with any version of .NET Core thanks to .NET Standard. So in theory you can use it wherever you are using Entity Framework 6.x.

If you find issues, please let me know, detailing what .NET version are you using and what kind of problems are you experiencing.

Known issues

  • The component only supports SQL Server, but can be easily adapted to support another providers just creating a new implementation of ISqlTagger and using it in the interceptor configuration.
  • If you are mocking a DbSet that is queried using TagWith() you may find problems. Please read this issue from @jsgoupil to see how to solve it.

Contributions

Feel free to send issues, comments or pull requests. The following users have contributed to this project so far:

Thank you!

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
.NET Framework net40 is compatible.  net403 was computed.  net45 is compatible.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on EF6.TagWith:

Repository Stars
SparkDevNetwork/Rock
An open source CMS, Relationship Management System (RMS) and Church Management System (ChMS) all rolled into one.
Version Downloads Last updated
1.3.0-preview1 441 4/2/2023
1.2.1 73,221 1/24/2021
1.2.0 822 1/11/2021
1.1.0-preview2 760 3/29/2020
1.1.0-preview1 697 3/28/2020
1.0.4 14,577 8/13/2019
1.0.3 861 8/6/2019
1.0.2 881 6/19/2019
1.0.1 866 6/16/2019
1.0.0 902 6/14/2019

Added in this release:
- Bug fixes