efcore.pg
efcore.pg copied to clipboard
Repeated UpdateData for List<string> property with HasData and migrations
Bug description
When using EF Core's HasData to seed data for an entity that contains a List
Your code
await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
public class BlogContext : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql("Host=localhost;Username=test;Password=test")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().HasData(
new MyEntity
{
Id = 1,
Tags = new List<string> { "A", "B", "C" }
}
);
}
}
public class MyEntity
{
public int Id { get; set; }
public List<string> Tags { get; set; }
}
EF Core version
9.0.4
Database provider
Npgsql.EntityFrameworkCore.PostgreSQL
Target framework
.NET 9.0
Operating system
Windows 11
IDE
Visual Studio 2022 17.4
Thanks @Varorbc, I can see this happening now - will investigate.
As a workaround, i have done this in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
AddManualListComparer<int>(modelBuilder);
AddManualListComparer<string>(modelBuilder);
AddManualListComparer<long>(modelBuilder);
}
private static void AddManualListComparer<TElement>(ModelBuilder modelBuilder)
{
foreach (var type in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in type.GetProperties())
{
// Fix Npgsql bug #3635: List<int> primitive collections have broken ValueComparer
// https://github.com/npgsql/efcore.pg/issues/3635
if (property.ClrType == typeof(List<TElement>) && property.IsPrimitiveCollection)
{
property.SetValueComparer(
new ValueComparer<List<TElement>>(
(c1, c2) =>
(c1 == null && c2 == null)
|| (c1 != null && c2 != null && c1.SequenceEqual(c2)),
c => c == null ? 0 : c.Aggregate(0, (a, v) => HashCode.Combine(a, v)),
c => c == null ? null! : c.ToList()
)
);
}
}
}
}
It's a bit of a brute-force solution, but it at least prevents the extra migration changes :)