GraphQL.EntityFrameworkCore.Helpers 0.4.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package GraphQL.EntityFrameworkCore.Helpers --version 0.4.0
NuGet\Install-Package GraphQL.EntityFrameworkCore.Helpers -Version 0.4.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GraphQL.EntityFrameworkCore.Helpers" Version="0.4.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add GraphQL.EntityFrameworkCore.Helpers --version 0.4.0
#r "nuget: GraphQL.EntityFrameworkCore.Helpers, 0.4.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install GraphQL.EntityFrameworkCore.Helpers as a Cake Addin
#addin nuget:?package=GraphQL.EntityFrameworkCore.Helpers&version=0.4.0

// Install GraphQL.EntityFrameworkCore.Helpers as a Cake Tool
#tool nuget:?package=GraphQL.EntityFrameworkCore.Helpers&version=0.4.0

GraphQL Helpers for EF Core - Connections, Selection from Request and Filtering

Helpers to add bidirectional cursor based paginaton to your endpoints with the IQueryable extension method ToConnection(IConnectionInput<T> request, IModel model). The method expects a request that implements IConnectionInput<T> as input. Parameters is added to the ConnectionBuilder using the extension method Paged(defaultPageSize).

The SelectFromContext(IResolveFieldContext context, IModel model) extension methods helps you to avoid overfetching data by only selecting the fields requested. Foreign key fields is included by default as the value might be used for data loaded fields.

With the Filter(IResolveFieldContext context, IModel model) extension method you can filter a list of items on specific properties, including any requested data loaded fields. What fields that should be filterable is determined by the IsFilterableAttribute or the FieldBuilder extension method IsFilterable.

Getting Started

dotnet add package GraphQL.EntityFrameworkCore.Helpers

And register GraphTypes to DI:

services
    .AddGraphQLEntityFrameworkCoreHelpers();

Examples

Connection

Bidirectional cursor based pagination with support for ordering, filtering and selecting based on what is requested.

With the parameter orderBy (array of strings, i.e [ "name", "id" ]) you can specify in the client what order you want the items in. The order will be ascending by default, you can change it to descending by setting the parameter isAsc to false.

It is important that each resulting cursor points at a unique row in order to be able to determine what rows to include before or after a specific row. Therefore the primary key(s) of a certain entity is automaticaly included in the orderBy if the asked for order by columns isn't considered unique. Columns considered unique is either the primary key, has a unique constraint or is of type GUID, DATETIME or DATETIMEOFFSET. If you have column that you know are unique but doesn't meet those criterias you can use the Unique attribute.

ToConnection applies both SelectFromContext and Filter by default.

Connection<DroidGraphType>()
    .Name("Droids")
    .Paged()
    .ResolveAsync(async context =>
    {
        var request = new ConnectionInput();
        request.SetConnectionInput(context);

        return await dbContext.Droids
            .ToConnection(request, dbContext.Model); // IModel is required for SelectFromContext from Request
    });
Validating the request

You can validate the request outside of ToConnection if you want to ensure it's valid in your validation pipeline, the first generic type parameter is the DbSet and the second parameter is the resulting type of the request. It needs the IModel to determine that the orderBy is valid, it doesn't need a orderBy if there is primary key it could use instead.

var validationResult = request.Validate<Human, Clone>(dbContext.Model);
Select from Request

SelectFromContext applies Filter by default (Filtered have to be applied to the Field first).

FieldAsync<ListGraphType<HumanGraphType>>(
    "Humans",
    resolve: async context => await dbContext.Humans.SelectFromContext(context, dbContext.Model).ToListAsync());
Filter

Add the IsFilterable-attribute to columns that should be filterable.

public class Human
{
    public Guid Id { get; set; }

    [IsFilterable]
    public string Name { get; set; }
}

Or, mark fields as filterable with the extension method IsFilterable. If the name of the field doesn't match the property name this method needs to be used with a Func targeting the matching property.

Field(x => x.Sector)
    .IsFilterable(x => x.Sector)
    .Name("StarSector");

Add the argument to the FieldBuilder using the extension method Filtered() and filter the list with the IQueryable extension method Filter(Context, IModel).

Field<ListGraphType<HumanGraphType>>()
    .Name("Humans")
    .Filtered()
    .ResolveAsync(async context => await dbContext.Humans
        .Filter(context, dbContext.Model)
        .SelectFromContext(context, dbContext.Model)
        .ToListAsync());
Filter input

The simplest way to filter a list is by filtering all filterable properties to see if they contain a search term (using Like).

{
    "filterInput": {
        "mode": "shallow", // Default (Not required)
        "fields": [
            {
                "target": "all", // Default (Not required)
                "value": "search term",
                "operator": "or", // Default (Not required)
                "valueOperator": "like" // Default (Not required)
            }
        ]
    }
}

The input can apply to all requested data loaded properties as well as the main query or just the main query. This is determined by the specified mode, shallow to only appply to main or deep to apply to all filterable fields.

All fields that is using the or-operator is combined into one where-clause where one of them needs to be true, fields that is using the and-operator is separated into it's own where-clauses.

The search term can be compared to the field value using the operators: like, notlike, equal or notequal.

Below is a more complex query where the main query is filtered to only include humans that come from the planet Tatooine and has a blue eye color, to also apply this to the data loaded property homePlanet the mode needs to be changed to deep.

query humans($filterInput: FilterInput) {
    humans(filter: $filterInput) {
        id
        name
        homePlanet {
            id
            name
        }
    }
}
{
    "filterInput": {
        "mode": "Shallow",
        "fields": [
            {
                "target": "eyeColor",
                "value": "blue",
                "operator": "And"
            },
            {
                "target": "homePlanet",
                "fields": [
                    {
                        "target": "name",
                        "value": "tatooine",
                        "operator": "And"
                    }
                ]
            }
        ]
    }
}
Validating the filter input
var validationResult = filterInput.Validate(IResolveFieldContext);
Working with data loaded fields (navigation properties)

The helper methods can only follow data loaded properties that is loaded through navigation properties (Foreign Keys). If the data loaded field isn't named the same in the schema as the property in the model the FieldBuilder extension method MapsTo(Func) has to be applied.

Field<ListGraphType<HumanGraphType>, IEnumerable<Human>>()
    .Name("Residents")
    .MapsTo(x => x.Habitants)
    .ResolveAsync(context =>
    {
        ...
    });

In some cases, especially with many-to-many relationships, you might want to skip one level in the hierarchy and use a childs child property, that can be done using the method ThenTo(Func), which can be applied after MapsTo(Func). See sample project for example of this.

Known issues

The Schema First approach haven't been tested and will likely have some issues configuring.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.6 856 11/9/2021
1.1.5 294 11/8/2021
1.1.4 479 9/19/2021
1.1.3 767 6/14/2021
1.1.2 370 5/27/2021
1.1.0 424 3/20/2021
1.0.1 333 3/16/2021
1.0.0 347 2/28/2021
0.6.0 354 1/3/2021
0.5.2 449 10/4/2020
0.5.1 494 9/13/2020
0.5.0 449 9/12/2020
0.4.0 430 8/19/2020
0.3.0 472 8/11/2020
0.2.5 504 5/2/2020
0.2.4 509 5/2/2020
0.2.3 554 3/29/2020
0.2.2 492 3/29/2020
0.2.1 489 3/25/2020
0.2.0 466 3/25/2020
0.1.0 482 3/24/2020