Skip to content

Commit a3b5401

Browse files
Copilotroji
andauthored
Document parameterized collection translation as breaking change in EF Core 10 (#5163)
Co-authored-by: Shay Rojansky <roji@roji.org>
1 parent 6b7be11 commit a3b5401

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

entity-framework/core/what-is-new/ef-core-10.0/breaking-changes.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This page documents API and behavior changes that have the potential to break ex
2424
|:--------------------------------------------------------------------------------------------------------------- | -----------|
2525
| [Application Name is now injected into the connection string](#sqlserver-application-name) | Low |
2626
| [SQL Server json data type used by default on Azure SQL and compatibility level 170](#sqlserver-json-data-type) | Low |
27+
| [Parameterized collections now use multiple parameters by default](#parameterized-collections) | Low |
2728
| [ExecuteUpdateAsync now accepts a regular, non-expression lambda](#ExecuteUpdateAsync-lambda) | Low |
2829
| [Complex type column names are now uniquified](#complex-type-column-uniquification) | Low |
2930
| [Nested complex type properties use full path in column names](#nested-complex-type-column-names) | Low |
@@ -132,6 +133,88 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
132133
}
133134
```
134135

136+
<a name="parameterized-collections"></a>
137+
138+
### Parameterized collections now use multiple parameters by default
139+
140+
[Tracking Issue #36368](https://github.com/dotnet/efcore/issues/36368)
141+
142+
#### Old behavior
143+
144+
In EF Core 9 and earlier, parameterized collections in LINQ queries (such as those used with `.Contains()`) were translated to SQL using a JSON array parameter by default. Consider the following query:
145+
146+
```c#
147+
int[] ids = [1, 2, 3];
148+
var blogs = await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();
149+
```
150+
151+
On SQL Server, this generated the following SQL:
152+
153+
```sql
154+
@__ids_0='[1,2,3]'
155+
156+
SELECT [b].[Id], [b].[Name]
157+
FROM [Blogs] AS [b]
158+
WHERE [b].[Id] IN (
159+
SELECT [i].[value]
160+
FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
161+
)
162+
```
163+
164+
#### New behavior
165+
166+
Starting with EF Core 10.0, parameterized collections are now translated using multiple scalar parameters by default:
167+
168+
```sql
169+
SELECT [b].[Id], [b].[Name]
170+
FROM [Blogs] AS [b]
171+
WHERE [b].[Id] IN (@ids1, @ids2, @ids3)
172+
```
173+
174+
#### Why
175+
176+
The new default translation provides the query planner with cardinality information about the collection, which can lead to better query plans in many scenarios. The multiple parameter approach balances between plan cache efficiency (by parameterizing) and query optimization (by providing cardinality).
177+
178+
However, different workloads may benefit from different translation strategies depending on collection sizes, query patterns, and database characteristics.
179+
180+
#### Mitigations
181+
182+
If you encounter issues with the new default behavior (such as performance regressions), you can configure the translation mode globally:
183+
184+
```c#
185+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
186+
=> optionsBuilder
187+
.UseSqlServer("<CONNECTION STRING>",
188+
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
189+
```
190+
191+
Available modes are:
192+
193+
- `ParameterTranslationMode.MultipleParameters` - The new default (multiple scalar parameters)
194+
- `ParameterTranslationMode.Constant` - Inlines values as constants (pre-EF8 default behavior)
195+
- `ParameterTranslationMode.Parameter` - Uses JSON array parameter (EF8-9 default)
196+
197+
You can also control the translation on a per-query basis:
198+
199+
```c#
200+
// Use constants instead of parameters for this specific query
201+
var blogs = await context.Blogs
202+
.Where(b => EF.Constant(ids).Contains(b.Id))
203+
.ToListAsync();
204+
205+
// Use a single parameter (e.g. JSON parameter with OPENJSON) instead of parameters for this specific query
206+
var blogs = await context.Blogs
207+
.Where(b => EF.Parameter(ids).Contains(b.Id))
208+
.ToListAsync();
209+
210+
// Use multiple scalar parameters for this specific query. This is the default in EF 10, but is useful if the default was changed globally:
211+
var blogs = await context.Blogs
212+
.Where(b => EF.MultipleParameters(ids).Contains(b.Id))
213+
.ToListAsync();
214+
```
215+
216+
For more information about parameterized collection translation, [see the documentation](xref:core/what-is-new/ef-core-10.0/whatsnew#parameterized-collection-translation).
217+
135218
<a name="ExecuteUpdateAsync-lambda"></a>
136219

137220
### ExecuteUpdateAsync now accepts a regular, non-expression lambda

0 commit comments

Comments
 (0)