Skip to content

Commit 7fb6fd7

Browse files
authored
Detect conflicting default ordering across contexts (#24)
Add runtime validation to Configuration.Cache to detect and reject conflicting default ordering configurations for the same entity type across different DbContext types, throwing an InvalidOperationException when ClauseMetadataList differs. Add tests to cover the new behavior (MigrationTests.ConflictingOrderingAcrossContexts_Throws), introduce SharedEntity and supporting contexts, and add ThenBy/ThenByDesc test entities and contexts. Also adjust RequireOrderingTests to match the expected ordering and bump package version to 0.3.2 in Directory.Build.props.
1 parent 955f57a commit 7fb6fd7

5 files changed

Lines changed: 90 additions & 8 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Nullable>enable</Nullable>
66
<LangVersion>latest</LangVersion>
77
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
8-
<Version>0.3.1</Version>
8+
<Version>0.3.2</Version>
99
<AssemblyVersion>1.0.0</AssemblyVersion>
1010
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1111
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>

src/EfOrderBy/Configuration.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ sealed class Configuration(Type elementType)
88
static readonly ConcurrentDictionary<Type, Configuration> cache = new();
99

1010
internal static void Cache(Type entityType, Configuration configuration)
11-
=> cache[entityType] = configuration;
11+
=> cache.AddOrUpdate(
12+
entityType,
13+
configuration,
14+
(type, existing) =>
15+
{
16+
if (!existing.ClauseMetadataList.SequenceEqual(configuration.ClauseMetadataList))
17+
{
18+
throw new InvalidOperationException(
19+
$"Conflicting default ordering configurations for entity type '{type.Name}'. " +
20+
$"When multiple DbContext types share the same entity, they must configure the same default ordering.");
21+
}
22+
23+
return existing;
24+
});
1225

1326
internal static Configuration? TryGet(Type entityType)
1427
=> cache.GetValueOrDefault(entityType);

src/Tests/DuplicateOrderByTests.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ public class AnotherDuplicateTestEntity
132132
public string Value { get; set; } = "";
133133
}
134134

135+
public class ThenByEntity
136+
{
137+
public int Id { get; set; }
138+
public string Name { get; set; } = "";
139+
public int Priority { get; set; }
140+
}
141+
142+
public class ThenByDescEntity
143+
{
144+
public int Id { get; set; }
145+
public string Name { get; set; } = "";
146+
public int Priority { get; set; }
147+
}
148+
135149
#endregion
136150

137151
#region Test Contexts - Each with unique configuration
@@ -186,20 +200,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
186200

187201
public class OrderByWithThenByContext(DbContextOptions options) : DbContext(options)
188202
{
189-
public DbSet<DuplicateTestEntity> Entities => Set<DuplicateTestEntity>();
203+
public DbSet<ThenByEntity> Entities => Set<ThenByEntity>();
190204

191205
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
192-
modelBuilder.Entity<DuplicateTestEntity>()
206+
modelBuilder.Entity<ThenByEntity>()
193207
.OrderBy(_ => _.Name)
194208
.ThenBy(_ => _.Priority); // Correct usage
195209
}
196210

197211
public class OrderByDescWithThenByDescContext(DbContextOptions options) : DbContext(options)
198212
{
199-
public DbSet<DuplicateTestEntity> Entities => Set<DuplicateTestEntity>();
213+
public DbSet<ThenByDescEntity> Entities => Set<ThenByDescEntity>();
200214

201215
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
202-
modelBuilder.Entity<DuplicateTestEntity>()
216+
modelBuilder.Entity<ThenByDescEntity>()
203217
.OrderByDescending(_ => _.Name)
204218
.ThenByDescending(_ => _.Priority); // Correct usage
205219
}

src/Tests/MigrationTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,59 @@ public void DesignTimeModelHasNoConfigurationAnnotations()
105105
Assert.That(designTimeModel.FindAnnotation("DefaultOrderBy:InterceptorRegistered"), Is.Null);
106106
Assert.That(designTimeModel.FindAnnotation("DefaultOrderBy:IndexCreationDisabled"), Is.Null);
107107
}
108+
109+
[Test]
110+
public void ConflictingOrderingAcrossContexts_Throws()
111+
{
112+
// First context configures SharedEntity with OrderBy(Name)
113+
var options1 = new DbContextOptionsBuilder<ContextWithNameOrdering>()
114+
.UseSqlServer("Server=.;Database=Test;Trusted_Connection=True")
115+
.UseDefaultOrderBy()
116+
.Options;
117+
118+
using (var context = new ContextWithNameOrdering(options1))
119+
{
120+
_ = context.Model;
121+
}
122+
123+
// Second context configures SharedEntity with OrderByDescending(Value) - should throw
124+
var options2 = new DbContextOptionsBuilder<ContextWithValueOrdering>()
125+
.UseSqlServer("Server=.;Database=Test;Trusted_Connection=True")
126+
.UseDefaultOrderBy()
127+
.Options;
128+
129+
var exception = Assert.Throws<InvalidOperationException>(() =>
130+
{
131+
using var context = new ContextWithValueOrdering(options2);
132+
_ = context.Model;
133+
});
134+
135+
Assert.That(exception!.Message, Does.Contain("SharedEntity"));
136+
Assert.That(exception.Message, Does.Contain("Conflicting"));
137+
}
138+
}
139+
140+
public class SharedEntity
141+
{
142+
public int Id { get; set; }
143+
public string Name { get; set; } = "";
144+
public string Value { get; set; } = "";
145+
}
146+
147+
class ContextWithNameOrdering(DbContextOptions options) : DbContext(options)
148+
{
149+
public DbSet<SharedEntity> Entities => Set<SharedEntity>();
150+
151+
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
152+
modelBuilder.Entity<SharedEntity>()
153+
.OrderBy(_ => _.Name);
154+
}
155+
156+
class ContextWithValueOrdering(DbContextOptions options) : DbContext(options)
157+
{
158+
public DbSet<SharedEntity> Entities => Set<SharedEntity>();
159+
160+
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
161+
modelBuilder.Entity<SharedEntity>()
162+
.OrderByDescending(_ => _.Value);
108163
}

src/Tests/RequireOrderingTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
113113
{
114114
base.OnModelCreating(modelBuilder);
115115

116-
// All entities have ordering configured
116+
// All entities have ordering configured (must match TestDbContext's ordering)
117117
modelBuilder.Entity<TestEntity>()
118-
.OrderBy(_ => _.CreatedDate);
118+
.OrderByDescending(_ => _.CreatedDate);
119119
}
120120
}
121121

0 commit comments

Comments
 (0)