diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5572c51..2d85462 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ enable latest true - 0.3.1 + 0.3.2 1.0.0 true true diff --git a/src/EfOrderBy/Configuration.cs b/src/EfOrderBy/Configuration.cs index fdfbe58..2880012 100644 --- a/src/EfOrderBy/Configuration.cs +++ b/src/EfOrderBy/Configuration.cs @@ -8,7 +8,20 @@ sealed class Configuration(Type elementType) static readonly ConcurrentDictionary cache = new(); internal static void Cache(Type entityType, Configuration configuration) - => cache[entityType] = configuration; + => cache.AddOrUpdate( + entityType, + configuration, + (type, existing) => + { + if (!existing.ClauseMetadataList.SequenceEqual(configuration.ClauseMetadataList)) + { + throw new InvalidOperationException( + $"Conflicting default ordering configurations for entity type '{type.Name}'. " + + $"When multiple DbContext types share the same entity, they must configure the same default ordering."); + } + + return existing; + }); internal static Configuration? TryGet(Type entityType) => cache.GetValueOrDefault(entityType); diff --git a/src/Tests/DuplicateOrderByTests.cs b/src/Tests/DuplicateOrderByTests.cs index 73468e9..709524d 100644 --- a/src/Tests/DuplicateOrderByTests.cs +++ b/src/Tests/DuplicateOrderByTests.cs @@ -132,6 +132,20 @@ public class AnotherDuplicateTestEntity public string Value { get; set; } = ""; } +public class ThenByEntity +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public int Priority { get; set; } +} + +public class ThenByDescEntity +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public int Priority { get; set; } +} + #endregion #region Test Contexts - Each with unique configuration @@ -186,20 +200,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public class OrderByWithThenByContext(DbContextOptions options) : DbContext(options) { - public DbSet Entities => Set(); + public DbSet Entities => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) => - modelBuilder.Entity() + modelBuilder.Entity() .OrderBy(_ => _.Name) .ThenBy(_ => _.Priority); // Correct usage } public class OrderByDescWithThenByDescContext(DbContextOptions options) : DbContext(options) { - public DbSet Entities => Set(); + public DbSet Entities => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) => - modelBuilder.Entity() + modelBuilder.Entity() .OrderByDescending(_ => _.Name) .ThenByDescending(_ => _.Priority); // Correct usage } diff --git a/src/Tests/MigrationTests.cs b/src/Tests/MigrationTests.cs index 32a2e33..9b7729f 100644 --- a/src/Tests/MigrationTests.cs +++ b/src/Tests/MigrationTests.cs @@ -105,4 +105,59 @@ public void DesignTimeModelHasNoConfigurationAnnotations() Assert.That(designTimeModel.FindAnnotation("DefaultOrderBy:InterceptorRegistered"), Is.Null); Assert.That(designTimeModel.FindAnnotation("DefaultOrderBy:IndexCreationDisabled"), Is.Null); } + + [Test] + public void ConflictingOrderingAcrossContexts_Throws() + { + // First context configures SharedEntity with OrderBy(Name) + var options1 = new DbContextOptionsBuilder() + .UseSqlServer("Server=.;Database=Test;Trusted_Connection=True") + .UseDefaultOrderBy() + .Options; + + using (var context = new ContextWithNameOrdering(options1)) + { + _ = context.Model; + } + + // Second context configures SharedEntity with OrderByDescending(Value) - should throw + var options2 = new DbContextOptionsBuilder() + .UseSqlServer("Server=.;Database=Test;Trusted_Connection=True") + .UseDefaultOrderBy() + .Options; + + var exception = Assert.Throws(() => + { + using var context = new ContextWithValueOrdering(options2); + _ = context.Model; + }); + + Assert.That(exception!.Message, Does.Contain("SharedEntity")); + Assert.That(exception.Message, Does.Contain("Conflicting")); + } +} + +public class SharedEntity +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Value { get; set; } = ""; +} + +class ContextWithNameOrdering(DbContextOptions options) : DbContext(options) +{ + public DbSet Entities => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.Entity() + .OrderBy(_ => _.Name); +} + +class ContextWithValueOrdering(DbContextOptions options) : DbContext(options) +{ + public DbSet Entities => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.Entity() + .OrderByDescending(_ => _.Value); } diff --git a/src/Tests/RequireOrderingTests.cs b/src/Tests/RequireOrderingTests.cs index 907b619..4cdeba9 100644 --- a/src/Tests/RequireOrderingTests.cs +++ b/src/Tests/RequireOrderingTests.cs @@ -113,9 +113,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - // All entities have ordering configured + // All entities have ordering configured (must match TestDbContext's ordering) modelBuilder.Entity() - .OrderBy(_ => _.CreatedDate); + .OrderByDescending(_ => _.CreatedDate); } }