From aab2603ec78ce910b1ee23741ebff404dad09390 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Feb 2026 08:21:17 +1100 Subject: [PATCH 1/2] Format IN/NOT IN lists and add tests Add formatting for SQL IN/NOT IN value lists and related tests. Introduce InPredicateCollector to collect InPredicate nodes from TSql fragments and update FormattedScriptGenerator to expand single-line comma lists into indented multi-line blocks for better readability. Add two tests (RecordingInClause, RecordingNotInClause) and their .verified.txt expectations. Update readme snippet source links to reflect new line ranges. --- readme.md | 10 +++--- .../CoreTests.RecordingInClause.verified.txt | 20 +++++++++++ ...oreTests.RecordingNotInClause.verified.txt | 18 ++++++++++ src/Verify.EntityFramework.Tests/CoreTests.cs | 34 +++++++++++++++++++ .../FormattedScriptGenerator.cs | 33 ++++++++++++++++++ .../InPredicateCollector.cs | 7 ++++ 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/Verify.EntityFramework.Tests/CoreTests.RecordingInClause.verified.txt create mode 100644 src/Verify.EntityFramework.Tests/CoreTests.RecordingNotInClause.verified.txt create mode 100644 src/Verify.EntityFramework/InPredicateCollector.cs diff --git a/readme.md b/readme.md index 4e22bf6c..8ed7ba17 100644 --- a/readme.md +++ b/readme.md @@ -168,7 +168,7 @@ await Verify( entries }); ``` -snippet source | anchor +snippet source | anchor @@ -265,7 +265,7 @@ await data await Verify(); ``` -snippet source | anchor +snippet source | anchor @@ -623,7 +623,7 @@ protected override void ConfigureWebHost(IWebHostBuilder webBuilder) _ => dataBuilder.Options)); } ``` -snippet source | anchor +snippet source | anchor Then use the same identifier for recording: @@ -639,7 +639,7 @@ var companies = await httpClient.GetFromJsonAsync("/companies"); var entries = Recording.Stop(testName); ``` -snippet source | anchor +snippet source | anchor The results will not be automatically included in verified file so it will have to be verified manually: @@ -654,7 +654,7 @@ await Verify( sql = entries }); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Verify.EntityFramework.Tests/CoreTests.RecordingInClause.verified.txt b/src/Verify.EntityFramework.Tests/CoreTests.RecordingInClause.verified.txt new file mode 100644 index 00000000..53a911f5 --- /dev/null +++ b/src/Verify.EntityFramework.Tests/CoreTests.RecordingInClause.verified.txt @@ -0,0 +1,20 @@ +{ + ef: { + Type: ReaderExecutedAsync, + HasTransaction: false, + Parameters: { + @ids1 (Int32): 1, + @ids2 (Int32): 2, + @ids3 (Int32): 4, + @ids4 (Int32): 6 + }, + Text: +select c.Id, + c.Name +from Companies as c +where c.Id in (@ids1, + @ids2, + @ids3, + @ids4) + } +} \ No newline at end of file diff --git a/src/Verify.EntityFramework.Tests/CoreTests.RecordingNotInClause.verified.txt b/src/Verify.EntityFramework.Tests/CoreTests.RecordingNotInClause.verified.txt new file mode 100644 index 00000000..669380f5 --- /dev/null +++ b/src/Verify.EntityFramework.Tests/CoreTests.RecordingNotInClause.verified.txt @@ -0,0 +1,18 @@ +{ + ef: { + Type: ReaderExecutedAsync, + HasTransaction: false, + Parameters: { + @ids1 (Int32): 1, + @ids2 (Int32): 2, + @ids3 (Int32): 4 + }, + Text: +select c.Id, + c.Name +from Companies as c +where c.Id not in (@ids1, + @ids2, + @ids3) + } +} \ No newline at end of file diff --git a/src/Verify.EntityFramework.Tests/CoreTests.cs b/src/Verify.EntityFramework.Tests/CoreTests.cs index 972a25d1..ae8e877f 100644 --- a/src/Verify.EntityFramework.Tests/CoreTests.cs +++ b/src/Verify.EntityFramework.Tests/CoreTests.cs @@ -510,6 +510,40 @@ await data await Verify(); } + [Test] + public async Task RecordingInClause() + { + var database = await DbContextBuilder.GetDatabase(); + var data = database.Context; + + Recording.Start(); + + var ids = new[] { 1, 2, 4, 6 }; + await data + .Companies + .Where(_ => ids.Contains(_.Id)) + .ToListAsync(); + + await Verify(); + } + + [Test] + public async Task RecordingNotInClause() + { + var database = await DbContextBuilder.GetDatabase(); + var data = database.Context; + + Recording.Start(); + + var ids = new[] { 1, 2, 4 }; + await data + .Companies + .Where(_ => !ids.Contains(_.Id)) + .ToListAsync(); + + await Verify(); + } + [Test] public async Task RecordingDisabledTest() { diff --git a/src/Verify.EntityFramework/FormattedScriptGenerator.cs b/src/Verify.EntityFramework/FormattedScriptGenerator.cs index 2924c1cb..8bcbce69 100644 --- a/src/Verify.EntityFramework/FormattedScriptGenerator.cs +++ b/src/Verify.EntityFramework/FormattedScriptGenerator.cs @@ -46,6 +46,39 @@ public string GenerateScript(TSqlFragment fragment) script = string.Concat(script.AsSpan(0, pos), multiLine, script.AsSpan(pos + singleLine.Length)); } + var inCollector = new InPredicateCollector(); + fragment.Accept(inCollector); + + foreach (var inPredicate in inCollector.Predicates) + { + if (inPredicate.Values.Count <= 1) + { + continue; + } + + var values = new List(inPredicate.Values.Count); + foreach (var value in inPredicate.Values) + { + generator.GenerateScript(value, out var text); + values.Add(text); + } + + var singleLine = string.Join(", ", values); + + var pos = script.IndexOf(singleLine, StringComparison.Ordinal); + if (pos == -1) + { + continue; + } + + var lineStart = script.LastIndexOf('\n', pos) + 1; + var indent = new string(' ', pos - lineStart); + + var multiLine = string.Join(",\n" + indent, values); + + script = string.Concat(script.AsSpan(0, pos), multiLine, script.AsSpan(pos + singleLine.Length)); + } + return script; } } diff --git a/src/Verify.EntityFramework/InPredicateCollector.cs b/src/Verify.EntityFramework/InPredicateCollector.cs new file mode 100644 index 00000000..8291a900 --- /dev/null +++ b/src/Verify.EntityFramework/InPredicateCollector.cs @@ -0,0 +1,7 @@ +class InPredicateCollector : TSqlFragmentVisitor +{ + public List Predicates { get; } = []; + + public override void Visit(InPredicate node) => + Predicates.Add(node); +} From f730f0be4a99ce15f3ab08dc5bd604a8523f8875 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Feb 2026 08:22:20 +1100 Subject: [PATCH 2/2] Update Directory.Build.props --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 746be191..2fe545a9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;CS8632;EF1001;NU1608;NU1109 - 15.0.1 + 15.0.2 preview 1.0.0 EntityFramework, Verify