Skip to content

Commit 8bb2a1f

Browse files
authored
Remove missing-order enforcement and related code (#944)
Remove the built-in missing-ORDER BY enforcement and all associated plumbing: delete MissingOrder visitor/factory/visitor files, remove ThrowForMissingOrderBy API, drop order-required SqlInstance and related tests/verified outputs, and remove NameOrAlias helper. Update docs and snippets to no longer reference the removed feature and add guidance to use EntityFramework.OrderBy instead. Adjust DbContextBuilder initialization and global usings accordingly.
1 parent d8ca8a2 commit 8bb2a1f

15 files changed

Lines changed: 24 additions & 331 deletions

claude.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ src/
3636
│ │ ├── QueryableConverter.cs # Converts IQueryable to SQL + results
3737
│ │ ├── TrackerConverter.cs # Converts ChangeTracker state
3838
│ │ └── LogEntryConverter.cs # Recorded SQL entries
39-
│ ├── LogCommandInterceptor.cs # DbCommandInterceptor for SQL recording
40-
│ └── MissingOrder/ # Detects missing ORDER BY clauses
39+
│ └── LogCommandInterceptor.cs # DbCommandInterceptor for SQL recording
4140
4241
├── Verify.EntityFramework.Tests/ # EF Core tests
4342
├── Verify.EntityFrameworkClassic/ # EF 6 library
@@ -64,7 +63,6 @@ Tests use `[ModuleInitializer]` to call `VerifyEntityFramework.Initialize(model)
6463
- `IgnoreNavigationProperties()` - Exclude EF navigation properties from serialization
6564
- `EnableRecording()` - Enable SQL command recording on DbContextOptionsBuilder
6665
- `DisableRecording()` - Stop recording for a specific context instance
67-
- `ThrowForMissingOrderBy()` - Enforce ORDER BY clauses in queries
6866
- `ScrubInlineEfDateTimes()` - Sanitize DateTime values in SQL
6967

7068
## Testing Conventions

readme.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ builder.UseSqlServer(connection);
8787
builder.EnableRecording();
8888
var data = new SampleDbContext(builder.Options);
8989
```
90-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L384-L391' title='Snippet source file'>snippet source</a> | <a href='#snippet-EnableRecording' title='Start of snippet'>anchor</a></sup>
90+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L329-L336' title='Snippet source file'>snippet source</a> | <a href='#snippet-EnableRecording' title='Start of snippet'>anchor</a></sup>
9191
<!-- endSnippet -->
9292

9393
`EnableRecording` should only be called in the test context.
@@ -116,7 +116,7 @@ await data
116116

117117
await Verify();
118118
```
119-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L483-L501' title='Snippet source file'>snippet source</a> | <a href='#snippet-Recording' title='Start of snippet'>anchor</a></sup>
119+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L428-L446' title='Snippet source file'>snippet source</a> | <a href='#snippet-Recording' title='Start of snippet'>anchor</a></sup>
120120
<!-- endSnippet -->
121121

122122
Will result in the following verified file:
@@ -168,7 +168,7 @@ await Verify(
168168
entries
169169
});
170170
```
171-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L674-L699' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingSpecific' title='Start of snippet'>anchor</a></sup>
171+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L619-L644' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingSpecific' title='Start of snippet'>anchor</a></sup>
172172
<!-- endSnippet -->
173173

174174

@@ -200,7 +200,7 @@ await data2
200200

201201
await Verify();
202202
```
203-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L451-L474' title='Snippet source file'>snippet source</a> | <a href='#snippet-MultiDbContexts' title='Start of snippet'>anchor</a></sup>
203+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L396-L419' title='Snippet source file'>snippet source</a> | <a href='#snippet-MultiDbContexts' title='Start of snippet'>anchor</a></sup>
204204
<!-- endSnippet -->
205205

206206
<!-- snippet: CoreTests.MultiDbContexts.verified.txt -->
@@ -265,7 +265,7 @@ await data
265265

266266
await Verify();
267267
```
268-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L545-L568' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingDisableForInstance' title='Start of snippet'>anchor</a></sup>
268+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L490-L513' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingDisableForInstance' title='Start of snippet'>anchor</a></sup>
269269
<!-- endSnippet -->
270270

271271
<!-- snippet: CoreTests.RecordingDisabledTest.verified.txt -->
@@ -313,7 +313,7 @@ public async Task Added()
313313
await Verify(data.ChangeTracker);
314314
}
315315
```
316-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L87-L103' title='Snippet source file'>snippet source</a> | <a href='#snippet-Added' title='Start of snippet'>anchor</a></sup>
316+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L32-L48' title='Snippet source file'>snippet source</a> | <a href='#snippet-Added' title='Start of snippet'>anchor</a></sup>
317317
<!-- endSnippet -->
318318

319319
Will result in the following verified file:
@@ -358,7 +358,7 @@ public async Task Deleted()
358358
await Verify(data.ChangeTracker);
359359
}
360360
```
361-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L105-L124' title='Snippet source file'>snippet source</a> | <a href='#snippet-Deleted' title='Start of snippet'>anchor</a></sup>
361+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L50-L69' title='Snippet source file'>snippet source</a> | <a href='#snippet-Deleted' title='Start of snippet'>anchor</a></sup>
362362
<!-- endSnippet -->
363363

364364
Will result in the following verified file:
@@ -403,7 +403,7 @@ public async Task Modified()
403403
await Verify(data.ChangeTracker);
404404
}
405405
```
406-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L126-L146' title='Snippet source file'>snippet source</a> | <a href='#snippet-Modified' title='Start of snippet'>anchor</a></sup>
406+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L71-L91' title='Snippet source file'>snippet source</a> | <a href='#snippet-Modified' title='Start of snippet'>anchor</a></sup>
407407
<!-- endSnippet -->
408408

409409
Will result in the following verified file:
@@ -438,7 +438,7 @@ var queryable = data.Companies
438438
.Where(_ => _.Name == "company name");
439439
await Verify(queryable);
440440
```
441-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L341-L347' title='Snippet source file'>snippet source</a> | <a href='#snippet-Queryable' title='Start of snippet'>anchor</a></sup>
441+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L286-L292' title='Snippet source file'>snippet source</a> | <a href='#snippet-Queryable' title='Start of snippet'>anchor</a></sup>
442442
<!-- endSnippet -->
443443

444444
Will result in the following verified files:
@@ -505,7 +505,7 @@ await Verify(data.AllData())
505505
serializer =>
506506
serializer.TypeNameHandling = TypeNameHandling.Objects);
507507
```
508-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L320-L327' title='Snippet source file'>snippet source</a> | <a href='#snippet-AllData' title='Start of snippet'>anchor</a></sup>
508+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L265-L272' title='Snippet source file'>snippet source</a> | <a href='#snippet-AllData' title='Start of snippet'>anchor</a></sup>
509509
<!-- endSnippet -->
510510

511511
Will result in the following verified file with all data in the database:
@@ -588,7 +588,7 @@ public async Task IgnoreNavigationProperties()
588588
.IgnoreNavigationProperties();
589589
}
590590
```
591-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L148-L170' title='Snippet source file'>snippet source</a> | <a href='#snippet-IgnoreNavigationProperties' title='Start of snippet'>anchor</a></sup>
591+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L93-L115' title='Snippet source file'>snippet source</a> | <a href='#snippet-IgnoreNavigationProperties' title='Start of snippet'>anchor</a></sup>
592592
<!-- endSnippet -->
593593

594594

@@ -601,7 +601,7 @@ var options = DbContextOptions();
601601
using var data = new SampleDbContext(options);
602602
VerifyEntityFramework.IgnoreNavigationProperties();
603603
```
604-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L198-L204' title='Snippet source file'>snippet source</a> | <a href='#snippet-IgnoreNavigationPropertiesGlobal' title='Start of snippet'>anchor</a></sup>
604+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L143-L149' title='Snippet source file'>snippet source</a> | <a href='#snippet-IgnoreNavigationPropertiesGlobal' title='Start of snippet'>anchor</a></sup>
605605
<!-- endSnippet -->
606606

607607

@@ -622,7 +622,7 @@ protected override void ConfigureWebHost(IWebHostBuilder webBuilder)
622622
_ => dataBuilder.Options));
623623
}
624624
```
625-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L628-L640' title='Snippet source file'>snippet source</a> | <a href='#snippet-EnableRecordingWithIdentifier' title='Start of snippet'>anchor</a></sup>
625+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L573-L585' title='Snippet source file'>snippet source</a> | <a href='#snippet-EnableRecordingWithIdentifier' title='Start of snippet'>anchor</a></sup>
626626
<!-- endSnippet -->
627627

628628
Then use the same identifier for recording:
@@ -638,7 +638,7 @@ var companies = await httpClient.GetFromJsonAsync<Company[]>("/companies");
638638

639639
var entries = Recording.Stop(testName);
640640
```
641-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L601-L611' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordWithIdentifier' title='Start of snippet'>anchor</a></sup>
641+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L546-L556' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordWithIdentifier' title='Start of snippet'>anchor</a></sup>
642642
<!-- endSnippet -->
643643

644644
The results will not be automatically included in verified file so it will have to be verified manually:
@@ -653,10 +653,15 @@ await Verify(
653653
sql = entries
654654
});
655655
```
656-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L613-L622' title='Snippet source file'>snippet source</a> | <a href='#snippet-VerifyRecordedCommandsWithIdentifier' title='Start of snippet'>anchor</a></sup>
656+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L558-L567' title='Snippet source file'>snippet source</a> | <a href='#snippet-VerifyRecordedCommandsWithIdentifier' title='Start of snippet'>anchor</a></sup>
657657
<!-- endSnippet -->
658658

659659

660+
## Missing OrderBy
661+
662+
To detect and correct missing `OrderBy` clauses in EF queries, use [EntityFramework.OrderBy](https://github.com/SimonCropp/EntityFramework.OrderBy).
663+
664+
660665
## ScrubInlineEfDateTimes
661666

662667
In some scenarios EntityFrmaeowrk does not parameterise DateTimes. For example when querying [temporal tables](https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables).
@@ -680,7 +685,7 @@ var settings = new VerifySettings();
680685
settings.ScrubInlineEfDateTimes();
681686
await Verify(target, settings);
682687
```
683-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L34-L40' title='Snippet source file'>snippet source</a> | <a href='#snippet-ScrubInlineEfDateTimesInstance' title='Start of snippet'>anchor</a></sup>
688+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L10-L16' title='Snippet source file'>snippet source</a> | <a href='#snippet-ScrubInlineEfDateTimesInstance' title='Start of snippet'>anchor</a></sup>
684689
<!-- endSnippet -->
685690

686691

@@ -692,7 +697,7 @@ await Verify(target, settings);
692697
await Verify(target)
693698
.ScrubInlineEfDateTimes();
694699
```
695-
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L48-L53' title='Snippet source file'>snippet source</a> | <a href='#snippet-ScrubInlineEfDateTimesFluent' title='Start of snippet'>anchor</a></sup>
700+
<sup><a href='/src/Verify.EntityFramework.Tests/CoreTests.cs#L24-L29' title='Snippet source file'>snippet source</a> | <a href='#snippet-ScrubInlineEfDateTimesFluent' title='Start of snippet'>anchor</a></sup>
696701
<!-- endSnippet -->
697702

698703

src/Verify.EntityFramework.Tests/CoreTests.MissingOrderBy.verified.txt

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/Verify.EntityFramework.Tests/CoreTests.NestedMissingOrderBy.verified.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/Verify.EntityFramework.Tests/CoreTests.SingleMissingOrder.verified.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/Verify.EntityFramework.Tests/CoreTests.WithNestedOrderBy.verified.txt

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/Verify.EntityFramework.Tests/CoreTests.WithOrderBy.verified.txt

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/Verify.EntityFramework.Tests/CoreTests.cs

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,6 @@
22
[Parallelizable(ParallelScope.All)]
33
public class CoreTests
44
{
5-
[Test]
6-
public async Task MissingOrderBy()
7-
{
8-
await using var database = await DbContextBuilder.GetOrderRequiredDatabase();
9-
var data = database.Context;
10-
await ThrowsTask(
11-
() => data.Companies
12-
.ToListAsync())
13-
.IgnoreStackTrace();
14-
}
15-
16-
[Test]
17-
public async Task NestedMissingOrderBy()
18-
{
19-
await using var database = await DbContextBuilder.GetOrderRequiredDatabase();
20-
var data = database.Context;
21-
await ThrowsTask(
22-
() => data.Companies
23-
.Include(_ => _.Employees)
24-
.OrderBy(_ => _.Name)
25-
.ToListAsync())
26-
.IgnoreStackTrace();
27-
}
28-
295
[Test]
306
public async Task ScrubInlineEfDateTimes()
317
{
@@ -53,37 +29,6 @@ await Verify(target)
5329
#endregion
5430
}
5531

56-
[Test]
57-
public async Task WithOrderBy()
58-
{
59-
await using var database = await DbContextBuilder.GetOrderRequiredDatabase();
60-
var data = database.Context;
61-
await Verify(
62-
data.Companies
63-
.OrderBy(_ => _.Name)
64-
.ToListAsync());
65-
}
66-
67-
[Test]
68-
public async Task SingleMissingOrder()
69-
{
70-
await using var database = await DbContextBuilder.GetOrderRequiredDatabase();
71-
var data = database.Context;
72-
await Verify(data.Companies.Where(_ => _.Name == "Company1").SingleAsync());
73-
}
74-
75-
[Test]
76-
public async Task WithNestedOrderBy()
77-
{
78-
await using var database = await DbContextBuilder.GetOrderRequiredDatabase();
79-
var data = database.Context;
80-
await Verify(
81-
data.Companies
82-
.Include(_ => _.Employees.OrderBy(_ => _.Age))
83-
.OrderBy(_ => _.Name)
84-
.ToListAsync());
85-
}
86-
8732
#region Added
8833

8934
[Test]

src/Verify.EntityFramework.Tests/Snippets/DbContextBuilder.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,16 @@
33

44
public static class DbContextBuilder
55
{
6-
static DbContextBuilder()
7-
{
6+
static DbContextBuilder() =>
87
sqlInstance = new(
98
buildTemplate: CreateDb,
109
constructInstance: builder =>
1110
{
1211
builder.EnableRecording();
1312
return new(builder.Options);
1413
});
15-
orderRequiredSqlInstance = new(
16-
buildTemplate: CreateDb,
17-
storage: Storage.FromSuffix<SampleDbContext>("ThrowForMissingOrderBy"),
18-
constructInstance: builder =>
19-
{
20-
builder.EnableRecording();
21-
builder.ThrowForMissingOrderBy();
22-
return new(builder.Options);
23-
});
24-
}
2514

2615
static SqlInstance<SampleDbContext> sqlInstance;
27-
static SqlInstance<SampleDbContext> orderRequiredSqlInstance;
2816

2917
static async Task CreateDb(SampleDbContext data)
3018
{
@@ -77,7 +65,4 @@ static async Task CreateDb(SampleDbContext data)
7765

7866
public static Task<SqlDatabase<SampleDbContext>> GetDatabase([CallerMemberName] string suffix = "")
7967
=> sqlInstance.Build(suffix);
80-
81-
public static Task<SqlDatabase<SampleDbContext>> GetOrderRequiredDatabase([CallerMemberName] string suffix = "")
82-
=> orderRequiredSqlInstance.Build(suffix);
8368
}

src/Verify.EntityFramework/Extensions.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,6 @@ public static IQueryable<object> AsNoTracking(this IQueryable<object> set, Type
1111
return (IQueryable<object>) genericNoTracking.Invoke(null, [set])!;
1212
}
1313

14-
public static string? NameOrAlias(this TableExpressionBase tableExpressionBase)
15-
{
16-
if (tableExpressionBase.Alias != null)
17-
{
18-
return tableExpressionBase.Alias;
19-
}
20-
21-
if (tableExpressionBase is TableExpression tableExpression)
22-
{
23-
return tableExpression.Name;
24-
}
25-
26-
return null;
27-
}
28-
2914
public static IQueryable<object> Set(this DbContext data, Type t) =>
3015
(IQueryable<object>) setMethod
3116
.MakeGenericMethod(t)

0 commit comments

Comments
 (0)