diff --git a/entity-framework/core/providers/sqlite/value-generation.md b/entity-framework/core/providers/sqlite/value-generation.md new file mode 100644 index 0000000000..a9190c3f57 --- /dev/null +++ b/entity-framework/core/providers/sqlite/value-generation.md @@ -0,0 +1,71 @@ +--- +title: SQLite Database Provider - Value Generation - EF Core +description: Value Generation Patterns Specific to the SQLite Entity Framework Core Database Provider +author: AndriySvyryd +ms.date: 09/26/2025 +uid: core/providers/sqlite/value-generation +--- +# SQLite Value Generation + +This page details value generation configuration and patterns that are specific to the SQLite provider. It's recommended to first read [the general page on value generation](xref:core/modeling/generated-properties). + +## AUTOINCREMENT columns + +By convention, numeric primary key columns that are configured to have their values generated on add are set up with SQLite's AUTOINCREMENT feature. Starting with EF Core 10, SQLite AUTOINCREMENT is a first-class feature with full support through conventions and the Fluent API. + +### Configuring AUTOINCREMENT + +By convention, integer primary keys are automatically configured with AUTOINCREMENT when they are not composite and don't have a foreign key on them. However, you may need to explicitly configure a property to use SQLite AUTOINCREMENT when the property has a value conversion from a non-integer type, or when overriding conventions: + +[!code-csharp[Main](../../../../samples/core/Sqlite/ValueGeneration/SqliteAutoincrementWithValueConverter.cs?name=SqliteAutoincrementWithValueConverter&highlight=6)] + +## Disabling AUTOINCREMENT for default SQLite value generation + +In some cases, you may want to disable AUTOINCREMENT and use SQLite's default value generation behavior instead. You can do this using the Metadata API: + +[!code-csharp[Main](../../../../samples/core/Sqlite/ValueGeneration/SqliteValueGenerationStrategyNone.cs?name=SqliteValueGenerationStrategyNone&highlight=5)] + +Starting with EF Core 10, you can also use the strongly-typed Metadata API: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .Property(p => p.Id) + .Metadata.SetValueGenerationStrategy(SqliteValueGenerationStrategy.None); +} +``` + +Alternatively, you can disable value generation entirely: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .Property(b => b.Id) + .ValueGeneratedNever(); +} +``` + +This means that it's up to the application to supply a value for the property before saving to the database. Note that this still won't disable the default value generation server-side, so non-EF usages could still get a generated value. To completely disable value generation the user can change the column type from `INTEGER` to `INT`. + +## Migration behavior + +When EF Core generates migrations for SQLite AUTOINCREMENT columns, the generated migration will include the `Sqlite:Autoincrement` annotation: + +```csharp +migrationBuilder.CreateTable( + name: "Blogs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Blogs", x => x.Id); + }); +``` + +This ensures that the AUTOINCREMENT feature is properly applied when the migration is executed against the SQLite database. diff --git a/entity-framework/core/what-is-new/ef-core-10.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-10.0/whatsnew.md index 5b84bddb7d..ccbde80e7b 100644 --- a/entity-framework/core/what-is-new/ef-core-10.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-10.0/whatsnew.md @@ -628,6 +628,16 @@ await context.Blogs.ExecuteUpdateAsync(s => Thanks to [@aradalvand](https://github.com/aradalvand) for proposing and pushing for this change (in [#32018](https://github.com/dotnet/efcore/issues/32018)). + + +## SQLite + +### Improved AUTOINCREMENT support + +SQLite AUTOINCREMENT is now a first-class feature with full support through conventions and the Fluent API. Previously, properties with value converters couldn't configure AUTOINCREMENT and would cause false pending model change warnings. + +For more information, see [SQLite Value Generation](xref:core/providers/sqlite/value-generation). + ## Other improvements diff --git a/entity-framework/toc.yml b/entity-framework/toc.yml index 12b1bb3055..529e1a2800 100644 --- a/entity-framework/toc.yml +++ b/entity-framework/toc.yml @@ -423,6 +423,8 @@ href: core/providers/sqlite/limitations.md - name: Function mappings href: core/providers/sqlite/functions.md + - name: Value generation + href: core/providers/sqlite/value-generation.md - name: Spatial data displayName: GIS href: core/providers/sqlite/spatial.md diff --git a/samples/core/Sqlite/ValueGeneration/SqliteAutoincrementWithValueConverter.cs b/samples/core/Sqlite/ValueGeneration/SqliteAutoincrementWithValueConverter.cs new file mode 100644 index 0000000000..e5cae61860 --- /dev/null +++ b/samples/core/Sqlite/ValueGeneration/SqliteAutoincrementWithValueConverter.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; + +namespace EFCore.Sqlite.ValueGeneration; + +public readonly struct BlogId +{ + public BlogId(int value) => Value = value; + public int Value { get; } + + public static implicit operator int(BlogId id) => id.Value; + public static implicit operator BlogId(int value) => new(value); +} + +public class SqliteAutoincrementWithValueConverterContext : DbContext +{ + public DbSet Blogs { get; set; } + + #region SqliteAutoincrementWithValueConverter + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(b => b.Id) + .HasConversion() + .UseAutoincrement(); + } + #endregion + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("Data Source=sample.db"); +} + +public class BlogPost +{ + public BlogId Id { get; set; } + public string Title { get; set; } +} \ No newline at end of file diff --git a/samples/core/Sqlite/ValueGeneration/SqliteValueGeneration.csproj b/samples/core/Sqlite/ValueGeneration/SqliteValueGeneration.csproj new file mode 100644 index 0000000000..6798d6ed9c --- /dev/null +++ b/samples/core/Sqlite/ValueGeneration/SqliteValueGeneration.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + disable + Library + + + + + + + \ No newline at end of file diff --git a/samples/core/Sqlite/ValueGeneration/SqliteValueGenerationStrategyNone.cs b/samples/core/Sqlite/ValueGeneration/SqliteValueGenerationStrategyNone.cs new file mode 100644 index 0000000000..35711cbe5d --- /dev/null +++ b/samples/core/Sqlite/ValueGeneration/SqliteValueGenerationStrategyNone.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata; + +namespace EFCore.Sqlite.ValueGeneration; + +public class SqliteValueGenerationStrategyNoneContext : DbContext +{ + public DbSet Posts { get; set; } + + #region SqliteValueGenerationStrategyNone + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(p => p.Id) + .Metadata.SetValueGenerationStrategy(SqliteValueGenerationStrategy.None); + } + #endregion + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("Data Source=sample.db"); +} + +public class Post +{ + public int Id { get; set; } + public string Content { get; set; } +} \ No newline at end of file diff --git a/samples/global.json b/samples/global.json new file mode 100644 index 0000000000..be4942c34f --- /dev/null +++ b/samples/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "10.0.100-rc.1.25451.107" + } +} \ No newline at end of file