8000 Added QueryContext extension methods for Select and Include. (#8013) · ChilliCream/graphql-platform@b2dc87e · GitHub
[go: up one dir, main page]

Skip to content

Commit

Permalink
Added QueryContext extension methods for Select and Include. (#8013)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Feb 12, 2025
1 parent 13c074d commit b2dc87e
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 53 deletions.
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// ReSharper disable once CheckNamespace
using System.Linq.Expressions;
using GreenDonut.Data.Internal;

namespace GreenDonut.Data;

/// <summary>
/// Provides extension methods for the <see cref="QueryContext{TEntity}"/>.
/// </summary>
public static class GreenDonutQueryContextExtensions
{
public static QueryContext<TEntity> Include<TEntity, TValue>(
this QueryContext<TEntity> context,
Expression<Func<TEntity, TValue>>? propertySelector)
{
ArgumentNullException.ThrowIfNull(context);

if(propertySelector is null)
{
return context;
}

var normalizedSelector = ExpressionHelpers.Rewrite(propertySelector);

if(context.Selector is null)
{
return context with
{
Selector = normalizedSelector
};
}
else
{
return context with
{
Selector = ExpressionHelpers.Combine(context.Selector, normalizedSelector)
};
}
}

public static QueryContext<TEntity> Select<TEntity>(
this QueryContext<TEntity> context,
Expression<Func<TEntity, TEntity>>? selector)
{
ArgumentNullException.ThrowIfNull(context);

if(selector is null)
{
return context;
}

if(context.Selector is null)
{
return context with
{
Selector = selector
};
}
else
{
return context with
{
Selector = ExpressionHelpers.Combine(context.Selector, selector)
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ private static Expression CombineExpressions(Expression first, Expression second
return second;
}


private static Expression CombineWithConvertExpression(UnaryExpression first, Expression second)
{
if (second is MemberInitExpression otherMemberInit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;

namespace GreenDonut.Data;

public sealed class CapturePagingQueryInterceptor : PagingQueryInterceptor
{
public List<QueryInfo> Queries { get; } = new();

public override void OnBeforeExecute<T>(IQueryable<T> query)
{
Queries.Add(
new QueryInfo
{
ExpressionText = query.Expression.ToString(),
QueryText = query.ToQueryString()
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public async Task Paging_Empty_PagingArgs()
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -30,7 +29,7 @@ public async Task Paging_Empty_PagingArgs()

// Assert
await CreateSnapshot()
.AddQueries(queries)
.AddQueries(capture.Queries)
.Add(
new
{
Expand All @@ -51,8 +50,7 @@ public async Task Paging_First_5()
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -62,7 +60,7 @@ public async Task Paging_First_5()

// Assert
await CreateSnapshot()
.AddQueries(queries)
.AddQueries(capture.Queries)
.Add(
new
{
Expand All @@ -83,8 +81,7 @@ public async Task Paging_First_5_After_Id_13()
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -98,7 +95,7 @@ public async Task Paging_First_5_After_Id_13()

// Assert
await CreateSnapshot()
.AddQueries(queries)
.AddQueries(capture.Queries)
.Add(
new
{
Expand All @@ -119,8 +116,7 @@ public async Task Paging_Last_5()
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -130,7 +126,7 @@ public async Task Paging_Last_5()

// Assert
await CreateSnapshot()
.AddQueries(queries)
.AddQueries(capture.Queries)
.Add(
new
{
Expand All @@ -151,8 +147,7 @@ public async Task Paging_First_5_Before_Id_96()
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -166,7 +161,7 @@ public async Task Paging_First_5_Before_Id_96()

// Assert
await CreateSnapshot()
.AddQueries(queries)
.AddQueries(capture.Queries)
.Add(
new
{
Expand All @@ -193,8 +188,7 @@ public async Task BatchPaging_First_5()

var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -220,7 +214,7 @@ public async Task BatchPaging_First_5()
name: page.Key.ToString());
}

snapshot.AddQueries(queries);
snapshot.AddQueries(capture.Queries);
snapshot.MatchMarkdownSnapshot();
}

Expand All @@ -236,8 +230,7 @@ public async Task BatchPaging_Last_5()

var connectionString = CreateConnectionString();
await SeedAsync(connectionString);
var queries = new List<QueryInfo>();
using var capture = new CapturePagingQueryInterceptor(queries);
using var capture = new CapturePagingQueryInterceptor();

// Act
await using var context = new CatalogContext(connectionString);
Expand All @@ -262,7 +255,7 @@ public async Task BatchPaging_Last_5()
name: page.Key.ToString());
}

snapshot.AddQueries(queries);
snapshot.AddQueries(capture.Queries);
snapshot.MatchMarkdownSnapshot();
}

Expand Down Expand Up @@ -398,33 +391,3 @@ private static Snapshot CreateSnapshot()
#endif
}
}

file static class Extensions
{
public static Snapshot AddQueries(
this Snapshot snapshot,
List<QueryInfo> queries)
{
for (var i = 0; i < queries.Count; i++)
{
snapshot
.Add(queries[i].QueryText, $"SQL {i}", "sql")
.Add(queries[i].ExpressionText, $"Expression {i}");
}

return snapshot;
}
}

file sealed class CapturePagingQueryInterceptor(List<QueryInfo> queries) : PagingQueryInterceptor
{
public override void OnBeforeExecute<T>(IQueryable<T> query)
{
queries.Add(
new QueryInfo
{
ExpressionText = query.Expression.ToString(),
QueryText = query.ToQueryString()
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,120 @@ public async Task Fetch_Last_2_Items()
page.MatchMarkdownSnapshot();
}

[Fact]
public async Task QueryContext_Simple_Selector()
{
// Arrange
using var interceptor = new CapturePagingQueryInterceptor();
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var query = new QueryContext<Product>(
Selector: t => new Product { Id = t.Id, Name = t.Name },
Sorting: new SortDefinition<Product>().AddDescending(t => t.Id));

var arguments = new PagingArguments(last: 2);

await using var context = new CatalogContext(connectionString);

var page = await context.Products
.With(query)
.ToPageAsync(arguments);

// Assert
CreateSnapshot()
.AddQueries(interceptor.Queries)
.MatchMarkdown();
}

[Fact]
public async Task QueryContext_Simple_Selector_Include_Brand()
{
// Arrange
using var interceptor = new CapturePagingQueryInterceptor();
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var query = new QueryContext<Product>(
Selector: t => new Product { Id = t.Id, Name = t.Name },
Sorting: new SortDefinition<Product>().AddDescending(t => t.Id));

query = query.Include(t => t.Brand);

var arguments = new PagingArguments(last: 2);

await using var context = new CatalogContext(connectionString);

var page = await context.Products
.With(query)
.ToPageAsync(arguments);

// Assert
CreateSnapshot()
.AddQueries(interceptor.Queries)
.MatchMarkdown();
}

[Fact]
public async Task QueryContext_Simple_Selector_Include_Brand_Name()
{
// Arrange
using var interceptor = new CapturePagingQueryInterceptor();
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var query = new QueryContext<Product>(
Selector: t => new Product { Id = t.Id, Name = t.Name },
Sorting: new SortDefinition<Product>().AddDescending(t => t.Id));

query = query.Select(t => new Product { Brand = new Brand { Name = t.Brand!.Name } });

var arguments = new PagingArguments(last: 2);

await using var context = new CatalogContext(connectionString);

var page = await context.Products
.With(query)
.ToPageAsync(arguments);

// Assert
CreateSnapshot()
.AddQueries(interceptor.Queries)
.MatchMarkdown();
}

[Fact]
public async Task QueryContext_Simple_Selector_Include_Product_List()
{
// Arrange
using var interceptor = new CapturePagingQueryInterceptor();
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var query = new QueryContext<Brand>(
Selector: t => new Brand { Id = t.Id, Name = t.Name },
Sorting: new SortDefinition<Brand>().AddDescending(t => t.Id));

query = query.Select(t => new Brand { Products = t.Products.Select(p => new Product { Id = p.Id, Name = p.Name }).ToList() });

var arguments = new PagingArguments(last: 2);

await using var context = new CatalogContext(connectionString);

var page = await context.Brands
.With(query)
.ToPageAsync(arguments);

// Assert
CreateSnapshot()
.AddQueries(interceptor.Queries)
.MatchMarkdown();
}

[Fact]
public async Task Fetch_Last_2_Items_Before_Last_Page()
{
Expand Down Expand Up @@ -306,4 +420,13 @@ private static async Task SeedTestAsync(string connectionString)

await context.SaveChangesAsync();
}

private static Snapshot CreateSnapshot()
{
#if NET9_0_OR_GREATER
return Snapshot.Create();
#else
return Snapshot.Create("NET8_0");
#endif
}
}
Loading

0 comments on commit b2dc87e

Please sign in to comment.
0