SQL injection is a risk at the boundary between a service and its database: crafted input may change the SQL the database receives. EF Core helps guard against it, but using EF Core alone is not a reason to skip an audit. Treat the database connection and its requests and responses as part of the application’s threat model. section 9.4 section 9.2
A practical check follows the SQL from application code to the database:
- Search for hand-written SQL calls, including FromSqlRaw, FromSqlInterpolated, and Database.ExecuteSqlCommand. Review how input reaches each statement. section 9.4.1
- Exercise database operations in tests and attach a DbCommandInterceptor to the DbContext. Log each DbCommand.CommandText before EF Core sends it to the data store. section 9.4.1
- Inspect the generated statements rather than assuming every LINQ query takes the same shape. A lookup by ID uses a parameter such as @__id_0; a collection-based title lookup is shown with values in the SQL instead. section 9.4.1
An alarming-looking value is not proof of execution
A title list contains `Robert'); DROP TABLE Students;--`. In the generated `IN` clause, EF Core doubles the embedded quote, leaving that text inside a SQL string value. Inspect the complete statement to distinguish an attack-shaped input from SQL that actually changes the query’s structure. section 9.4.1
Listing 9.6 LogSqlInterceptor logging SQL generated by EF Core
using System.Data.Common; #1
using Microsoft.EntityFrameworkCore.Diagnostics; #2
using Xunit.Abstractions; #3
namespace ManningBooksApi.Tests;
public class LogSqlInterceptor : DbCommandInterceptor
{
private readonly ITestOutputHelper _testOutput; #4
public LogSqlInterceptor(ITestOutputHelper testOutput)
=> _testOutput = testOutput;
public override
ValueTask<InterceptionResult<DbDataReader>> #5
ReaderExecutingAsync( #6
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancelToken)
{
_testOutput.WriteLine(command.CommandText.Trim()); #7
return base.ReaderExecutingAsync( #8
command, eventData, result, cancelToken);
}
}
The interceptor implementation shows where to capture SQL and how to send it to xUnit’s test output, avoiding mixed console logs from parallel tests. section 9.4.1
Generated SQL can also vary by EF Core version: an EF Core 8 example passes a title list as a JSON parameter and reads it with OPENJSON. The useful habit is to trust the framework’s protections while verifying the statements produced by the operations you use. section 9.4.1 section 9.7








