From ccf0247c508c0a2de912e701bac6f7efba08605b Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Wed, 31 Jul 2024 00:41:19 -0700 Subject: [PATCH] Fix to #34056 - AOT/Query: for queries with JSON, interceptors generate code with labels that are not uniquified (#34323) Problem was that when we generate shaper for extracting json values from the reader (streaming) we use hard-coded name for a label that's used in the loop going through json properties. This is fine for normal scenarios, since compiler can figure out the right label to jump to based on scope/reference, but when we generate c# code from that expression, we run into issues for case of nested JSON. Existing code already works if the label is not named explicitly - LinqToCSharpSyntaxTranslator generates names for unnamed labels and uniquifies the names when needed. Fix is to add the uniquification to all labels, not only the unnamed one. Also fixed another small bug in LinqToCSharpSyntaxTranslator encountered in the testing - when we process LambdaExpression and are dealing with lifted statements (that need to be incorporated into the lambda body), we assumed it would always be a single expression, but it could also be a block. Fix is to add handling for the block case, which is simply pre-pending lifted statements to the ones already in the block. Added JSON entites to Blog so that we can test the scenario, also added some posts (there were none) for cases which don't work with JSON entities, e.g. set ops. Fixes #34056 --- .../Internal/LinqToCSharpSyntaxTranslator.cs | 39 ++- .../PrecompiledQueryRelationalFixture.cs | 26 +- .../PrecompiledQueryRelationalTestBase.cs | 113 ++++++- .../Query/PrecompiledQuerySqlServerTest.cs | 306 +++++++++++------- 4 files changed, 351 insertions(+), 133 deletions(-) diff --git a/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs b/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs index 470a72cccb5..7e3537c1559 100644 --- a/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs +++ b/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs @@ -28,7 +28,7 @@ private sealed record StackFrame( Dictionary Variables, HashSet VariableNames, Dictionary Labels, - HashSet UnnamedLabelNames); + HashSet UniqueLabelNames); private readonly Stack _stack = new([new StackFrame([], [], [], [])]); @@ -160,7 +160,7 @@ protected virtual SyntaxNode TranslateCore( Check.DebugAssert(_stack.Peek().Variables.Count == 0, "_stack.Peek().Parameters.Count == 0"); Check.DebugAssert(_stack.Peek().VariableNames.Count == 0, "_stack.Peek().ParameterNames.Count == 0"); Check.DebugAssert(_stack.Peek().Labels.Count == 0, "_stack.Peek().Labels.Count == 0"); - Check.DebugAssert(_stack.Peek().UnnamedLabelNames.Count == 0, "_stack.Peek().UnnamedLabelNames.Count == 0"); + Check.DebugAssert(_stack.Peek().UniqueLabelNames.Count == 0, "_stack.Peek().UniqueLabelNames.Count == 0"); foreach (var unsafeAccessor in _fieldUnsafeAccessors.Values.Concat(_methodUnsafeAccessors.Values)) { @@ -714,6 +714,8 @@ static bool IsExpressionValidAsStatement(ExpressionSyntax expression) void PreprocessLabels() { // LINQ label targets can be unnamed, so we need to generate names for unnamed ones and maintain a target->name mapping. + // Also labels can have duplicated names - we need to de-duplicate them before we can generate a valid c# code + // just like we do with variables/parameters // We need to maintain this as a stack for every block which has labels. // Normal blocks get their own labels stack frame, which gets popped when we leave the block. Expression labels add their // labels to their parent's stack frame (since they get lifted). @@ -726,21 +728,17 @@ void PreprocessLabels() continue; } - var (_, _, labels, unnamedLabelNames) = stackFrame; + var (_, _, labels, uniqueLabelNames) = stackFrame; - // Generate names for unnamed label targets and uniquify + // Generate names for unnamed label targets and uniquify (all label names) identifier = label.Target.Name ?? "unnamedLabel"; var identifierBase = identifier; - for (var i = 0; unnamedLabelNames.Contains(identifier); i++) + for (var i = 0; uniqueLabelNames.Contains(identifier); i++) { identifier = identifierBase + i; } - if (label.Target.Name is null) - { - unnamedLabelNames.Add(identifier); - } - + uniqueLabelNames.Add(identifier); labels.Add(label.Target, identifier); } } @@ -1507,15 +1505,24 @@ protected override Expression VisitLambda(Expression lambda) var expressionBody = body as ExpressionSyntax; var blockBody = body as BlockSyntax; - // If the lambda body was an expression that had lifted statements (e.g. some block in expression context), we need to create - // a block to contain these statements if (_liftedState.Statements.Count > 0) { Check.DebugAssert(lambda.ReturnType != typeof(void), "lambda.ReturnType != typeof(void)"); - Check.DebugAssert(expressionBody != null, "expressionBody != null"); - blockBody = Block(_liftedState.Statements.Append(ReturnStatement(expressionBody))); - expressionBody = null; + if (expressionBody != null) + { + // If the lambda body was an expression that had lifted statements (e.g. some block in expression context), we need to create + // a block to contain these statements + blockBody = Block(_liftedState.Statements.Append(ReturnStatement(expressionBody))); + expressionBody = null; + } + else + { + // If the lambda body was already a block, we just prepend lifted statements to the ones already existing in the block + Check.DebugAssert(blockBody != null, "expressionBody != null || blockBody != null"); + blockBody = Block(_liftedState.Statements.Concat(blockBody.Statements)); + } + _liftedState.Statements.Clear(); } @@ -2734,7 +2741,7 @@ private StackFrame PushNewStackFrame() new Dictionary(previousFrame.Variables), [..previousFrame.VariableNames], new Dictionary(previousFrame.Labels), - [..previousFrame.UnnamedLabelNames]); + [..previousFrame.UniqueLabelNames]); _stack.Push(newFrame); diff --git a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalFixture.cs index fd4d1f19e9d..74c4c745a9f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalFixture.cs @@ -4,6 +4,9 @@ using Microsoft.EntityFrameworkCore.Query.Internal; using static Microsoft.EntityFrameworkCore.TestUtilities.PrecompiledQueryTestHelpers; using Blog = Microsoft.EntityFrameworkCore.Query.PrecompiledQueryRelationalTestBase.Blog; +using Post = Microsoft.EntityFrameworkCore.Query.PrecompiledQueryRelationalTestBase.Post; +using JsonRoot = Microsoft.EntityFrameworkCore.Query.PrecompiledQueryRelationalTestBase.JsonRoot; +using JsonBranch = Microsoft.EntityFrameworkCore.Query.PrecompiledQueryRelationalTestBase.JsonBranch; namespace Microsoft.EntityFrameworkCore.Query; public abstract class PrecompiledQueryRelationalFixture @@ -27,9 +30,26 @@ protected override IServiceCollection AddServices(IServiceCollection serviceColl protected override async Task SeedAsync(PrecompiledQueryRelationalTestBase.PrecompiledQueryContext context) { - context.Blogs.AddRange( - new Blog { Id = 8, Name = "Blog1" }, - new Blog { Id = 9, Name = "Blog2" }); + var blog1 = new Blog { Id = 8, Name = "Blog1", Json = [] }; + var blog2 = new Blog + { + Id = 9, + Name = "Blog2", + Json = + [ + new JsonRoot { Number = 1, Text = "One", Inner = new JsonBranch { Date = new DateTime(2001, 1, 1) } }, + new JsonRoot { Number = 2, Text = "Two", Inner = new JsonBranch { Date = new DateTime(2002, 2, 2) } }, + ]}; + + context.Blogs.AddRange(blog1, blog2); + + var post11 = new Post { Id = 11, Title = "Post11", Blog = blog1 }; + var post12 = new Post { Id = 12, Title = "Post12", Blog = blog1 }; + var post21 = new Post { Id = 21, Title = "Post21", Blog = blog2 }; + var post22 = new Post { Id = 22, Title = "Post22", Blog = blog2 }; + var post23 = new Post { Id = 23, Title = "Post23", Blog = blog2 }; + + context.Posts.AddRange(post11, post12, post21, post22, post23); await context.SaveChangesAsync(); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs index d304fb03ee4..6a7642767f6 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs @@ -35,6 +35,27 @@ public virtual Task BinaryExpression() => Test(""" var id = 3; var blogs = await context.Blogs.Where(b => b.Id > id).ToListAsync(); + +Assert.Equal(2, blogs.Count); +var orderedBlogs = blogs.OrderBy(x => x.Id).ToList(); +var blog1 = orderedBlogs[0]; +var blog2 = orderedBlogs[1]; + +Assert.Equal(8, blog1.Id); +Assert.Equal("Blog1", blog1.Name); +Assert.Empty(blog1.Json); + +Assert.Equal(9, blog2.Id); +Assert.Equal("Blog2", blog2.Name); +Assert.Equal(2, blog2.Json.Count); + +Assert.Equal(1, blog2.Json[0].Number); +Assert.Equal("One", blog2.Json[0].Text); +Assert.Equal(new DateTime(2001, 1, 1), blog2.Json[0].Inner.Date); + +Assert.Equal(2, blog2.Json[1].Number); +Assert.Equal("Two", blog2.Json[1].Text); +Assert.Equal(new DateTime(2002, 2, 2), blog2.Json[1].Inner.Date); """); [ConditionalFact] @@ -729,6 +750,23 @@ public virtual Task Terminating_ExecuteUpdateAsync() public virtual Task Union() => Test( """ +var posts = await context.Posts.Where(p => p.Id > 11) + .Union(context.Posts.Where(p => p.Id < 21)) + .OrderBy(p => p.Id) + .ToListAsync(); + +Assert.Collection(posts, + b => Assert.Equal(11, b.Id), + b => Assert.Equal(12, b.Id), + b => Assert.Equal(21, b.Id), + b => Assert.Equal(22, b.Id), + b => Assert.Equal(23, b.Id)); +"""); + + [ConditionalFact(Skip = "issue 33378")] + public virtual Task UnionOnEntitiesWithJson() + => Test( + """ var blogs = await context.Blogs.Where(b => b.Id > 7) .Union(context.Blogs.Where(b => b.Id < 10)) .OrderBy(b => b.Id) @@ -743,6 +781,24 @@ public virtual Task Union() public virtual Task Concat() => Test( """ +var posts = await context.Posts.Where(p => p.Id > 11) + .Concat(context.Posts.Where(p => p.Id < 21)) + .OrderBy(p => p.Id) + .ToListAsync(); + +Assert.Collection(posts, + b => Assert.Equal(11, b.Id), + b => Assert.Equal(12, b.Id), + b => Assert.Equal(12, b.Id), + b => Assert.Equal(21, b.Id), + b => Assert.Equal(22, b.Id), + b => Assert.Equal(23, b.Id)); +"""); + + [ConditionalFact(Skip = "issue 33378")] + public virtual Task ConcatOnEntitiesWithJson() + => Test( + """ var blogs = await context.Blogs.Where(b => b.Id > 7) .Concat(context.Blogs.Where(b => b.Id < 10)) .OrderBy(b => b.Id) @@ -759,6 +815,20 @@ public virtual Task Concat() public virtual Task Intersect() => Test( """ +var posts = await context.Posts.Where(b => b.Id > 11) + .Intersect(context.Posts.Where(b => b.Id < 22)) + .OrderBy(b => b.Id) + .ToListAsync(); + +Assert.Collection(posts, + b => Assert.Equal(12, b.Id), + b => Assert.Equal(21, b.Id)); +"""); + + [ConditionalFact(Skip = "issue 33378")] + public virtual Task IntersectOnEntitiesWithJson() + => Test( + """ var blogs = await context.Blogs.Where(b => b.Id > 7) .Intersect(context.Blogs.Where(b => b.Id > 8)) .OrderBy(b => b.Id) @@ -771,6 +841,20 @@ public virtual Task Intersect() public virtual Task Except() => Test( """ +var posts = await context.Posts.Where(b => b.Id > 11) + .Except(context.Posts.Where(b => b.Id > 21)) + .OrderBy(b => b.Id) + .ToListAsync(); + +Assert.Collection(posts, + b => Assert.Equal(12, b.Id), + b => Assert.Equal(21, b.Id)); +"""); + + [ConditionalFact(Skip = "issue 33378")] + public virtual Task ExceptOnEntitiesWithJson() + => Test( + """ var blogs = await context.Blogs.Where(b => b.Id > 7) .Except(context.Blogs.Where(b => b.Id > 8)) .OrderBy(b => b.Id) @@ -1066,6 +1150,20 @@ public class PrecompiledQueryContext(DbContextOptions options) : DbContext(optio { public DbSet Blogs { get; set; } = null!; public DbSet Posts { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().OwnsMany( + x => x.Json, + n => + { + n.ToJson(); + n.OwnsOne(xx => xx.Inner); + }); + modelBuilder.Entity().HasMany(x => x.Posts).WithOne(x => x.Blog).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + } } protected PrecompiledQueryRelationalFixture Fixture { get; } @@ -1128,8 +1226,21 @@ public Blog(int id, string name) [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public string? Name { get; set; } - public List Posts { get; set; } = new(); + public List Json { get; set; } = new(); + } + + public class JsonRoot + { + public int Number { get; set; } + public string? Text { get; set; } + + public JsonBranch Inner { get; set; } = null!; + } + + public class JsonBranch + { + public DateTime Date { get; set; } } public class Post diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs index c56b17b6239..760bf5794f4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs @@ -24,7 +24,7 @@ public override async Task BinaryExpression() """ @__id_0='3' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > @__id_0 """); @@ -106,7 +106,7 @@ public override async Task ListInit_fully_evaluatable() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] IN (7, 8) """); @@ -118,7 +118,7 @@ public override async Task MethodCallExpression_no_evaluatability() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] IS NOT NULL AND LEFT([b].[Name], LEN([b].[Name])) = [b].[Name] """); @@ -132,7 +132,7 @@ public override async Task MethodCallExpression_with_evaluatable_with_captured_v """ @__pattern_0_startswith='foo%' (Size = 4000) -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] LIKE @__pattern_0_startswith ESCAPE N'\' """); @@ -144,7 +144,7 @@ public override async Task MethodCallExpression_with_evaluatable_without_capture AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] LIKE N'foo%' """); @@ -156,7 +156,7 @@ public override async Task MethodCallExpression_fully_evaluatable() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -167,7 +167,7 @@ public override async Task New_with_no_arguments() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 0 """); @@ -256,7 +256,7 @@ public override async Task Unary() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE CAST([b].[Id] AS smallint) = CAST(8 AS smallint) """); @@ -279,7 +279,7 @@ public override async Task Terminating_AsEnumerable() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -290,7 +290,7 @@ public override async Task Terminating_AsAsyncEnumerable_on_DbSet() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -301,7 +301,7 @@ public override async Task Terminating_AsAsyncEnumerable_on_IQueryable() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 8 """); @@ -313,7 +313,7 @@ public override async Task Foreach_sync_over_operator() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 8 """); @@ -325,7 +325,7 @@ public override async Task Terminating_ToArray() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -336,7 +336,7 @@ public override async Task Terminating_ToArrayAsync() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -347,7 +347,7 @@ public override async Task Terminating_ToDictionary() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -358,7 +358,7 @@ public override async Task Terminating_ToDictionaryAsync() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -391,7 +391,7 @@ public override async Task Terminating_ToHashSet() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -402,7 +402,7 @@ public override async Task Terminating_ToHashSetAsync() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -413,7 +413,7 @@ public override async Task Terminating_ToLookup() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -424,7 +424,7 @@ public override async Task Terminating_ToList() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -435,7 +435,7 @@ public override async Task Terminating_ToListAsync() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -736,7 +736,7 @@ public override async Task Terminating_ElementAt() """ @__p_0='1' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -745,7 +745,7 @@ OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY """ @__p_0='3' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -760,7 +760,7 @@ public override async Task Terminating_ElementAtAsync() """ @__p_0='1' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -769,7 +769,7 @@ OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY """ @__p_0='3' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -784,7 +784,7 @@ public override async Task Terminating_ElementAtOrDefault() """ @__p_0='1' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -793,7 +793,7 @@ OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY """ @__p_0='3' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -808,7 +808,7 @@ public override async Task Terminating_ElementAtOrDefaultAsync() """ @__p_0='1' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -817,7 +817,7 @@ OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY """ @__p_0='3' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] OFFSET @__p_0 ROWS FETCH NEXT 1 ROWS ONLY @@ -830,25 +830,25 @@ public override async Task Terminating_First() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -860,25 +860,25 @@ public override async Task Terminating_FirstAsync() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -890,25 +890,25 @@ public override async Task Terminating_FirstOrDefault() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -920,25 +920,25 @@ public override async Task Terminating_FirstOrDefaultAsync() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -950,7 +950,7 @@ public override async Task Terminating_GetEnumerator() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """); @@ -962,27 +962,27 @@ public override async Task Terminating_Last() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC @@ -995,27 +995,27 @@ public override async Task Terminating_LastAsync() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC @@ -1028,27 +1028,27 @@ public override async Task Terminating_LastOrDefault() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC @@ -1061,27 +1061,27 @@ public override async Task Terminating_LastOrDefaultAsync() AssertSql( """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 ORDER BY [b].[Id] DESC """, // """ -SELECT TOP(1) [b].[Id], [b].[Name] +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 ORDER BY [b].[Id] DESC @@ -1192,25 +1192,25 @@ public override async Task Terminating_Single() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -1222,25 +1222,25 @@ public override async Task Terminating_SingleAsync() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -1252,25 +1252,25 @@ public override async Task Terminating_SingleOrDefault() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -1282,25 +1282,25 @@ public override async Task Terminating_SingleOrDefaultAsync() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 8 """, // """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = 7 """); @@ -1424,13 +1424,33 @@ public override async Task Union() AssertSql( """ -SELECT [u].[Id], [u].[Name] +SELECT [u].[Id], [u].[BlogId], [u].[Title] +FROM ( + SELECT [p].[Id], [p].[BlogId], [p].[Title] + FROM [Posts] AS [p] + WHERE [p].[Id] > 11 + UNION + SELECT [p0].[Id], [p0].[BlogId], [p0].[Title] + FROM [Posts] AS [p0] + WHERE [p0].[Id] < 21 +) AS [u] +ORDER BY [u].[Id] +"""); + } + + public override async Task UnionOnEntitiesWithJson() + { + await base.UnionOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [u].[Id], [u].[Name], [u].[Json] FROM ( - SELECT [b].[Id], [b].[Name] + SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 7 UNION - SELECT [b0].[Id], [b0].[Name] + SELECT [b0].[Id], [b0].[Name], [b0].[Json] FROM [Blogs] AS [b0] WHERE [b0].[Id] < 10 ) AS [u] @@ -1444,13 +1464,33 @@ public override async Task Concat() AssertSql( """ -SELECT [u].[Id], [u].[Name] +SELECT [u].[Id], [u].[BlogId], [u].[Title] +FROM ( + SELECT [p].[Id], [p].[BlogId], [p].[Title] + FROM [Posts] AS [p] + WHERE [p].[Id] > 11 + UNION ALL + SELECT [p0].[Id], [p0].[BlogId], [p0].[Title] + FROM [Posts] AS [p0] + WHERE [p0].[Id] < 21 +) AS [u] +ORDER BY [u].[Id] +"""); + } + + public override async Task ConcatOnEntitiesWithJson() + { + await base.ConcatOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [u].[Id], [u].[Name], [u].[Json] FROM ( - SELECT [b].[Id], [b].[Name] + SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 7 UNION ALL - SELECT [b0].[Id], [b0].[Name] + SELECT [b0].[Id], [b0].[Name], [b0].[Json] FROM [Blogs] AS [b0] WHERE [b0].[Id] < 10 ) AS [u] @@ -1464,13 +1504,33 @@ public override async Task Intersect() AssertSql( """ -SELECT [i].[Id], [i].[Name] +SELECT [i].[Id], [i].[BlogId], [i].[Title] +FROM ( + SELECT [p].[Id], [p].[BlogId], [p].[Title] + FROM [Posts] AS [p] + WHERE [p].[Id] > 11 + INTERSECT + SELECT [p0].[Id], [p0].[BlogId], [p0].[Title] + FROM [Posts] AS [p0] + WHERE [p0].[Id] < 22 +) AS [i] +ORDER BY [i].[Id] +"""); + } + + public override async Task IntersectOnEntitiesWithJson() + { + await base.IntersectOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [i].[Id], [i].[Name], [i].[Json] FROM ( - SELECT [b].[Id], [b].[Name] + SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 7 INTERSECT - SELECT [b0].[Id], [b0].[Name] + SELECT [b0].[Id], [b0].[Name], [b0].[Json] FROM [Blogs] AS [b0] WHERE [b0].[Id] > 8 ) AS [i] @@ -1484,13 +1544,33 @@ public override async Task Except() AssertSql( """ -SELECT [e].[Id], [e].[Name] +SELECT [e].[Id], [e].[BlogId], [e].[Title] +FROM ( + SELECT [p].[Id], [p].[BlogId], [p].[Title] + FROM [Posts] AS [p] + WHERE [p].[Id] > 11 + EXCEPT + SELECT [p0].[Id], [p0].[BlogId], [p0].[Title] + FROM [Posts] AS [p0] + WHERE [p0].[Id] > 21 +) AS [e] +ORDER BY [e].[Id] +"""); + } + + public override async Task ExceptOnEntitiesWithJson() + { + await base.ExceptOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] FROM ( - SELECT [b].[Id], [b].[Name] + SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] > 7 EXCEPT - SELECT [b0].[Id], [b0].[Name] + SELECT [b0].[Id], [b0].[Name], [b0].[Json] FROM [Blogs] AS [b0] WHERE [b0].[Id] > 8 ) AS [e] @@ -1504,7 +1584,7 @@ public override async Task ValuesExpression() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE ( SELECT COUNT(*) @@ -1521,7 +1601,7 @@ public override async Task Contains_with_parameterized_collection() """ @__ids_0='[1,2,3]' (Size = 4000) -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] IN ( SELECT [i].[value] @@ -1536,7 +1616,7 @@ public override async Task FromSqlRaw() AssertSql( """ -SELECT [m].[Id], [m].[Name] +SELECT [m].[Id], [m].[Name], [m].[Json] FROM ( SELECT * FROM "Blogs" WHERE "Id" > 8 ) AS [m] @@ -1553,7 +1633,7 @@ public override async Task FromSql_with_FormattableString_parameters() p0='8' p1='9' -SELECT [m].[Id], [m].[Name] +SELECT [m].[Id], [m].[Name], [m].[Json] FROM ( SELECT * FROM "Blogs" WHERE "Id" > @p0 AND "Id" < @p1 ) AS [m] @@ -1677,7 +1757,7 @@ public override async Task DbContext_as_local_variable() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -1688,7 +1768,7 @@ public override async Task DbContext_as_field() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -1699,7 +1779,7 @@ public override async Task DbContext_as_property() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -1710,7 +1790,7 @@ public override async Task DbContext_as_captured_variable() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -1721,7 +1801,7 @@ public override async Task DbContext_as_method_invocation_result() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); } @@ -1764,7 +1844,7 @@ public override async Task NotParameterizedAttribute_with_constant() AssertSql( """ -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] = N'Blog2' """); @@ -1803,7 +1883,7 @@ public override async Task OrderBy() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Name] """); @@ -1817,7 +1897,7 @@ public override async Task Skip() """ @__p_0='1' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Name] OFFSET @__p_0 ROWS @@ -1832,7 +1912,7 @@ public override async Task Take() """ @__p_0='1' -SELECT TOP(@__p_0) [b].[Id], [b].[Name] +SELECT TOP(@__p_0) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Name] """); @@ -1875,7 +1955,7 @@ public override async Task Two_captured_variables_in_different_lambdas() @__starts_0_startswith='blog%' (Size = 4000) @__ends_1_endswith='%2' (Size = 4000) -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] LIKE @__starts_0_startswith ESCAPE N'\' AND [b].[Name] LIKE @__ends_1_endswith ESCAPE N'\' """); @@ -1890,7 +1970,7 @@ public override async Task Same_captured_variable_twice_in_same_lambda() @__foo_0_startswith='X%' (Size = 4000) @__foo_0_endswith='%X' (Size = 4000) -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] LIKE @__foo_0_startswith ESCAPE N'\' AND [b].[Name] LIKE @__foo_0_endswith ESCAPE N'\' """); @@ -1905,7 +1985,7 @@ public override async Task Same_captured_variable_twice_in_different_lambdas() @__foo_0_startswith='X%' (Size = 4000) @__foo_0_endswith='%X' (Size = 4000) -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Name] LIKE @__foo_0_startswith ESCAPE N'\' AND [b].[Name] LIKE @__foo_0_endswith ESCAPE N'\' """); @@ -1917,7 +1997,7 @@ public override async Task Include_single() AssertSql( """ -SELECT [b].[Id], [b].[Name], [p].[Id], [p].[BlogId], [p].[Title] +SELECT [b].[Id], [b].[Name], [b].[Json], [p].[Id], [p].[BlogId], [p].[Title] FROM [Blogs] AS [b] LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId] WHERE [b].[Id] > 8 @@ -1931,7 +2011,7 @@ public override async Task Include_split() AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Id] """, @@ -1950,7 +2030,7 @@ public override async Task Final_GroupBy() AssertSql( """ -SELECT [b].[Name], [b].[Id] +SELECT [b].[Name], [b].[Id], [b].[Json] FROM [Blogs] AS [b] ORDER BY [b].[Name] """); @@ -1965,7 +2045,7 @@ public override async Task Multiple_queries_with_captured_variables() @__id1_0='8' @__id2_1='9' -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = @__id1_0 OR [b].[Id] = @__id2_1 """, @@ -1973,7 +2053,7 @@ FROM [Blogs] AS [b] """ @__id1_0='8' -SELECT TOP(2) [b].[Id], [b].[Name] +SELECT TOP(2) [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] WHERE [b].[Id] = @__id1_0 """); @@ -1985,12 +2065,12 @@ public override async Task Unsafe_accessor_gets_generated_once_for_multiple_quer AssertSql( """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """, // """ -SELECT [b].[Id], [b].[Name] +SELECT [b].[Id], [b].[Name], [b].[Json] FROM [Blogs] AS [b] """); }