Ever made it to production only to realize your code fails miserably at scale? When performance problems rear their gnarly head and there name is EntityFramework, there is but one blade to slice that gordeon knot: LINQPad.
However, if you're using the ASP.NET Boilerplate (ABP) framework, the situation is a tad more dire. That's because ABP uses a repository pattern, which, it turns out, is less than compatible with LINQPad's
DataContext centric approach.
In this post, I'll describe two ways to use LINQPad with ABP apps to solve performance problems:
- Rewrite repository pattern based queries to run in LINQPad with a data context. This works well for small problems.
- Enable LINQPad to call directly into your code, support authentication, multi-tenancy, and the unit of work and repository patterns with the help of a NuGet packge I just released called LINQPad.ABP. This helps immensely for more complex performance problems.
Repository Pattern 101
The Repository and Unit of Work patterns used in ASP.NET Boilerplate apps are a wonderful abstraction for simplifying unit testing, enabling annotation based transactions, and handling database connection management. As described in this article from the Microsoft MVC docs:
The repository and unit of work patterns are intended to create an abstraction layer between the data access layer and the business logic layer of an application. Implementing these patterns can help insulate your application from changes in the data store and can facilitate automated unit testing or test-driven development (TDD).
That document gives a nice diagram to show the difference between an architecture with and without a repository pattern:
However, these abstractions present a problem for LINQPad. When using LINQPad to diagnose performance problems, it would often be super convenient to call directly into your app's code to see the queries translated into SQL and executed. However, even if LINQPad understood dependency injection, it would have no idea how to populate a
IRepository, what to do with a
[UnitOfWork(TransactionScopeOption.RequiresNew)] attribute, or what value to return for
IAbpSession.TenantId. Fortunately, I just released a NuGet Package to make that easy.
But first, the simplest way to solve the problem for single queries is just to rewrite the query without a repository pattern and paste it into LINQPad.
Undoing Repository Pattern
This is where this blog post gets into the weeds. If you'd rather watch me perform the following steps, please check out my latest episode of Code Hour:
Otherwise, here it is in written form:
Step one is to enable ABP'S data context to support a constructor that takes a connection string. If you add the following code to your
private string _connectionString;
public MyProjDbContext(string connectionString)
: base(new DbContextOptions())
_connectionString = connectionString;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
if (_connectionString == null)
var dbContextOptionsBuilder = new DbContextOptionsBuilder();
Then in LINQPad, you can:
- Add a connection
- "Use a typed data context from your own assembly"
- Select a Path to Custom Assembly like "MyProjSolution\server\src\MyProj.Web.Host\bin\Debug\netcoreapp2.1\MyProj.EntityFrameworkCore.dll"
- Enter a Full Type Name of Typed
DbContext like "
- LINQPad should instantiate your
DbContext via a constructor that accepts a string, then provide your connection string
Now, when you start a new query, you can write:
var thing = this.Things.Where(t => t.Id == 1);
And if you run it, you'll see the resulting SQL statement.
Not bad. If you paste in any real code, you'll need to add
using statements and replace
this.Things and you'll be translating LINQ to SQL in no time.
Pretty cool. It certainly works for simple queries.
However, in my experience, performance problems rarely crop up in the easy parts of the system. Performance problems always seem to happen in places where multiple classes interact because there was simply too much logic for the author to have stuffed it all into one class and have been able to sleep at night.
To enable LINQPad to call directly into ABP code, you'll need to set up dependency injection, define a module that starts up your core module, specify a current user and tenant to impersonate, and somehow override the default unit of work to use LINQPad's context rather than ABP's. That's a lot of work.
Fortunately, I just published LINQPad.ABP, an Open Source NuGet Package that does all this for you. To enable it:
- In LINQPad, add a reference to "
- Add a custom Module that's dependent on your project's specific EF Module.
- Create and Cache a LINQPad ABP Context.
- Start a
UnitOfWork and specify the user and tenant you want to impersonate.
The code will look like this:
public class LinqPadModule : LinqPadModuleBase
public LinqPadModule(MyProjEntityFrameworkModule abpProjectNameEntityFrameworkModule)
abpProjectNameEntityFrameworkModule.SkipDbSeed = true;
public override void InitializeServices(ServiceCollection services)
async Task Main()
var abpCtx = Util.Cache(LinqPadAbp.InitModule(), "LinqPadAbp");
using (var uowManager = abpCtx.StartUow(this, tenantId: 5, userId: 8))
var thingService = abpCtx.IocManager.Resolve();
var entity = await thingService.GetEntityByIdAsync(1045);
That may look like a lot of code, but trust me, it's way simpler than it would otherwise be. And now, you can call into your code and watch every single query and how it gets translated to SQL.
I hope this helps you track down a hard bug faster. If maybe, then please vote or comment below.
Lee is a Microsoft MVP and a prolific writer, speaker, and youtuber on .Net and open source topics. Lee is a Solution Samurai at InfernoRed ( http://infernoredtech.com). When not coding he enjoys running, mountain biking, smoking brisket, electronics, 3D printing, and woodworking. He is active on twitter where you can reach him @lprichar ( https://twitter.com/lprichar).