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
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<Version>0.3.1</Version>
<Version>0.3.2</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
Expand Down
15 changes: 14 additions & 1 deletion src/EfOrderBy/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ sealed class Configuration(Type elementType)
static readonly ConcurrentDictionary<Type, Configuration> 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);
Expand Down
22 changes: 18 additions & 4 deletions src/Tests/DuplicateOrderByTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -186,20 +200,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

public class OrderByWithThenByContext(DbContextOptions options) : DbContext(options)
{
public DbSet<DuplicateTestEntity> Entities => Set<DuplicateTestEntity>();
public DbSet<ThenByEntity> Entities => Set<ThenByEntity>();

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<DuplicateTestEntity>()
modelBuilder.Entity<ThenByEntity>()
.OrderBy(_ => _.Name)
.ThenBy(_ => _.Priority); // Correct usage
}

public class OrderByDescWithThenByDescContext(DbContextOptions options) : DbContext(options)
{
public DbSet<DuplicateTestEntity> Entities => Set<DuplicateTestEntity>();
public DbSet<ThenByDescEntity> Entities => Set<ThenByDescEntity>();

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<DuplicateTestEntity>()
modelBuilder.Entity<ThenByDescEntity>()
.OrderByDescending(_ => _.Name)
.ThenByDescending(_ => _.Priority); // Correct usage
}
Expand Down
55 changes: 55 additions & 0 deletions src/Tests/MigrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContextWithNameOrdering>()
.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<ContextWithValueOrdering>()
.UseSqlServer("Server=.;Database=Test;Trusted_Connection=True")
.UseDefaultOrderBy()
.Options;

var exception = Assert.Throws<InvalidOperationException>(() =>
{
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<SharedEntity> Entities => Set<SharedEntity>();

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<SharedEntity>()
.OrderBy(_ => _.Name);
}

class ContextWithValueOrdering(DbContextOptions options) : DbContext(options)
{
public DbSet<SharedEntity> Entities => Set<SharedEntity>();

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<SharedEntity>()
.OrderByDescending(_ => _.Value);
}
4 changes: 2 additions & 2 deletions src/Tests/RequireOrderingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestEntity>()
.OrderBy(_ => _.CreatedDate);
.OrderByDescending(_ => _.CreatedDate);
}
}

Expand Down