diff --git a/.github/agents/docs-generator.agent.md b/.github/agents/docs-generator.agent.md
new file mode 100644
index 0000000000..e1170eba9d
--- /dev/null
+++ b/.github/agents/docs-generator.agent.md
@@ -0,0 +1,36 @@
+---
+name: EF Documentation Generator
+description: This agent creates documentation PRs in the EF documentation site when new features are implemented in EF Core.
+disable-model-invocation: true
+---
+
+# Document new EF features
+
+Given an EF issue by the user, this custom agent generates documentation for features introduced in that issue and submits a PR to the EF docs repo (dotnet/EntityFramework.Docs).
+
+## Target branch
+
+* The EF repo has automation to automatically add a label indicating in which preview/rc the feature has been completed; the label is applied to the issue (not PR), and has the form `preview-3` or `rc-2` with the number adjusted.
+* The docs repo should have a corresponding branch, containing documentation to be published live when that preview/rc is published.
+* When the workflow is launched, check the issue, find the preview/rc label, and submit the PR against the corresponding branch in the doc repo (dotnet/EntityFramework.Docs).
+* If the label is missing, abort and post a comment to the triggering issue.
+
+## Writing the documentation
+
+* Fully read the conversation history of the issue, as well as any linked PRs or relevant issues linked from it, to gain good context on the feature, APIs introduced, etc.
+* Add documentation in the appropriate section of the docs, depending on what the feature is.
+* Fully document the feature, but keep it brief - do not add edge-case, non-important documentation in the name of exhaustivity that wouldn't be relevant to the majority of users.
+* Before the new documentation, add the following note (adjusting for the major version):
+
+```
+> [!NOTE]
+> This feature is being introduced in EF Core 11, which is currently in preview.
+```
+
+* Find the "what's new" page for the latest major release (typically `core/what-is-new/ef-core-11.0`, adjusting for the version), and add a **brief** section on the feature - just the minimum needed to make the user understand what it's about; include a minimal code sample as well if relevant. At the bottom, add a line such as "For more information on X, see the documentation" linking to the full docs added above, in case the user wants to dive deeper.
+* For both the full docs and the what's new documentation, do not simply create a new section; first check to see if there's an existing section that already covers related/similar functionality; if there is, either merge the new content into it or place the new section next to it.
+* If the issue adds a function translation, add the appropriate entry (or entries) in the provider's functions page. Do not add functions entries for LINQ operators (e.g. `Contains`).
+
+## Additional instructions
+
+* The commit in the resulting PR should have a title of the form "Document X", where X is the name of the feature as it appears in the title of the originating issue. If the title is too long for a git commit, make it shorter. The commit body should be of the form "Document Y", where Y is a link to the originating issue.
diff --git a/entity-framework/core/extensions/index.md b/entity-framework/core/extensions/index.md
index 0aa159d15f..846940f9ec 100644
--- a/entity-framework/core/extensions/index.md
+++ b/entity-framework/core/extensions/index.md
@@ -108,6 +108,12 @@ Provides a wrapper around [SQL Server Express LocalDB](/sql/database-engine/conf
[GitHub repository](https://github.com/SimonCropp/LocalDb) | [NuGet](https://www.nuget.org/packages/EfLocalDb)
+### EfCore.InMemory.Transactions
+
+Seamless transaction support for EF Core InMemory provider. Eliminates "transactions with isolation level are not supported" errors in tests without changing production code. Provides safe extension methods and NoOpDbContextTransaction for UnitOfWork patterns. For EF Core: 8-10.
+
+[GitHub repository](https://github.com/ShadyNagy/EfCore.InMemory.Transactions) | [NuGet](https://www.nuget.org/packages/EfCore.InMemory.Transactions)
+
### EntityFrameworkCore.Projectables
Flexible projection magic for EF Core. Use properties, methods, and extension methods in your query without client evaluation. For EF Core: 3-6, 8.
diff --git a/entity-framework/core/learn-more/community-standups.md b/entity-framework/core/learn-more/community-standups.md
index e2b0d1dba1..3641d355db 100644
--- a/entity-framework/core/learn-more/community-standups.md
+++ b/entity-framework/core/learn-more/community-standups.md
@@ -16,6 +16,7 @@ The .NET Data Community Standups are live-streamed monthly (roughly) on Wednesda
| Date | Area | Title |
|--------------|-----------------------|------------------------------------------------------------------------------------------|
+| Feb 12, 2026 | AI | [Adam tells us about Microsoft.Extensions.DataIngestion](#Feb12_2026) |
| Nov 20, 2025 | Release | [EF 10 release celebration 🎉](#Nov20_2025) |
| Oct 16, 2025 | Migrations | [Jeremy Miller shares his view on migrations and Marten](#Oct16_2025) |
| Sep 18, 2025 | ORMs | [Jiri and Nick talk about experience with Dapper and EF Core](#Sep18_2025) |
@@ -108,6 +109,25 @@ The .NET Data Community Standups are live-streamed monthly (roughly) on Wednesda
| Jun 10, 2020 | EF Core Power Tools | [EF Core Power Tools](#power-tools1) |
| May 6, 2020 | Welcome! | [Introducing the EF Core Community Standup](#one) |
+## 2026
+
+
+
+### Feb 12: [Adam tells us about Microsoft.Extensions.DataIngestion](https://www.youtube.com/live/pQivzi4n6jM?si=Ms9H0O3Mgw147hhj)
+
+Microsoft.Extensions.DataIngestion is new addition into Microsoft.Extensions collection and this one touches databases and AI. Exciting. Adam Sitnik, who worked on implementation, tells us what's what and maybe some behind the scenes.
+
+Featuring:
+
+- [Adam Sitnik](https://github.com/adamsitnik) (Special guest)
+- [Jiri Cincura](https://www.tabsoverspaces.com/) (Host)
+
+Links:
+
+- [Introducing Data Ingestion Building Blocks (Preview)](https://devblogs.microsoft.com/dotnet/introducing-data-ingestion-building-blocks-preview/)
+- [dotnet/extensions GitHub](https://github.com/dotnet/extensions)
+- Template: `Microsoft.Extensions.AI.Templates`
+
## 2025
diff --git a/entity-framework/core/logging-events-diagnostics/interceptors.md b/entity-framework/core/logging-events-diagnostics/interceptors.md
index cd50d052bf..46bc091a44 100644
--- a/entity-framework/core/logging-events-diagnostics/interceptors.md
+++ b/entity-framework/core/logging-events-diagnostics/interceptors.md
@@ -2,7 +2,7 @@
title: Interceptors - EF Core
description: Interception for database operations and other events
author: SamMonoRT
-ms.date: 11/15/2021
+ms.date: 02/26/2026
uid: core/logging-events-diagnostics/interceptors
ms.custom: sfi-ropc-nochange
---
@@ -15,6 +15,20 @@ Interceptors are different from logging and diagnostics in that they allow modif
Interceptors are registered per DbContext instance when the context is configured. Use a [diagnostic listener](xref:core/logging-events-diagnostics/diagnostic-listeners) to get the same information but for all DbContext instances in the process.
+## Available interceptors
+
+The following table shows the available interceptor interfaces:
+
+| Interceptor | Operations intercepted | [Singleton](#singleton-interceptors) |
+|:--------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------:|
+| | Creating commandsExecuting commandsCommand failuresDisposing the command's DbDataReader | No |
+| | Opening and closing connectionsCreating connectionsConnection failures | No |
+| | Creating transactionsUsing existing transactionsCommitting transactionsRolling back transactionsCreating and using savepointsTransaction failures | No |
+| | SavingChanges/SavedChangesSaveChangesFailedOptimistic concurrency handling | No |
+| | Creating, initializing, and finalizing entity instances from query results | Yes |
+| | Modifying the LINQ expression tree before a query is compiled | Yes |
+| | Resolving identity conflicts when tracking entities | Yes |
+
## Registering interceptors
Interceptors are registered using when [configuring a DbContext instance](xref:core/dbcontext-configuration/index). This is commonly done in an override of . For example:
@@ -49,6 +63,48 @@ public class TaggedQueryCommandInterceptorContext : BlogsContext
Every interceptor instance must implement one or more interface derived from . Each instance should only be registered once even if it implements multiple interception interfaces; EF Core will route events for each interface as appropriate.
+### Singleton interceptors
+
+Some interceptors implement (see table above); these interceptors are registered as singleton services in EF Core's internal service provider, meaning a single instance is shared across all `DbContext` instances that use the same service provider.
+
+Because singleton interceptors become part of EF Core's internal service configuration, each distinct interceptor instance causes a new internal service provider to be built. Passing a **new instance** of a singleton interceptor each time a `DbContext` is configured--for example, in `AddDbContext`--will eventually trigger a `ManyServiceProvidersCreatedWarning` and degrade performance.
+
+> [!WARNING]
+> Always reuse the same singleton interceptor instance for all `DbContext` instances. Do not create a new instance each time the context is configured.
+
+For example, the following is **incorrect** because a new interceptor instance is created for each context configuration:
+
+```csharp
+// Don't do this! A new instance each time causes a new internal service provider to be built.
+services.AddDbContext(
+ b => b.UseSqlServer(connectionString)
+ .AddInterceptors(new MyMaterializationInterceptor()));
+```
+
+Instead, reuse the same instance:
+
+```csharp
+// Correct: reuse a single interceptor instance
+var interceptor = new MyMaterializationInterceptor();
+services.AddDbContext(
+ b => b.UseSqlServer(connectionString)
+ .AddInterceptors(interceptor));
+```
+
+Or use a static field:
+
+```csharp
+public class CustomerContext : DbContext
+{
+ private static readonly MyMaterializationInterceptor _interceptor = new();
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.AddInterceptors(_interceptor);
+}
+```
+
+Because these interceptors are singletons, they must be thread-safe. They should generally not hold mutable state. If you need to access scoped services (such as the current `DbContext`), use the or similar properties on the event data passed to each interceptor method.
+
## Database interception
> [!NOTE]
@@ -59,7 +115,7 @@ Low-level database interception is split into the three interfaces shown in the
| Interceptor | Database operations intercepted
|:-----------------------------------------------------------------------|-------------------------------------------------
| | Creating commandsExecuting commandsCommand failuresDisposing the command's DbDataReader
-| | Opening and closing connectionsConnection failures
+| | Opening and closing connectionsCreating connectionsConnection failures
| | Creating transactionsUsing existing transactionsCommitting transactionsRolling back transactionsCreating and using savepointsTransaction failures
The base classes , , and contain no-op implementations for each method in the corresponding interface. Use the base classes to avoid the need to implement unused interception methods.
@@ -185,6 +241,91 @@ public class AadAuthenticationInterceptor : DbConnectionInterceptor
> [!WARNING]
> in some situations the access token may not be cached automatically the Azure Token Provider. Depending on the kind of token requested, you may need to implement your own caching here.
+### Example: Lazy initialization of a connection string
+
+Connection strings are often static assets read from a configuration file. These can easily be passed to `UseSqlServer` or similar when configuring a `DbContext`. However, sometimes the connection string can change for each context instance. For example, each tenant in a multi-tenant system may have a different connection string.
+
+An can be used to handle dynamic connections and connection strings. This starts with the ability to configure the `DbContext` without any connection string. For example:
+
+```csharp
+services.AddDbContext(
+ b => b.UseSqlServer());
+```
+
+One of the `IDbConnectionInterceptor` methods can then be implemented to configure the connection before it is used. `ConnectionOpeningAsync` is a good choice, since it can perform an async operation to obtain the connection string, find an access token, and so on. For example, imagine a service scoped to the current request that understands the current tenant:
+
+```csharp
+services.AddScoped();
+```
+
+> [!WARNING]
+> Performing an asynchronous lookup for a connection string, access token, or similar every time it is needed can be very slow. Consider caching these things and only refreshing the cached string or token periodically. For example, access tokens can often be used for a significant period of time before needing to be refreshed.
+
+This can be injected into each `DbContext` instance using constructor injection:
+
+```csharp
+public class CustomerContext : DbContext
+{
+ private readonly ITenantConnectionStringFactory _connectionStringFactory;
+
+ public CustomerContext(
+ DbContextOptions options,
+ ITenantConnectionStringFactory connectionStringFactory)
+ : base(options)
+ {
+ _connectionStringFactory = connectionStringFactory;
+ }
+
+ // ...
+}
+```
+
+This service is then used when constructing the interceptor implementation for the context:
+
+```csharp
+protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.AddInterceptors(
+ new ConnectionStringInitializationInterceptor(_connectionStringFactory));
+```
+
+Finally, the interceptor uses this service to obtain the connection string asynchronously and set it the first time that the connection is used:
+
+```csharp
+public class ConnectionStringInitializationInterceptor : DbConnectionInterceptor
+{
+ private readonly ITenantConnectionStringFactory _connectionStringFactory;
+
+ public ConnectionStringInitializationInterceptor(ITenantConnectionStringFactory connectionStringFactory)
+ {
+ _connectionStringFactory = connectionStringFactory;
+ }
+
+ public override InterceptionResult ConnectionOpening(
+ DbConnection connection,
+ ConnectionEventData eventData,
+ InterceptionResult result)
+ => throw new NotSupportedException("Synchronous connections not supported.");
+
+ public override async ValueTask ConnectionOpeningAsync(
+ DbConnection connection, ConnectionEventData eventData, InterceptionResult result,
+ CancellationToken cancellationToken = new())
+ {
+ if (string.IsNullOrEmpty(connection.ConnectionString))
+ {
+ connection.ConnectionString = (await _connectionStringFactory.GetConnectionStringAsync(cancellationToken));
+ }
+
+ return result;
+ }
+}
+```
+
+> [!NOTE]
+> The connection string is only obtained the first time that a connection is used. After that, the connection string stored on the `DbConnection` will be used without looking up a new connection string.
+
+> [!TIP]
+> This interceptor overrides the non-async `ConnectionOpening` method to throw since the service to get the connection string must be called from an async code path.
+
### Example: Advanced command interception for caching
> [!TIP]
@@ -388,6 +529,78 @@ Free beer for unicorns
Notice from the log output that the application continues to use the cached message until the timeout expires, at which point the database is queried again for any new message.
+### Example: Logging SQL Server query statistics
+
+This example shows two interceptors that work together to send SQL Server query statistics to the application log. To generate the statistics, we need an to do two things.
+
+First, the interceptor will prefix commands with `SET STATISTICS IO ON`, which tells SQL Server to send statistics to the client after a result set has been consumed:
+
+```csharp
+public override ValueTask> ReaderExecutingAsync(
+ DbCommand command,
+ CommandEventData eventData,
+ InterceptionResult result,
+ CancellationToken cancellationToken = default)
+{
+ command.CommandText = "SET STATISTICS IO ON;" + Environment.NewLine + command.CommandText;
+
+ return new(result);
+}
+```
+
+Second, the interceptor will implement the `DataReaderClosingAsync` method, which is called after the has finished consuming results, but _before_ it has been closed. When SQL Server is sending statistics, it puts them in a second result on the reader, so at this point the interceptor reads that result by calling `NextResultAsync` which populates statistics onto the connection.
+
+```csharp
+public override async ValueTask DataReaderClosingAsync(
+ DbCommand command,
+ DataReaderClosingEventData eventData,
+ InterceptionResult result)
+{
+ await eventData.DataReader.NextResultAsync();
+
+ return result;
+}
+```
+
+The second interceptor is needed to obtain the statistics from the connection and write them out to the application's logger. For this, we'll use an , implementing the `ConnectionCreated` method. `ConnectionCreated` is called immediately after EF Core has created a connection, and so can be used to perform additional configuration of that connection. In this case, the interceptor obtains an `ILogger` and then hooks into the event to log the messages.
+
+```csharp
+public override DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result)
+{
+ var logger = eventData.Context!.GetService().CreateLogger("InfoMessageLogger");
+ ((SqlConnection)eventData.Connection).InfoMessage += (_, args) =>
+ {
+ logger.LogInformation(1, args.Message);
+ };
+ return result;
+}
+```
+
+> [!IMPORTANT]
+> The `ConnectionCreating` and `ConnectionCreated` methods are only called when EF Core creates a `DbConnection`. They will not be called if the application creates the `DbConnection` and passes it to EF Core.
+
+### Filtering by command source
+
+The supplied to diagnostics sources and interceptors contains a property indicating which part of EF was responsible for creating the command. This can be used as a filter in the interceptor. For example, we may want an interceptor that only applies to commands that come from `SaveChanges`:
+
+```csharp
+public class CommandSourceInterceptor : DbCommandInterceptor
+{
+ public override InterceptionResult ReaderExecuting(
+ DbCommand command, CommandEventData eventData, InterceptionResult result)
+ {
+ if (eventData.CommandSource == CommandSource.SaveChanges)
+ {
+ Console.WriteLine($"Saving changes for {eventData.Context!.GetType().Name}:");
+ Console.WriteLine();
+ Console.WriteLine(command.CommandText);
+ }
+
+ return result;
+ }
+}
+```
+
## SaveChanges interception
> [!TIP]
@@ -750,3 +963,390 @@ Audit 201fef4d-66a7-43ad-b9b6-b57e9d3f37b3 from 10/14/2020 9:10:17 PM to 10/14/2
Inserting Post with Id: '3' BlogId: '' Title: 'EF Core 3.1!'
Error: SQLite Error 19: 'UNIQUE constraint failed: Post.Id'.
```
+
+### Example: Optimistic concurrency interception
+
+EF Core supports the [optimistic concurrency pattern](xref:core/saving/concurrency) by checking that the number of rows actually affected by an update or delete is the same as the number of rows expected to be affected. This is often coupled with a concurrency token; that is, a column value that will only match its expected value if the row has not been updated since the expected value was read.
+
+EF signals a violation of optimistic concurrency by throwing a . has methods `ThrowingConcurrencyException` and `ThrowingConcurrencyExceptionAsync` that are called before the `DbUpdateConcurrencyException` is thrown. These interception points allow the exception to be suppressed, possibly coupled with async database changes to resolve the violation.
+
+For example, if two requests attempt to delete the same entity at almost the same time, then the second delete may fail because the row in the database no longer exists. This may be fine--the end result is that the entity has been deleted anyway. The following interceptor demonstrates how this can be done:
+
+```csharp
+public class SuppressDeleteConcurrencyInterceptor : ISaveChangesInterceptor
+{
+ public InterceptionResult ThrowingConcurrencyException(
+ ConcurrencyExceptionEventData eventData,
+ InterceptionResult result)
+ {
+ if (eventData.Entries.All(e => e.State == EntityState.Deleted))
+ {
+ Console.WriteLine("Suppressing Concurrency violation for command:");
+ Console.WriteLine(((RelationalConcurrencyExceptionEventData)eventData).Command.CommandText);
+
+ return InterceptionResult.Suppress();
+ }
+
+ return result;
+ }
+
+ public ValueTask ThrowingConcurrencyExceptionAsync(
+ ConcurrencyExceptionEventData eventData,
+ InterceptionResult result,
+ CancellationToken cancellationToken = default)
+ => new(ThrowingConcurrencyException(eventData, result));
+}
+```
+
+There are several things worth noting about this interceptor:
+
+* Both the synchronous and asynchronous interception methods are implemented. This is important if the application may call either `SaveChanges` or `SaveChangesAsync`. However, if all application code is async, then only `ThrowingConcurrencyExceptionAsync` needs to be implemented. Likewise, if the application never uses asynchronous database methods, then only `ThrowingConcurrencyException` needs to be implemented. This is generally true for all interceptors with sync and async methods.
+* The interceptor has access to objects for the entities being saved. In this case, this is used to check whether or not the concurrency violation is happening for a delete operation.
+* If the application is using a relational database provider, then the object can be cast to a object. This provides additional, relational-specific information about the database operation being performed. In this case, the relational command text is printed to the console.
+* Returning `InterceptionResult.Suppress()` tells EF Core to suppress the action it was about to take--in this case, throwing the `DbUpdateConcurrencyException`. This ability to _change the behavior of EF Core_, rather than just observing what EF Core is doing, is one of the most powerful features of interceptors.
+
+## Materialization interception
+
+ supports interception before and after an entity instance is created, and before and after properties of that instance are initialized. The interceptor can change or replace the entity instance at each point. This allows:
+
+* Setting unmapped properties or calling methods needed for validation, computed values, or flags.
+* Using a factory to create instances.
+* Creating a different entity instance than EF would normally create, such as an instance from a cache, or of a proxy type.
+* Injecting services into an entity instance.
+
+> [!NOTE]
+> `IMaterializationInterceptor` is a singleton interceptor, meaning a single instance is shared between all `DbContext` instances.
+
+### Example: Simple actions on entity creation
+
+Imagine that we want to keep track of the time that an entity was retrieved from the database, perhaps so it can be displayed to a user editing the data. To accomplish this, we first define an interface:
+
+```csharp
+public interface IHasRetrieved
+{
+ DateTime Retrieved { get; set; }
+}
+```
+
+Using an interface is common with interceptors since it allows the same interceptor to work with many different entity types. For example:
+
+```csharp
+public class Customer : IHasRetrieved
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = null!;
+ public string? PhoneNumber { get; set; }
+
+ [NotMapped]
+ public DateTime Retrieved { get; set; }
+}
+```
+
+Notice that the `[NotMapped]` attribute is used to indicate that this property is used only while working with the entity, and should not be persisted to the database.
+
+The interceptor must then implement the appropriate method from `IMaterializationInterceptor` and set the time retrieved:
+
+```csharp
+public class SetRetrievedInterceptor : IMaterializationInterceptor
+{
+ public object InitializedInstance(MaterializationInterceptionData materializationData, object instance)
+ {
+ if (instance is IHasRetrieved hasRetrieved)
+ {
+ hasRetrieved.Retrieved = DateTime.UtcNow;
+ }
+
+ return instance;
+ }
+}
+```
+
+An instance of this interceptor is registered when configuring the `DbContext`:
+
+```csharp
+public class CustomerContext : DbContext
+{
+ private static readonly SetRetrievedInterceptor _setRetrievedInterceptor = new();
+
+ public DbSet Customers => Set();
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder
+ .AddInterceptors(_setRetrievedInterceptor)
+ .UseSqlite("Data Source = customers.db");
+}
+```
+
+> [!TIP]
+> This interceptor is stateless, which is common, so a single instance is created and shared between all `DbContext` instances.
+
+Now, whenever a `Customer` is queried from the database, the `Retrieved` property will be set automatically. For example:
+
+```csharp
+await using (var context = new CustomerContext())
+{
+ var customer = await context.Customers.SingleAsync(e => e.Name == "Alice");
+ Console.WriteLine($"Customer '{customer.Name}' was retrieved at '{customer.Retrieved.ToLocalTime()}'");
+}
+```
+
+Produces output:
+
+```output
+Customer 'Alice' was retrieved at '9/22/2022 5:25:54 PM'
+```
+
+### Example: Injecting services into entities
+
+EF Core already has built-in support for injecting some special services into context instances; for example, see [Lazy loading without proxies](xref:core/querying/related-data/lazy#lazy-loading-without-proxies), which works by injecting the `ILazyLoader` service.
+
+An `IMaterializationInterceptor` can be used to generalize this to any service. The following example shows how to inject an into entities such that they can perform their own logging.
+
+> [!NOTE]
+> Injecting services into entities couples those entity types to the injected services, which some people consider to be an anti-pattern.
+
+As before, an interface is used to define what can be done.
+
+```csharp
+public interface IHasLogger
+{
+ ILogger? Logger { get; set; }
+}
+```
+
+And entity types that will log must implement this interface. For example:
+
+```csharp
+public class Customer : IHasLogger
+{
+ private string? _phoneNumber;
+
+ public int Id { get; set; }
+ public string Name { get; set; } = null!;
+
+ public string? PhoneNumber
+ {
+ get => _phoneNumber;
+ set
+ {
+ Logger?.LogInformation(1, $"Updating phone number for '{Name}' from '{_phoneNumber}' to '{value}'.");
+
+ _phoneNumber = value;
+ }
+ }
+
+ [NotMapped]
+ public ILogger? Logger { get; set; }
+}
+```
+
+This time, the interceptor must implement `IMaterializationInterceptor.InitializedInstance`, which is called after every entity instance has been created and its property values have been initialized. The interceptor obtains an `ILogger` from the context and initializes `IHasLogger.Logger` with it:
+
+```csharp
+public class LoggerInjectionInterceptor : IMaterializationInterceptor
+{
+ private ILogger? _logger;
+
+ public object InitializedInstance(MaterializationInterceptionData materializationData, object instance)
+ {
+ if (instance is IHasLogger hasLogger)
+ {
+ _logger ??= materializationData.Context.GetService().CreateLogger("CustomersLogger");
+ hasLogger.Logger = _logger;
+ }
+
+ return instance;
+ }
+}
+```
+
+This time a new instance of the interceptor is used for each `DbContext` instance, since the `ILogger` obtained can change per `DbContext` instance, and the `ILogger` is cached on the interceptor:
+
+```csharp
+protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.AddInterceptors(new LoggerInjectionInterceptor());
+```
+
+Now, whenever the `Customer.PhoneNumber` is changed, this change will be logged to the application's log. For example:
+
+```output
+info: CustomersLogger[1]
+ Updating phone number for 'Alice' from '+1 515 555 0123' to '+1 515 555 0125'.
+```
+
+## Query expression interception
+
+ allows interception of the [LINQ expression tree](xref:core/querying/how-query-works) for a query before it is compiled. This can be used to dynamically modify queries in ways that apply across the application.
+
+> [!NOTE]
+> `IQueryExpressionInterceptor` is a singleton interceptor, meaning a single instance is typically shared between all `DbContext` instances.
+
+> [!WARNING]
+> Interceptors are powerful, but it's easy to get things wrong when working with expression trees. Always consider if there is an easier way of achieving what you want, such as modifying the query directly.
+
+### Example: Inject ordering into queries for stable sorting
+
+Consider a method that returns a page of customers:
+
+```csharp
+Task> GetPageOfCustomers(string sortProperty, int page)
+{
+ using var context = new CustomerContext();
+
+ return context.Customers
+ .OrderBy(e => EF.Property