efcore.pg
efcore.pg copied to clipboard
Migration does not specify a default value for non-nullable List<> properties
Version: Npgsql.EntityFrameworkCore.PostgreSQL 8.0.4
Reproduction:
Add property public List<string> NewArray { get; set; } = []; to entity model.
Add Migration.
Actual:
migrationBuilder.AddColumn<List<string>>(
name: "new_array",
table: "some_table",
type: "text[]",
nullable: false);
Expected:
migrationBuilder.AddColumn<List<string>>(
name: "new_array",
table: "some_table",
type: "text[]",
defaultValue: new List<string>(),
nullable: false);
Because the migration does not specify a default value, when the migration is run, when the table some_table is not empty the migration fails because it attempts to insert null into the non-nullable column.
@hackf5 thanks for filing, but can you please show where this is actually affecting your program, e.g. causing an error?
Note added: migration fails due to attempt to insert null into non-nullable column.
Sorry for taking so long to answer this.
Unfortunately EF's own infrastructure around handling default values for new columns is lacking - I opened https://github.com/dotnet/efcore/issues/34790 to track that. In the meantime, you can get around this by explicitly specifying a default value on the new column:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(b => b.NewArray).HasDefaultValue(new List<string> { "bla" });
}
Thanks for opening the issue on ef core.
Explicitly setting a default value is a decent workaround.
On Mon, 30 Sept 2024, 06:37 Shay Rojansky, @.***> wrote:
Sorry for taking so long to answer this.
Unfortunately EF's own infrastructure around handling default values for new columns is lacking - I opened dotnet/efcore#34790 https://github.com/dotnet/efcore/issues/34790 to track that. In the meantime, you can get around this by explicitly specifying a default value on the new column:
protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.Entity<Blog>().Property(b => b.NewArray).HasDefaultValue(new List
{ "bla" });} — Reply to this email directly, view it on GitHub https://github.com/npgsql/efcore.pg/issues/3244#issuecomment-2382152910, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALU56HJYAPOK6DBDOEPOOITZZDPRXAVCNFSM6AAAAABMTV4VGWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGOBSGE2TEOJRGA . You are receiving this because you were mentioned.Message ID: @.***>
@roji workaround adds default value to table definition, but now model differ always reports model is changed and generates bogus migrations
migrationBuilder.AlterColumn<List<Guid>>(
name: "Risk",
schema: "schema",
table: "MyTable",
type: "uuid[]",
nullable: false,
defaultValue: new List<Guid>(),
oldClrType: typeof(List<Guid>),
oldType: "uuid[]",
oldDefaultValue: new List<Guid>());
Probably due to List is reference type with deafault Equals.
I tried to hack it around with
public class EquatableList : List<Guid>, IEquatable<EquatableList>, IEquatable<List<Guid>>
But it refuses to generate migration
System.NotSupportedException: The type mapping for 'EquatableList' has not implemented code literal generation.
at Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping.GenerateCodeLiteral(Object value)
at Microsoft.EntityFrameworkCore.Design.Internal.CSharpHelper.UnknownLiteral(Object value)
at Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationOperationGenerator.Generate(AlterColumnOperation operation, IndentedStringBuilder builder)
...
I ended up with
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
configurationBuilder.Conventions.Add(_ => new NotNullPrimitiveCollectionConvention());
}
internal sealed class NotNullPrimitiveCollectionConvention : IModelFinalizingConvention
{
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
{
foreach (var prop in modelBuilder.Metadata.GetEntityTypes()
.SelectMany(x => x.GetProperties().Where(x => x.IsPrimitiveCollection && !x.IsNullable)))
{
if (prop.GetDefaultValueSql() == null)
{
// prop.SetDefaultValueSql("ARRAY[]::" + prop.GetColumnType());
// TypeMapping is not available in conventions :(
Type type = prop.GetElementType()!.ClrType;
string sqlType;
if (type == typeof(Guid))
sqlType = "uuid";
else
throw new NotSupportedException(type.FullName);
prop.SetDefaultValueSql($"ARRAY[]::{sqlType}[]");
}
}
}
}