When an Entity Framework Core application behaves unexpectedly, the problem is not always in the LINQ query written by the developer. EF Core translates LINQ expressions into SQL, and the generated query may contain joins, filters, ordering operations, or database parameters that are not immediately obvious from the application code.
For example, a query that looks simple in C# might generate a SQL statement that retrieves more records than expected. Another query might execute multiple database commands when the developer expects only one. Without inspecting the generated SQL, it can be difficult to understand what the application is actually sending to the database.
EF Core provides built-in logging through the Microsoft.Extensions.Logging infrastructure. Developers can use it to inspect SQL commands, query execution times, and database-related warnings during development.
This article explains how to enable EF Core SQL logging, configure it in an ASP.NET Core application, inspect generated queries, identify common performance problems, and avoid exposing sensitive information.
Why Log EF Core SQL Queries?
Logging generated SQL helps developers understand the relationship between LINQ queries and database operations.
Consider the following query:
var customers = await dbContext.Customers
.Where(customer => customer.IsActive)
.OrderBy(customer => customer.Name)
.Take(20)
.ToListAsync();The C# code expresses the required result, but the database executes the SQL generated by EF Core. Inspecting that SQL helps confirm whether the active-customer filter, ordering, and row limit are translated as expected.
SQL logging is useful when investigating:
Unexpected database queries.
Slow page loads or API responses.
Queries that retrieve too many records.
Repeated database calls inside loops.
Unexpected joins or subqueries.
Database commands that fail because of incorrect mappings or parameters.
SQL logging does not automatically improve performance. Its purpose is to provide evidence that helps developers identify and fix the underlying problem.
Enable SQL Logging in an ASP.NET Core Application
The simplest approach is to configure logging through the application's existing dependency injection setup.
In Program.cs, configure the logging providers and register the application's DbContext.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString(
"DefaultConnection"));
});
var app = builder.Build();
app.MapGet("/", () => "Application is running.");
app.Run();This example assumes that the SQL Server provider is installed and that AppDbContext is defined in the application.
ASP.NET Core already configures logging providers according to its hosting environment. Calling AddConsole() explicitly is useful when you want to ensure that console logging is registered, although it may be unnecessary in a standard application.
EF Core uses the configured logging infrastructure to report database activity. However, ordinary logging configuration does not necessarily display every SQL command at the default log level.
Show SQL Commands During Development
EF Core emits database command messages under the Microsoft.EntityFrameworkCore.Database.Command logging category. These messages commonly use the Information level for executed commands.
You can enable that category in appsettings.Development.json.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}The more specific category overrides the broader EF Core logging level for database commands.
This configuration allows developers to see executed SQL statements without enabling verbose logging for every EF Core subsystem.
For applications that already use structured logging, the same category can be configured through the existing logging provider rather than adding another provider.
What the log output tells you
Depending on the provider and logging configuration, a database command entry can contain:
The generated SQL statement.
Parameter information.
The command type.
The command execution duration.
Diagnostic information associated with the operation.
For example, the generated SQL might resemble the following:
SELECT TOP(20)
[c].[Id],
[c].[Name],
[c].[IsActive]
FROM [Customers] AS [c]
WHERE [c].[IsActive] = CAST(1 AS bit)
ORDER BY [c].[Name];The exact SQL depends on the database provider, EF Core version, entity mapping, and LINQ expression.
When reviewing this output, check whether the expected filters are present, whether the query returns only the required columns, and whether ordering and row limits are applied correctly.
Use LogTo for Focused Development Diagnostics
EF Core also provides the LogTo method for directing its log messages to a specified destination.
This can be useful in a small diagnostic setup where you want to inspect EF Core messages without changing the application's global logging configuration.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString(
"DefaultConnection"));
options.LogTo(
Console.WriteLine,
new[]
{
DbLoggerCategory.Database.Command.Name
},
LogLevel.Information);
});The category filter limits the output to database command messages. The log level controls the minimum severity of messages that are written.
This approach is convenient during local debugging, but avoid configuring several overlapping logging paths without a reason. Duplicate configuration can make logs noisy and harder to interpret.
For a larger application, the standard ILogger pipeline is usually easier to manage because it supports centralized filtering, structured logging, and different output destinations.
Inspect Queries Before Executing Them
Sometimes you need to inspect the SQL generated by a LINQ query without executing it against the database.
EF Core provides ToQueryString() for this purpose.
using Microsoft.EntityFrameworkCore;
var query = dbContext.Customers
.Where(customer => customer.IsActive)
.OrderBy(customer => customer.Name)
.Take(20);
var sql = query.ToQueryString();
Console.WriteLine(sql);This method returns a debugging representation of the SQL associated with the query.
It is useful when investigating translation problems or reviewing how a LINQ expression is converted into SQL.
There is an important distinction between ToQueryString() and database command logging. ToQueryString() does not execute the query. It also does not provide actual execution duration or prove that the database will execute the statement successfully.
Use command logging when you need to see what was executed, and use ToQueryString() when you want to inspect a query before execution.
Configure Sensitive Data Logging Carefully
EF Core normally avoids including sensitive parameter values in its logs. During local development, you may sometimes need to inspect those values to understand why a query returns unexpected results.
The EnableSensitiveDataLogging() option allows EF Core to include sensitive data in certain diagnostic messages.
if (builder.Environment.IsDevelopment())
{
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString(
"DefaultConnection"));
options.EnableSensitiveDataLogging();
});
}This setting should be enabled only when the development team has a clear need for it.
Parameter values can contain personal information, authentication-related data, or other confidential values. Sending those logs to a shared logging platform can expose data beyond the intended audience.
For production environments, keep sensitive data logging disabled unless a carefully reviewed diagnostic requirement justifies it. Even then, restrict access, control retention, and avoid capturing unnecessary information.
Find Common EF Core Query Problems
Detecting repeated queries
Suppose an application loads a collection of orders and then queries the database separately for each order's customer.
var orders = await dbContext.Orders
.ToListAsync();
foreach (var order in orders)
{
var customer = await dbContext.Customers
.FirstOrDefaultAsync(
customer => customer.Id == order.CustomerId);
}This pattern can generate one query for the orders and additional queries for individual customers.
When the application processes many orders, the number of database round trips can grow unnecessarily. SQL logs help reveal this repeated execution pattern.
Depending on the required data and relationship configuration, a projection or an appropriate join may allow the application to retrieve the required information more efficiently.
Detecting queries that retrieve too much data
Calling ToListAsync() before applying filters can cause the application to retrieve every matching entity from the database and filter the results in memory.
Prefer applying supported filters before materializing the query.
var activeCustomers = await dbContext.Customers
.Where(customer => customer.IsActive)
.Select(customer => new
{
customer.Id,
customer.Name
})
.ToListAsync();This query requests only active customers and selects the two required properties. EF Core can translate these operations into SQL when the provider supports the expression.
The generated SQL helps confirm whether the database is performing the filtering and projection as expected.
Investigating slow commands
EF Core command logs can show how long a database command took according to the logging instrumentation.
A slow command is a useful starting point, but it does not explain the root cause by itself. The delay might come from a missing index, blocking, a large result set, poor query plans, or database resource constraints.
Use the SQL statement and its parameter information to investigate the query in the database's diagnostic tools. Avoid assuming that a query is inefficient based only on its length or appearance.
Best Practices for Development SQL Logging
Enable detailed command logging in development. Keep the logging configuration focused on the categories needed for debugging.
Use
ToQueryString()for translation checks. Inspect a query without executing it when investigating how LINQ is translated.Protect sensitive values. Do not enable sensitive data logging casually or forward confidential parameters to shared systems.
Correlate logs with application requests. Use structured logging and request identifiers to connect database activity with the operation that triggered it.
Investigate the database execution plan. Use database-specific tools when logs identify a slow command.
Avoid logging everything indefinitely. Excessive SQL logging increases log volume and can make relevant failures harder to find.
Test with realistic data volumes. A query that appears fast with a handful of records may behave differently when the table grows.
Common Mistakes
One common mistake is setting the overall EF Core logging level to Warning and expecting ordinary SQL command messages to appear. Database command messages are typically logged at Information, so the category-specific level must permit them.
Another mistake is assuming that a SQL statement returned by ToQueryString() has actually run. The method is intended for query inspection, not execution measurement.
Developers may also enable sensitive data logging to solve a temporary issue and forget to disable it. This can expose parameter values in logs that are collected outside the local development environment.
Finally, SQL logging should not replace proper database monitoring. Logs can identify suspicious commands, but execution plans, indexing information, wait statistics, and database metrics are often necessary to understand performance problems.
Advantages and Disadvantages
Advantages | Disadvantages |
|---|---|
Reveals the SQL generated from LINQ expressions. | Detailed logs can become noisy. |
Helps identify repeated database operations. | Logging alone does not identify every performance bottleneck. |
Makes query translation issues easier to investigate. | Sensitive parameter logging can expose confidential data. |
Supports troubleshooting without rewriting application queries. | High log volume can increase storage and processing costs. |
Integrates with ASP.NET Core logging infrastructure. | Provider-specific SQL can require additional database knowledge. |
Summary
Logging EF Core SQL queries during development helps developers understand how their LINQ expressions interact with the database. The standard logging infrastructure provides a practical way to inspect executed commands, while ToQueryString() makes it possible to review generated SQL before execution.
Start by enabling the Microsoft.EntityFrameworkCore.Database.Command category at the appropriate log level. Use focused diagnostic logging, protect sensitive parameter values, and investigate slow commands with database-specific tools.
SQL logging is most valuable when used as part of a broader debugging process. Combined with realistic test data, execution plans, and application-level diagnostics, it helps developers identify inefficient queries and correct database access problems before they reach production.

Join the conversation! Your thoughts help the community grow.