Skip to content

Commit 7528098

Browse files
authored
Fixed ToPageAsync extension when using a child projection expression (#8235)
1 parent 8c9407e commit 7528098

File tree

4 files changed

+4227
-3
lines changed

4 files changed

+4227
-3
lines changed

src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExtractSelectExpressionVisitor.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
1212
{
1313
if (node.Method.Name == _selectMethod && node.Arguments.Count == 2)
1414
{
15-
var lambda = StripQuotes(node.Arguments[1]);
15+
var lambda = ConvertToLambda(node.Arguments[1]);
1616
if (lambda.Type.IsGenericType
1717
&& lambda.Type.GetGenericTypeDefinition() == typeof(Func<,>))
1818
{
@@ -29,13 +29,20 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
2929
return base.VisitMethodCall(node);
3030
}
3131

32-
private static LambdaExpression StripQuotes(Expression e)
32+
private static LambdaExpression ConvertToLambda(Expression e)
3333
{
3434
while (e.NodeType == ExpressionType.Quote)
3535
{
3636
e = ((UnaryExpression)e).Operand;
3737
}
3838

39-
return (LambdaExpression)e;
39+
if (e.NodeType != ExpressionType.MemberAccess)
40+
{
41+
return (LambdaExpression)e;
42+
}
43+
44+
// Convert the property expression into a lambda expression
45+
var typeArguments = e.Type.GetGenericArguments()[0].GetGenericArguments();
46+
return Expression.Lambda(e, Expression.Parameter(typeArguments[0]));
4047
}
4148
}

src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperIntegrationTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq.Expressions;
12
using GreenDonut.Data.TestContext;
23
using Microsoft.EntityFrameworkCore;
34
using Microsoft.Extensions.DependencyInjection;
@@ -178,6 +179,45 @@ await CreateSnapshot()
178179
.MatchMarkdownAsync();
179180
}
180181

182+
[Fact]
183+
public async Task Paging_WithChildCollectionProjectionExpression_First_5()
184+
{
185+
// Arrange
186+
var connectionString = CreateConnectionString();
187+
await SeedAsync(connectionString);
188+
using var capture = new CapturePagingQueryInterceptor();
189+
190+
// Act
191+
await using var context = new CatalogContext(connectionString);
192+
193+
var pagingArgs = new PagingArguments
194+
{
195+
First = 5
196+
};
197+
198+
var result = await context.Brands
199+
.Select(BrandWithProductsDto.Projection)
200+
.OrderBy(t => t.Name)
201+
.ThenBy(t => t.Id)
202+
.ToPageAsync(pagingArgs);
203+
204+
// Assert
205+
await CreateSnapshot()
206+
.AddQueries(capture.Queries)
207+
.Add(
208+
new
209+
{
210+
result.HasNextPage,
211+
result.HasPreviousPage,
212+
First = result.First?.Id,
213+
FirstCursor = result.First is not null ? result.CreateCursor(result.First) : null,
214+
Last = result.Last?.Id,
215+
LastCursor = result.Last is not null ? result.CreateCursor(result.Last) : null
216+
})
217+
.Add(result.Items)
218+
.MatchMarkdownAsync();
219+
}
220+
181221
[Fact]
182222
public async Task BatchPaging_First_5()
183223
{
@@ -351,6 +391,37 @@ public BrandDto(int id, string name)
351391
public string Name { get; }
352392
}
353393

394+
public class BrandWithProductsDto
395+
{
396+
public required int Id { get; init; }
397+
398+
public required string Name { get; init; }
399+
400+
public required IReadOnlyCollection<ProductDto> Products { get; init; }
401+
402+
public static Expression<Func<Brand, BrandWithProductsDto>> Projection
403+
=> brand => new BrandWithProductsDto
404+
{
405+
Id = brand.Id,
406+
Name = brand.Name,
407+
Products = brand.Products.AsQueryable().Select(ProductDto.Projection).ToList()
408+
};
409+
}
410+
411+
public class ProductDto
412+
{
413+
public required int Id { get; init; }
414+
415+
public required string Name { get; init; }
416+
417+
public static Expression<Func<Product, ProductDto>> Projection
418+
=> product => new ProductDto
419+
{
420+
Id = product.Id,
421+
Name = product.Name
422+
};
423+
}
424+
354425
public class ProductsByBrandDataLoader : StatefulBatchDataLoader<int, Page<Product>>
355426
{
356427
private readonly IServiceProvider _services;

0 commit comments

Comments
 (0)