Skip to content

Commit

Permalink
Added refinements to the ef core helpers.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Feb 23, 2024
1 parent 3070cde commit d6da908
Show file tree
Hide file tree
Showing 15 changed files with 273 additions and 35 deletions.
1 change: 0 additions & 1 deletion src/HotChocolate/Data/src/EntityFramework/DbContextKind.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using HotChocolate.Internal;
using HotChocolate.Resolvers.Expressions.Parameters;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using static HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions;
using static HotChocolate.WellKnownMiddleware;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
using System.Collections;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace HotChocolate.Data;

public class EntityFrameworkExecutable<T> : QueryableExecutable<T>
public class EntityFrameworkExecutable<T>(IQueryable<T> queryable) : QueryableExecutable<T>(queryable)
{
public EntityFrameworkExecutable(IQueryable<T> queryable) : base(queryable)
{
}

public override async ValueTask<IList> ToListAsync(CancellationToken cancellationToken) =>
await Source.ToListAsync(cancellationToken).ConfigureAwait(false);
public override async ValueTask<IList> ToListAsync(CancellationToken cancellationToken)
=> await Source.ToListAsync(cancellationToken).ConfigureAwait(false);

public override async ValueTask<object?> FirstOrDefaultAsync(
CancellationToken cancellationToken) =>
await Source.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
CancellationToken cancellationToken)
=> await Source.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);

public override async ValueTask<object?> SingleOrDefaultAsync(
CancellationToken cancellationToken) =>
await Source.SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false);
CancellationToken cancellationToken)
=> await Source.SingleOrDefaultAsync(cancellationToken).ConfigureAwait(false);

public override string Print() => Source.ToQueryString();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace HotChocolate.Data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using HotChocolate.Data;
using HotChocolate.Types.Descriptors.Definitions;
using Microsoft.EntityFrameworkCore;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Execution.Configuration;
using HotChocolate.Internal;
using HotChocolate.Types.Pagination;
using Microsoft.EntityFrameworkCore;

namespace Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -54,5 +56,25 @@ public static IRequestExecutorBuilder AutoRegisterDbContext(
builder.Services.AddSingleton<IParameterExpressionBuilder, InferDbContextParameterExpressionBuilder>();
return builder;
}

/// <summary>
/// Adds resolver compiler mapping for the <see cref="PagingArguments"/> from the EFCore helper lib.
/// </summary>
/// <param name="builder">
/// The <see cref="IRequestExecutorBuilder"/>.
/// </param>
/// <returns>
/// An <see cref="IRequestExecutorBuilder"/> that can be used to configure a schema
/// and its execution.
/// </returns>
public static IRequestExecutorBuilder AddPagingArguments(
this IRequestExecutorBuilder builder)
=> builder.AddParameterExpressionBuilder(
context => MapArguments(
context.GetLocalState<CursorPagingArguments>(
WellKnownContextData.PagingArguments)));

private static PagingArguments MapArguments(CursorPagingArguments arguments)
=> new(arguments.First, arguments.After, arguments.Last, arguments.Before);
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ public static TDbContext DbContext<TDbContext>(this IResolverContext context)

return casted;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Threading.Tasks;
using HotChocolate.Resolvers;

namespace HotChocolate.Types;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,36 @@ public static async Task<Connection<T>> ToConnectionAsync<T>(
where T : class
{
var result = await resultPromise;

return CreateConnection(result);
}

/// <summary>
/// Converts a <see cref="Page{T}"/> to a <see cref="Connection{T}"/>.
/// </summary>
/// <param name="resultPromise">
/// The page result.
/// </param>
/// <typeparam name="T">
/// The type of the items in the page.
/// </typeparam>
/// <returns></returns>
public static async ValueTask<Connection<T>> ToConnectionAsync<T>(
this ValueTask<Page<T>> resultPromise)
where T : class
{
var result = await resultPromise;
return CreateConnection(result);
}

private static Connection<T> CreateConnection<T>(Page<T> page) where T : class
{
return new Connection<T>(
result.Items.Select(t => new Edge<T>(t, result.CreateCursor)).ToArray(),
page.Items.Select(t => new Edge<T>(t, page.CreateCursor)).ToArray(),
new ConnectionPageInfo(
result.HasPreviousPage,
result.HasNextPage,
CreateCursor(result.First, result.CreateCursor),
CreateCursor(result.Last, result.CreateCursor)));
page.HasPreviousPage,
page.HasNextPage,
CreateCursor(page.First, page.CreateCursor),
CreateCursor(page.Last, page.CreateCursor)));
}

private static string? CreateCursor<T>(T? item, Func<T, string> createCursor) where T : class
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using HotChocolate.Internal;
Expand Down
3 changes: 0 additions & 3 deletions src/HotChocolate/Data/src/EntityFramework/ToListMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using HotChocolate.Resolvers;
using Microsoft.EntityFrameworkCore;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using HotChocolate.Types;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using HotChocolate.Data.TestContext;
using CookieCrumble;
using HotChocolate.Execution;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using HotChocolate.Types.Pagination;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Squadron;

namespace HotChocolate.Data;

public class IntegrationPagingHelperTests(PostgreSqlResource resource) : IClassFixture<PostgreSqlResource>
{
public PostgreSqlResource Resource { get; } = resource;

private string CreateConnectionString()
=> Resource.GetConnectionString($"db_{Guid.NewGuid():N}");

[Fact]
public async Task GetDefaultPage()
{
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var result = await new ServiceCollection()
.AddScoped(_ => new CatalogContext(connectionString))
.AddGraphQL()
.AddQueryType<Query>()
.AddPagingArguments()
.ExecuteRequestAsync(
"""
{
brands {
nodes {
id
name
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
""");

// Assert
result.MatchMarkdownSnapshot();
}

[Fact]
public async Task GetSecondPage_With_2_Items()
{
// Arrange
var connectionString = CreateConnectionString();
await SeedAsync(connectionString);

// Act
var result = await new ServiceCollection()
.AddScoped(_ => new CatalogContext(connectionString))
.AddGraphQL()
.AddQueryType<Query>()
.AddPagingArguments()
.ExecuteRequestAsync(
"""
{
brands(first: 2, after: "QnJhbmQxNzoxOA==") {
nodes {
id
name
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
""");

// Assert
result.MatchMarkdownSnapshot();
}

private static async Task SeedAsync(string connectionString)
{
await using var context = new CatalogContext(connectionString);
await context.Database.EnsureCreatedAsync();

var type = new ProductType { Name = "T-Shirt", };
context.ProductTypes.Add(type);

for (var i = 0; i < 100; i++)
{
var brand = new Brand { Name = "Brand" + i, };
context.Brands.Add(brand);

for (var j = 0; j < 100; j++)
{
var product = new Product
{
Name = $"Product {i}-{j}",
Type = type,
Brand = brand,
};
context.Products.Add(product);
}
}

await context.SaveChangesAsync();
}

public class Query
{
[UsePaging]
public async Task<Connection<Brand>> GetBrandsAsync(
CatalogContext context,
PagingArguments arguments,
CancellationToken ct)
=> await context.Brands
.OrderBy(t => t.Name)
.ThenBy(t => t.Id)
.ToPageAsync(arguments, cancellationToken: ct)
.ToConnectionAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# GetDefaultPage

```json
{
"data": {
"brands": {
"nodes": [
{
"id": 1,
"name": "Brand0"
},
{
"id": 2,
"name": "Brand1"
},
{
"id": 11,
"name": "Brand10"
},
{
"id": 12,
"name": "Brand11"
},
{
"id": 13,
"name": "Brand12"
},
{
"id": 14,
"name": "Brand13"
},
{
"id": 15,
"name": "Brand14"
},
{
"id": 16,
"name": "Brand15"
},
{
"id": 17,
"name": "Brand16"
},
{
"id": 18,
"name": "Brand17"
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "QnJhbmQwOjE=",
"endCursor": "QnJhbmQxNzoxOA=="
}
}
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# GetSecondPage_With_2_Items

```json
{
"data": {
"brands": {
"nodes": [
{
"id": 19,
"name": "Brand18"
},
{
"id": 20,
"name": "Brand19"
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "QnJhbmQxODoxOQ==",
"endCursor": "QnJhbmQxOToyMA=="
}
}
}
}
```

0 comments on commit d6da908

Please sign in to comment.