You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|[`Contains` in LINQ queries may stop working on older SQL Server versions](#sqlserver-contains-compatibility)| High |
25
+
|[Possible query performance regressions around `Contains` in LINQ queries](#contains-perf-regression)| High |
25
26
|[Enums in JSON are stored as ints instead of strings by default](#enums-as-ints)| High |
26
27
|[SQL Server `date` and `time` now scaffold to .NET `DateOnly` and `TimeOnly`](#sqlserver-date-time-only)| Medium |
27
28
|[Boolean columns with a database generated value are no longer scaffolded as nullable](#scaffold-bools)| Medium |
@@ -47,17 +48,79 @@ EF Core 8 targets .NET 8. Applications targeting older .NET, .NET Core, and .NET
47
48
48
49
#### Old behavior
49
50
50
-
Previously, when the `Contains` operator was used in LINQ queries with a parameterized value list, EF generated SQL that was inefficient but worked on all SQL Server versions.
51
+
EF had specialized support for LINQ queries using `Contains` operator over a parameterized value list:
52
+
53
+
```c#
54
+
varnames=new[] { "Blog1", "Blog2" };
55
+
56
+
varblogs=awaitcontext.Blogs
57
+
.Where(b=>names.Contains(b.Name))
58
+
.ToArrayAsync();
59
+
```
60
+
61
+
Before EF Core 8.0, EF inserted the parameterized values as constants into the SQL:
62
+
63
+
```sql
64
+
SELECT [b].[Id], [b].[Name]
65
+
FROM [Blogs] AS [b]
66
+
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
67
+
```
51
68
52
69
#### New behavior
53
70
54
-
Starting with EF Core 8.0, EF now generates SQL that is more efficient, but is unsupported on SQL Server 2014 and below.
71
+
Starting with EF Core 8.0, EF now generates SQL that is more efficient in many cases, but is unsupported on SQL Server 2014 and below:
72
+
73
+
```sql
74
+
SELECT [b].[Id], [b].[Name]
75
+
FROM [Blogs] AS [b]
76
+
WHERE [b].[Name] IN (
77
+
SELECT [n].[value]
78
+
FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
79
+
)
80
+
```
55
81
56
82
Note that newer SQL Server versions may be configured with an older [compatibility level](/sql/t-sql/statements/alter-database-transact-sql-compatibility-level), also making them incompatible with the new SQL. This can also occur with an Azure SQL database which was migrated from a previous on-premises SQL Server instance, carrying over the old compatibility level.
57
83
58
84
#### Why
59
85
60
-
The previous SQL generated by EF Core for `Contains` inserted the parameterized values as constants in the SQL. For example, the following LINQ query:
86
+
The insertion of constant values into the SQL creates many performance problems, defeating query plan caching and causing unneeded evictions of other queries. The new EF Core 8.0 translation uses the SQL Server [`OPENJSON`](/sql/t-sql/functions/openjson-transact-sql) function to instead transfer the values as a JSON array. This solves the performance issues inherent in the previous technique; however, the `OPENJSON` function is unavailable in SQL Server 2014 and below.
87
+
88
+
For more information about this change, [see this blog post](https://devblogs.microsoft.com/dotnet/announcing-ef8-preview-4/).
89
+
90
+
#### Mitigations
91
+
92
+
If your database is SQL Server 2016 (13.x) or newer, or if you're using Azure SQL, check the configured compatibility level of your database via the following command:
If the compatibility level is below 130 (SQL Server 2016), consider modifying it to a newer value ([documentation](/sql/t-sql/statements/alter-database-transact-sql-compatibility-level#best-practices-for-upgrading-database-compatibility-leve)).
99
+
100
+
Otherwise, if your database version really is older than SQL Server 2016, or is set to an old compatibility level which you cannot change for some reason, you can configure EF to revert to the older, pre-8.0 SQL. If you're using EF 9, you can use the newly-introduced <xref:Microsoft.EntityFrameworkCore.Infrastructure.RelationalDbContextOptionsBuilder%602.TranslateParameterizedCollectionsToConstants%2A>:
EF had specialized support for LINQ queries using `Contains` operator over a parameterized value list:
61
124
62
125
```c#
63
126
varnames=new[] { "Blog1", "Blog2" };
@@ -67,36 +130,54 @@ var blogs = await context.Blogs
67
130
.ToArrayAsync();
68
131
```
69
132
70
-
... would be translated to the following SQL:
133
+
Before EF Core 8.0, EF inserted the parameterized values as constants into the SQL:
71
134
72
135
```sql
73
136
SELECT [b].[Id], [b].[Name]
74
137
FROM [Blogs] AS [b]
75
138
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
76
139
```
77
140
78
-
Such insertion of constant values into the SQL creates many performance problems, defeating query plan caching and causing unneeded evictions of other queries. The new EF Core 8.0 translation uses the SQL Server [`OPENJSON`](/sql/t-sql/functions/openjson-transact-sql) function to instead transfer the values as a JSON array. This solves the performance issues inherent in the previous technique; however, the `OPENJSON` function is unavailable in SQL Server 2014 and below.
141
+
#### New behavior
79
142
80
-
For more information about this change, [see this blog post](https://devblogs.microsoft.com/dotnet/announcing-ef8-preview-4/).
143
+
Starting with EF Core 8.0, EF now generates the following:
144
+
145
+
```sql
146
+
SELECT [b].[Id], [b].[Name]
147
+
FROM [Blogs] AS [b]
148
+
WHERE [b].[Name] IN (
149
+
SELECT [n].[value]
150
+
FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
151
+
)
152
+
```
153
+
154
+
However, after the release of EF 8 it turned out that while the new SQL is more efficient for most cases, it can be dramatically less efficient in a minority of cases, even causing query timeouts in some cases
81
155
82
156
#### Mitigations
83
157
84
-
If your database is SQL Server 2016 (13.x) or newer, or if you're using Azure SQL, check the configured compatibility level of your database via the following command:
158
+
If you're using EF 9, you can use the newly-introduced <xref:Microsoft.EntityFrameworkCore.Infrastructure.RelationalDbContextOptionsBuilder%602.TranslateParameterizedCollectionsToConstants%2A> to revert the `Contains` translation for all queries back to the pre-8.0 behavior:
If the compatibility level is below 130 (SQL Server 2016), consider modifying it to a newer value ([documentation](/sql/t-sql/statements/alter-database-transact-sql-compatibility-level#best-practices-for-upgrading-database-compatibility-leve)).
91
-
92
-
Otherwise, if your database version really is older than SQL Server 2016, or is set to an old compatibility level which you cannot change for some reason, configure EF Core to revert to the older, less efficient SQL as follows:
165
+
If you're using EF 8, you can achieve the same effect when using SQL Server by configuring EF's SQL compatibility level:
Finally, you can control the translation on a query-by-query basis using <xref:Microsoft.EntityFrameworkCore.EF.Constant%2A?displayProperty=nameWithType> as follows:
174
+
175
+
```c#
176
+
varblogs=awaitcontext.Blogs
177
+
.Where(b=>EF.Constant(names).Contains(b.Name))
178
+
.ToArrayAsync();
179
+
```
180
+
100
181
<aname="enums-as-ints"></a>
101
182
102
183
### Enums in JSON are stored as ints instead of strings by default
0 commit comments