Generation of Migrations for new Types / Tables differs in the default value generation from existing types
File a bug
There seems to be a bug in the generation of default values for boolean properties that result in a difference of the table structure in the database.
The bug always happens if you create new project and make an initial schema via code first in C#. Always when you have a property that should have a default value like that:
public bool InitBool { get; set; } = false;
The resulting migration will be:
InitBool = table.Column<bool>(type: "boolean", nullable: false),
See the included code link to the first commit
After the first migration, you can add in the same type, another property in the same way:
public bool AfterInitBool { get; set; } = false;
And the migration code will look like this:
migrationBuilder.AddColumn<bool>(name: "AfterInitBool", table: "SampleData", type: "boolean", nullable: false, defaultValue: false);
Now it will have a configured default value. But an important notice is that the migration snapshot doesn't know about the default value in both cases.
b.Property<bool>("AfterInitBool")
.HasColumnType("boolean");
See the included code link to the second commit
Now if you create a completely new type and add a similar property, the migration will again not generate a column with default value.
See the included code link to the last commit
Include your code
The whole bug description refers to the reprodutiction repository that can be found here. Their are 3 cases that where tests that can be referenced by the following commits:
- Initialize new DB context with a simple schema (commit)
- Add to the existing type of the first commit, new propertiers (commit)
- Add a new type (commit)
Include provider and version information
EF Core version: Database provider: Npgsql.EntityFrameworkCore.PostgreSQL & Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 8.0 Operating system: Windows 11 (22631.4112) IDE: Visual Studio 17.12.0 Preview 1.0 / PowerShell 7.4.5
@maumar do you need more information or assistance? It would be nice to have this fixed before the big ef 9.0 release, if it is not to complicated
@paule96 apologies for delayed response. I'm able to reproduce this. Currently the way to specify default value is with fluent api:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AndAnotherSampleNewTable>(b =>
{
b.Property(x => x.AfterInitBool).HasDefaultValue(true);
b.Property(x => x.AfterInitBoolWithAttributes).HasDefaultValue(true);
b.Property(x => x.AfterInitBoolWithDefaultValueAttribute).HasDefaultValue(true);
});
}
Configuring default value using DefaultValueAttribute is tracked here: https://github.com/dotnet/efcore/issues/19546
When it comes to c# feature of initializing properties (public bool InitBool { get; set; } = false;) we don't think this is strong enough indicator of user's intent here. We want the default value to be specified more explicitly.
closing as duplicate of https://github.com/dotnet/efcore/issues/19546
@maumar I don't know if we should close this issue. Because even if the value assignment is not supported, it still changes effectively the database migration in some cases. So I guess the code path that does that, should be removed from the framework, if it like you said a not strong enough marker. And that is independent from #19546
@paule96 which scenario are you referring to? Those should have no impact on migrations whatsoever, e.g. for the second step, adding the following:
public bool AfterInitBool { get; set; } = false;
[NotNull]
[Required]
public bool AfterInitBoolWithAttributes { get; set; } = false;
[DefaultValue(false)]
public bool AfterInitBoolWithDefaultValueAttribute { get; set; }
public bool AfterInitBoolDefaultTrue { get; set; } = true;
[NotNull]
[Required]
public bool AfterInitBoolWithAttributesDefaultTrue { get; set; } = true;
[DefaultValue(true)]
public bool AfterInitBoolWithDefaultValueAttributeDefaultTrue { get; set; }
public bool AfterInitBoolNoDefault { get; set; }
[NotNull]
[Required]
public bool AfterInitBoolWithAttributesNoDefault { get; set; }
[DefaultValue(17)]
public bool AfterInitBoolWithDefaultValueAttributeMismatchedType { get; set; }
results in the following migration (note that default value in migration code is the same irrespective of property initializer. Basically, EF thinks default has not been set, so it uses the default value for the data type (which in this case is false)
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AfterInitBool",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolDefaultTrue",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolNoDefault",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithAttributes",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithAttributesDefaultTrue",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithAttributesNoDefault",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithDefaultValueAttribute",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithDefaultValueAttributeDefaultTrue",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AfterInitBoolWithDefaultValueAttributeMismatchedType",
table: "SampleData",
type: "bit",
nullable: false,
defaultValue: false);
Closely related to #27084.