Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/StrEnum.Npgsql.EntityFrameworkCore/ModelBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,19 @@ public static ModelBuilder MapStringEnumAsPostgresEnum<TEnum>(this ModelBuilder

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (entityType.ClrType is null)
continue;

var entityBuilder = modelBuilder.Entity(entityType.ClrType);

foreach (var property in entityType.GetProperties())
{
if (property.ClrType != typeof(TEnum))
continue;

// No HasConversion — see PropertyBuilderExtensions.HasPostgresStringEnum for why.
entityBuilder.Property(property.Name).HasColumnType(columnType);
// Mutate IMutableProperty directly instead of going through modelBuilder.Entity(...).Property(...).
// The latter re-enters EF's entity-discovery conventions, which will probe every CLR property
// on the entity (and on any reference-typed nested types they pull in). For an entity whose
// complex-collection element exposes e.g. Dictionary<K, V> with a user-supplied HasConversion,
// that probing fires before the user's fluent config has been applied — and EF then throws
// "Unable to determine the relationship represented by navigation ...". No HasConversion call
// here either — see PropertyBuilderExtensions.HasPostgresStringEnum for why.
property.SetColumnType(columnType);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Xunit;

namespace StrEnum.Npgsql.EntityFrameworkCore.UnitTests;
Expand Down Expand Up @@ -94,4 +95,72 @@ public void MapStringEnumAsPostgresEnum_QualifiesColumnTypeWithSchema()

sportProperty.GetColumnType().Should().Be("races.sport");
}

public class Stage
{
public Dictionary<int, long>? Splits { get; set; }
}

public class Schedule
{
public List<Stage> Stages { get; set; } = new();
}

public class Tournament
{
public Guid Id { get; set; }
public Schedule Schedule { get; set; } = new();
public Sport Sport { get; set; } = null!;
}

private sealed class DictionaryToStringConverter : ValueConverter<Dictionary<int, long>?, string>
{
public DictionaryToStringConverter()
: base(d => "", s => new Dictionary<int, long>())
{
}
}

private sealed class TournamentContext : DbContext
{
public DbSet<Tournament> Tournaments => Set<Tournament>();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseNpgsql("Host=localhost;Database=tests")
.UseStringEnums()
.ReplaceService<IModelCacheKeyFactory, UncachedModelKeyFactory>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Walker call FIRST, before the entity is configured — matches the failing consumer order.
modelBuilder.MapStringEnumAsPostgresEnum<Sport>();

modelBuilder.Entity<Tournament>(t =>
{
t.ComplexProperty(x => x.Schedule, schedule =>
{
schedule.ToJson("schedule");
schedule.ComplexCollection(s => s.Stages, stage =>
{
stage.Property(s => s.Splits).HasConversion(new DictionaryToStringConverter());
});
});
});
}

public IModel DesignTimeModel => this.GetService<IDesignTimeModel>().Model;
}

[Fact]
public void MapStringEnumAsPostgresEnum_DoesNotProbeCustomConvertedComplexCollectionProperties()
{
using var context = new TournamentContext();

var act = () => context.DesignTimeModel;

act.Should().NotThrow();
}
}
Loading