diff --git a/.gitignore b/.gitignore index 19bda18036..a9f1104c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -368,3 +368,5 @@ FodyWeavers.xsd # Rider .idea/ + +.DS_Store \ No newline at end of file diff --git a/entity-framework/core/extensions/index.md b/entity-framework/core/extensions/index.md index fa03296382..1bdf23f611 100644 --- a/entity-framework/core/extensions/index.md +++ b/entity-framework/core/extensions/index.md @@ -286,15 +286,21 @@ This package focuses on adding data masking support for SQL Server to EF Core. F [GitHub repository](https://github.com/sander1095/EntityFrameworkCore.Extensions.SqlServer.DataMasking) | [NuGet](https://www.nuget.org/packages/SanderTenBrinke.EntityFrameworkCore.Extensions.SqlServer.DataMasking) +### EFCommenter + +All xml summaries of entities, properties, and enums will be added as comments on the corresponding database tables and columns. For EF Core: 9. + +[GitHub repository](https://github.com/roohial57/DotNetComponent_EFCommenter) | [NuGet](https://www.nuget.org/packages/EFCommenter) + ## API Integrations These packages are designed to integrate directly with EF Core to expose various APIs. -### .NET Aspire +### Aspire -Enhance the local development experience by simplifying the management of your cloud-native app's configuration and interconnections. For EF Core: 8. +Enhance the local development experience by simplifying the management of your cloud-native app's configuration and interconnections. For EF Core: 8-10. -[Website](/dotnet/aspire/get-started/aspire-overview) | [GitHub repository](https://github.com/dotnet/aspire) | [NuGet](https://www.nuget.org/profiles/aspire) +[Website](https://aspire.dev) | [GitHub repository](https://github.com/dotnet/aspire) | [NuGet](https://www.nuget.org/profiles/aspire) ### HotChocolate diff --git a/entity-framework/core/providers/index.md b/entity-framework/core/providers/index.md index 466ea1079e..f5197a620a 100644 --- a/entity-framework/core/providers/index.md +++ b/entity-framework/core/providers/index.md @@ -27,7 +27,7 @@ Entity Framework Core can access many different databases through plug-in librar | [Npgsql.EntityFrameworkCore.PostgreSQL](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL) | PostgreSQL | [Npgsql Development Team](https://github.com/npgsql) | | 8, 9 | [docs](https://www.npgsql.org/efcore/index.html) | | [Pomelo.EntityFrameworkCore.MySql](https://www.nuget.org/packages/Pomelo.EntityFrameworkCore.MySql) | MySQL, MariaDB | [Pomelo Foundation Project](https://github.com/PomeloFoundation) | | 8, 9 | [readme](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/master/README.md) | | [MySql.EntityFrameworkCore](https://www.nuget.org/packages/MySql.EntityFrameworkCore) | MySQL | [MySQL project](https://dev.mysql.com) (Oracle) | | 8, 9 | [docs](https://dev.mysql.com/doc/connector-net/en/connector-net-entityframework-core.html) | -| [Oracle.EntityFrameworkCore](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/) | Oracle DB 11.2 onwards | [Oracle](https://www.oracle.com/technetwork/topics/dotnet/) | | 8, 9 | [website](https://www.oracle.com/technetwork/topics/dotnet/) | +| [Oracle.EntityFrameworkCore](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/) | Oracle DB 19c onwards | [Oracle](https://www.oracle.com/technetwork/topics/dotnet/) | | 8, 9, 10 | [docs](https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/ODPEFCore.html) | | [MongoDB.EntityFrameworkCore](https://www.nuget.org/packages/MongoDB.EntityFrameworkCore/) | MongoDB | [MongoDB](https://www.mongodb.com/) | | 8 | [docs](https://www.mongodb.com/docs/entity-framework/current/) | | [Couchbase.EntityFrameworkCore](https://www.nuget.org/packages/Couchbase.EntityFrameworkCore) | Couchbase | [Couchbase](https://github.com/couchbaselabs/couchbase-efcore-provider) | | 8, 9 | [docs](https://docs.couchbase.com/efcore-provider/current/start-using-efcore-provider.html) | | [Devart.Data.MySql.EFCore](https://www.nuget.org/packages/Devart.Data.MySql.EFCore/) | MySQL 5 onwards | [DevArt](https://www.devart.com/dotconnect/mysql/) | Paid | 8, 9 | [docs](https://docs.devart.com/dotconnect/mysql/GettingStarted.html) | diff --git a/entity-framework/core/providers/sql-server/misc.md b/entity-framework/core/providers/sql-server/misc.md index 2206fe39ef..022c787d1c 100644 --- a/entity-framework/core/providers/sql-server/misc.md +++ b/entity-framework/core/providers/sql-server/misc.md @@ -33,22 +33,46 @@ Use [HasPerformanceLevelSql](/dotnet/api/Microsoft.EntityFrameworkCore.SqlServer > [!TIP] > 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). -## SaveChanges, database triggers and unsupported computed columns - -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). - -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: - -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=TriggerConfiguration&highlight=4)] - -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). - -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): - -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=BlankTriggerAddingConvention)] - -Use the convention on your `DbContext` by overriding `ConfigureConventions`: - -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=ConfigureConventions)] - -This effectively calls `HasTrigger` on all your model's tables, instead of you having to do it manually for each and every table. +## SaveChanges, triggers and the OUTPUT clause + +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. + +If you run into a limitation related to the use of the OUTPUT clause, you can disable it on a specific table via : + +```c# +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)); +} +``` + +Doing this will make EF switch to an older, less efficient technique for updating the table. + +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): + +```c# +public class NoOutputClauseConvention : IModelFinalizingConvention +{ + public virtual void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); + if (table is not null) + { + entityType.Builder.UseSqlOutputClause(false); + } + + foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table)) + { + entityType.Builder.UseSqlOutputClause(false, fragment.StoreObject); + } + } + } +} +``` + +This effectively calls on all your model's tables, instead of you having to do it manually for each and every table. diff --git a/entity-framework/core/providers/sqlite/misc.md b/entity-framework/core/providers/sqlite/misc.md new file mode 100644 index 0000000000..dab10b9e89 --- /dev/null +++ b/entity-framework/core/providers/sqlite/misc.md @@ -0,0 +1,52 @@ +--- +title: Miscellaneous - SQLite Database Provider - EF Core +description: Miscellaneous information for the SQLite database provider +author: roji +ms.date: 11/27/2025 +uid: core/providers/sqlite/misc +--- +# Miscellaneous notes for SQLite + +## SaveChanges and the RETURNING clause + +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. + +If you run into a limitation related to the use of the RETURNING clause, you can disable it on a specific table via : + +```c# +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlReturningClause(false)); +} +``` + +Doing this will make EF switch to an older, less efficient technique for updating the table. + +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): + +```c# +public class NoOutputClauseConvention : IModelFinalizingConvention +{ + public virtual void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); + if (table is not null) + { + entityType.Builder.UseSqlReturningClause(false); + } + + foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table)) + { + entityType.Builder.UseSqlReturningClause(false, fragment.StoreObject); + } + } + } +} +``` + +This effectively calls on all your model's tables, instead of you having to do it manually for each and every table. diff --git a/entity-framework/core/what-is-new/ef-core-7.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-7.0/breaking-changes.md index efd3943cdb..e69057975a 100644 --- a/entity-framework/core/what-is-new/ef-core-7.0/breaking-changes.md +++ b/entity-framework/core/what-is-new/ef-core-7.0/breaking-changes.md @@ -131,7 +131,9 @@ The performance improvements linked to the new method are significant enough tha #### Mitigations -Starting with EF Core 8.0, the use or not of the "OUTPUT" clause can be configured explicitly. For example: +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: + +##### [EF 8+](#tab/data-annotations) ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -141,21 +143,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } ``` -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: - -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=TriggerConfiguration&highlight=4)] - -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_. +##### [EF 7](#tab/fluent-api) -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: - -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=BlankTriggerAddingConvention)] - -Use the convention on your `DbContext` by overriding `ConfigureConventions`: +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .ToTable(tb => tb.HasTrigger("SomeTrigger")); +} +``` -[!code-csharp[Main](../../../../samples/core/SqlServer/Misc/TriggersContext.cs?name=ConfigureConventions)] +--- -This effectively calls `HasTrigger` on all your model's tables, instead of you having to do it manually for each and every table. +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). diff --git a/entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md index 00dd937cac..01089252cb 100644 --- a/entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md @@ -2633,6 +2633,8 @@ Or to opt-out of `RETURNING` when using the SQLite provider: modelBuilder.Entity().ToTable(tb => tb.UseSqlReturningClause(false)); ``` +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. + ## Other minor changes In addition to the enhancements described above, there have been many smaller changes made to EF8. This includes: diff --git a/entity-framework/toc.yml b/entity-framework/toc.yml index 7aac5432db..5ef232c07b 100644 --- a/entity-framework/toc.yml +++ b/entity-framework/toc.yml @@ -428,6 +428,8 @@ - name: Spatial data displayName: GIS href: core/providers/sqlite/spatial.md + - name: Miscellaneous + href: core/providers/sqlite/misc.md - name: Microsoft.Data.Sqlite >> href: /dotnet/standard/data/sqlite/ - name: Azure Cosmos DB diff --git a/samples/core/.DS_Store b/samples/core/.DS_Store new file mode 100644 index 0000000000..63eba61765 Binary files /dev/null and b/samples/core/.DS_Store differ diff --git a/samples/core/Miscellaneous/.DS_Store b/samples/core/Miscellaneous/.DS_Store new file mode 100644 index 0000000000..e7b56c671c Binary files /dev/null and b/samples/core/Miscellaneous/.DS_Store differ diff --git a/samples/core/SqlServer/Misc/Blog.cs b/samples/core/SqlServer/Misc/Blog.cs deleted file mode 100644 index a7c92dae97..0000000000 --- a/samples/core/SqlServer/Misc/Blog.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SqlServer.Faq; - -public class Blog -{ - public int BlogId { get; set; } - public string Url { get; set; } -} diff --git a/samples/core/SqlServer/Misc/TriggersContext.cs b/samples/core/SqlServer/Misc/TriggersContext.cs deleted file mode 100644 index 431847097f..0000000000 --- a/samples/core/SqlServer/Misc/TriggersContext.cs +++ /dev/null @@ -1,58 +0,0 @@ - -using System.Linq; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; - -namespace SqlServer.Faq; - -public class TriggersContext : DbContext -{ - public DbSet Blogs { get; set; } - - #region TriggerConfiguration - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .ToTable(tb => tb.HasTrigger("SomeTrigger")); - } - #endregion - - #region ConfigureConventions - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention()); - } - #endregion -} - -#region BlankTriggerAddingConvention -public class BlankTriggerAddingConvention : IModelFinalizingConvention -{ - public virtual void ProcessModelFinalizing( - IConventionModelBuilder modelBuilder, - IConventionContext context) - { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) - { - var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); - if (table != null - && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null) - && (entityType.BaseType == null - || entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy)) - { - entityType.Builder.HasTrigger(table.Value.Name + "_Trigger"); - } - - foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table)) - { - if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null)) - { - entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger"); - } - } - } - } -} -#endregion