Skip to content

Commit 9442d7f

Browse files
authored
Document UseSqlOutputClause/UseSqlReturningClause (#5184)
Closes #4538
1 parent bfc4a32 commit 9442d7f

File tree

10 files changed

+114
-97
lines changed

10 files changed

+114
-97
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,5 @@ FodyWeavers.xsd
368368

369369
# Rider
370370
.idea/
371+
372+
.DS_Store

entity-framework/core/providers/sql-server/misc.md

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,46 @@ Use [HasPerformanceLevelSql](/dotnet/api/Microsoft.EntityFrameworkCore.SqlServer
3333
> [!TIP]
3434
> You can find all the supported values in the [ALTER DATABASE documentation](/sql/t-sql/statements/alter-database-transact-sql?view=azuresqldb-current&preserve-view=true).
3535
36-
## SaveChanges, database triggers and unsupported computed columns
37-
38-
Starting with EF Core 7.0, EF Core saves changes to the database with significantly optimized SQL; unfortunately, this technique is not supported on SQL Server if the target table has database triggers, or certain kinds of computed columns. For more information on this SQL Server limitation, see the documentation on the [OUTPUT clause](/sql/t-sql/queries/output-clause-transact-sql#remarks).
39-
40-
You can let EF Core know that the target table has a trigger; doing so will revert to the previous, less efficient technique. This can be done by configuring the corresponding entity type as follows:
41-
42-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=TriggerConfiguration&highlight=4)]
43-
44-
Note that doing this doesn't actually make EF Core create or manage the trigger in any way - it currently only informs EF Core that triggers are present on the table. As a result, any trigger name can be used, and this can also be used if an unsupported computed column is in use (regardless of triggers).
45-
46-
If most or all of your tables have triggers, you can opt out of using the newer, efficient technique for all your model's tables by using the following [model building convention](xref:core/modeling/bulk-configuration#conventions):
47-
48-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=BlankTriggerAddingConvention)]
49-
50-
Use the convention on your `DbContext` by overriding `ConfigureConventions`:
51-
52-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=ConfigureConventions)]
53-
54-
This effectively calls `HasTrigger` on all your model's tables, instead of you having to do it manually for each and every table.
36+
## SaveChanges, triggers and the OUTPUT clause
37+
38+
When EF Core saves changes to the database, it does so with an optimized technique using the T-SQL [OUTPUT clause](/sql/t-sql/queries/output-clause-transact-sql#remarks). Unfortunately, the OUTPUT clause has some limitations; it notably cannot be used with tables that have triggers, for example.
39+
40+
If you run into a limitation related to the use of the OUTPUT clause, you can disable it on a specific table via <xref:Microsoft.EntityFrameworkCore.SqlServerEntityTypeExtensions.UseSqlOutputClause*>:
41+
42+
```c#
43+
protected override void OnModelCreating(ModelBuilder modelBuilder)
44+
{
45+
modelBuilder.Entity<Blog>()
46+
.ToTable(tb => tb.UseSqlOutputClause(false));
47+
}
48+
```
49+
50+
Doing this will make EF switch to an older, less efficient technique for updating the table.
51+
52+
If most or all of your tables have triggers, you can configure this for all your model's tables by using the following [model building convention](xref:core/modeling/bulk-configuration#conventions):
53+
54+
```c#
55+
public class NoOutputClauseConvention : IModelFinalizingConvention
56+
{
57+
public virtual void ProcessModelFinalizing(
58+
IConventionModelBuilder modelBuilder,
59+
IConventionContext<IConventionModelBuilder> context)
60+
{
61+
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
62+
{
63+
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
64+
if (table is not null)
65+
{
66+
entityType.Builder.UseSqlOutputClause(false);
67+
}
68+
69+
foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
70+
{
71+
entityType.Builder.UseSqlOutputClause(false, fragment.StoreObject);
72+
}
73+
}
74+
}
75+
}
76+
```
77+
78+
This effectively calls <xref:Microsoft.EntityFrameworkCore.SqlServerEntityTypeExtensions.UseSqlOutputClause*> on all your model's tables, instead of you having to do it manually for each and every table.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: Miscellaneous - SQLite Database Provider - EF Core
3+
description: Miscellaneous information for the SQLite database provider
4+
author: roji
5+
ms.date: 11/27/2025
6+
uid: core/providers/sqlite/misc
7+
---
8+
# Miscellaneous notes for SQLite
9+
10+
## SaveChanges and the RETURNING clause
11+
12+
When EF Core saves changes to the database, it does so with an optimized technique using the SQL [RETURNING clause](https://sqlite.org/lang_returning.html). Unfortunately, the RETURNING clause has some limitations; it cannot be used with virtual tables or tables with certain trigger types, for example.
13+
14+
If you run into a limitation related to the use of the RETURNING clause, you can disable it on a specific table via <xref:Microsoft.EntityFrameworkCore.SqliteEntityTypeExtensions.UseSqlReturningClause*>:
15+
16+
```c#
17+
protected override void OnModelCreating(ModelBuilder modelBuilder)
18+
{
19+
modelBuilder.Entity<Blog>()
20+
.ToTable(tb => tb.UseSqlReturningClause(false));
21+
}
22+
```
23+
24+
Doing this will make EF switch to an older, less efficient technique for updating the table.
25+
26+
If most or all of your tables have triggers, you can configure this for all your model's tables by using the following [model building convention](xref:core/modeling/bulk-configuration#conventions):
27+
28+
```c#
29+
public class NoOutputClauseConvention : IModelFinalizingConvention
30+
{
31+
public virtual void ProcessModelFinalizing(
32+
IConventionModelBuilder modelBuilder,
33+
IConventionContext<IConventionModelBuilder> context)
34+
{
35+
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
36+
{
37+
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
38+
if (table is not null)
39+
{
40+
entityType.Builder.UseSqlReturningClause(false);
41+
}
42+
43+
foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
44+
{
45+
entityType.Builder.UseSqlReturningClause(false, fragment.StoreObject);
46+
}
47+
}
48+
}
49+
}
50+
```
51+
52+
This effectively calls <xref:Microsoft.EntityFrameworkCore.SqliteEntityTypeExtensions.UseSqlReturningClause*> on all your model's tables, instead of you having to do it manually for each and every table.

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ The performance improvements linked to the new method are significant enough tha
131131

132132
#### Mitigations
133133

134-
Starting with EF Core 8.0, the use or not of the "OUTPUT" clause can be configured explicitly. For example:
134+
In EF7 or later, if the target table has a trigger, then you can let EF Core know this, and EF will revert to the previous, less efficient technique. This can be done by configuring the corresponding entity type as follows:
135+
136+
##### [EF 8+](#tab/data-annotations)
135137

136138
```csharp
137139
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -141,21 +143,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
141143
}
142144
```
143145

144-
In EF7 or later, If the target table has a trigger, then you can let EF Core know this, and EF will revert to the previous, less efficient technique. This can be done by configuring the corresponding entity type as follows:
145-
146-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=TriggerConfiguration&highlight=4)]
147-
148-
Note that doing this doesn't actually make EF Core create or manage the trigger in any way - it currently only informs EF Core that triggers are present on the table. As a result, any trigger name can be used. Specifying a trigger can be used to revert the old behavior _even if there isn't actually a trigger in the table_.
146+
##### [EF 7](#tab/fluent-api)
149147

150-
If most or all of your tables have triggers, you can opt out of using the newer, efficient technique for all your model's tables by using the following model building convention:
151-
152-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=BlankTriggerAddingConvention)]
153-
154-
Use the convention on your `DbContext` by overriding `ConfigureConventions`:
148+
```csharp
149+
protected override void OnModelCreating(ModelBuilder modelBuilder)
150+
{
151+
modelBuilder.Entity<Blog>()
152+
.ToTable(tb => tb.HasTrigger("SomeTrigger"));
153+
}
154+
```
155155

156-
[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=ConfigureConventions)]
156+
---
157157

158-
This effectively calls `HasTrigger` on all your model's tables, instead of you having to do it manually for each and every table.
158+
For more information, including on how to configure this for all your tables, see the [SQL Server documentation](xref:core/providers/sql-server/misc#savechanges-triggers-and-the-output-clause).
159159

160160
<a name="sqlitetriggers"></a>
161161

entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,8 @@ Or to opt-out of `RETURNING` when using the SQLite provider:
26332633
modelBuilder.Entity<Customer>().ToTable(tb => tb.UseSqlReturningClause(false));
26342634
```
26352635
2636+
See the [SQL Server](xref:core/providers/sql-server/misc#savechanges-triggers-and-the-output-clause) and [SQLite](xref:core/providers/sqlite/misc#savechanges-and-the-returning-clause) documentation pages for more details.
2637+
26362638
## Other minor changes
26372639
26382640
In addition to the enhancements described above, there have been many smaller changes made to EF8. This includes:

entity-framework/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@
428428
- name: Spatial data
429429
displayName: GIS
430430
href: core/providers/sqlite/spatial.md
431+
- name: Miscellaneous
432+
href: core/providers/sqlite/misc.md
431433
- name: Microsoft.Data.Sqlite >>
432434
href: /dotnet/standard/data/sqlite/
433435
- name: Azure Cosmos DB

samples/core/.DS_Store

6 KB
Binary file not shown.
8 KB
Binary file not shown.

samples/core/SqlServer/Misc/Blog.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

samples/core/SqlServer/Misc/TriggersContext.cs

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)