NHibernate.GraphQL
0.0.1
See the version list below for details.
dotnet add package NHibernate.GraphQL --version 0.0.1
NuGet\Install-Package NHibernate.GraphQL -Version 0.0.1
<PackageReference Include="NHibernate.GraphQL" Version="0.0.1" />
paket add NHibernate.GraphQL --version 0.0.1
#r "nuget: NHibernate.GraphQL, 0.0.1"
// Install NHibernate.GraphQL as a Cake Addin #addin nuget:?package=NHibernate.GraphQL&version=0.0.1 // Install NHibernate.GraphQL as a Cake Tool #tool nuget:?package=NHibernate.GraphQL&version=0.0.1
Tools for GraphQL API implementation based on NHibernate
Main methods
Select data optimization
GraphQL allows optimize amount of data sending between server and client sides, but it would be also great to optimize traffic between application and database.
Method OptimizeQuery
keeps only explicitly requested fields in LINQ query:
var query = session.Query<User>()
.Select(user => new ExposedUser
{
Login = user.Login,
Name = user.FirstName + user.LastName,
Email = user.Email,
FirstName = user.FirstName
});
query = query.OptimizeQuery(new []
{
nameof(ExposedUser.Login),
nameof(ExposedUser.Name)
});
// Now the query retrieves only Login and Name, but Email and FirstName are skip.
var result = query.ToList();
Be aware that the technic decreases traffic between database and application, but increase sql query variety. So it may impact to database query plan cash size.
Creating Connections
GraphQL suggests to use Relay style pagination based on cursors technic.
ToConnection
and ToConnectionAsync
methods allow create Connection objects easily based on your LINQ query.
var query = session.Query<User>()
.Where(user => Email.Contains("@gmail.com"));
Connection<ExposedUser> connection = query.ToConnection(
orderBy: user => user.Id, // Order by field
select: user => new ExposedUser // Select statement
{
Login = user.Login,
Email = user.Email,
FirstName = user.FirstName,
Name = user.FirstName + user.LastName
},
request: new Request
{
First = 2, // Count of items in the current result (optional)
After = "MQ==" // Cursor value from previous response (optional)
});
Creating connection with sorting by a few fields
Sometimes sorting by ID is not enough. For example, we may want to sort our users by CreatedAt
field. But the field is not unique and few users can receive the same value for the field. In this case we cannot create unique cursor based only on CreateAt
field. So we have to add some unique field to the sorting to receive guarantee that our sorting is always consistent. ID is good for this.
We can use anonymous type to sort our objects by a few fields:
var query = session.Query<User>()
.Where(user => Email.Contains("@gmail.com"));
Connection<ExposedUser> connection = query.ToConnection(
orderBy: user => { user.CreatedAt, user.Id }, // Order by fields (order of fields is important!)
select: user => new ExposedUser // Select statement
{
Login = user.Login,
Email = user.Email,
FirstName = user.FirstName,
Name = user.FirstName + user.LastName
},
request: new Request
{
First = 2, // Count of items in the current result (optional)
});
Define sorting direction
OK, now we can sort users by creation time, but what if we want to do it in descending. We can use SortBy.Descending
method for this purpose and separately set sorting direction for each field:
var query = session.Query<User>()
.Where(user => Email.Contains("@gmail.com"));
Connection<ExposedUser> connection = query.ToConnection(
orderBy: user => {
SortBy.Descending(user.CreatedAt), // Now `CreatedAt` sorted by descending
user.Id
},
select: user => new ExposedUser // Select statement
{
Login = user.Login,
Email = user.Email,
FirstName = user.FirstName,
Name = user.FirstName + user.LastName
},
request: new Request
{
First = 2, // Count of items in the current result (optional)
});
Selecting objects from aggregated ids with Many To One relationship
DataLoader is nice way to optimize count of select queries to your database and it solves N+1 select problem. But sometimes amount of requested data might be too large to pass it to you database in one SQL request as most of DB has limitation for its size.
Methods BulkSelect
and BulkSelectAsync
allow to request data from database and put result to IDictionary
:
// income parameters from DataLoader
IReadOnlyCollection<long> userIds = new [] { 1, 2, 3, 4, 5 };
IDictionary<int, ExposedUserAddress> dictionary =
session.Query<UserAddress>().BulkSelect(
// filtering query to retrieve a junction of address and user
filter: (addresses, userIdsPart) =>
from address in addresses
from user in address.Users
where userIdsPart.Contains(user.Id)
select new { address, user },
// expression to extract result object from the junction
select: item => new ExposedUserAddress
{
Zip = item.address.Zip,
Street = item.address.Street,
House = item.address.House,
Text = item.address.Street + " " + item.address.House + ", " + item.address.Zip
}
// expression to extract id value from the junction
getId: item => item.address.Id,
// required ids
ids: userIds);
Selecting objects from aggregated ids with Many To Many relationship
Sometimes we need to select aggregated objects with many-to-many relationship. Methods BulkSelectMany
and BulkSelectManyAsync
help you with it and create ILookup
result:
// income parameters from DataLoader
IReadOnlyCollection<long> userIds = new [] { 1, 2, 3, 4, 5 };
ILookup<int, ExposedUserRole> lookup =
session.Query<UserRole>().BulkSelectMany(
// filtering query to retrieve a junction of address and user
filter: (roles, userIdsPart) =>
from role in roles
from user in role.Users
where userIdsPart.Contains(user.Id)
select new { role, user },
// expression to extract result object from the junction
select: junction => new ExposedUserRole
{
Code = junction.role.Code,
Name = junction.role.Name,
}
// expression to extract id value from the junction
getResultId: item => item.role.Id,
// expression to extract id value of joined object from the junction
getJoinedId: junction => junction.user.Id,
// required ids
ids: userIds);
Roadmap
- Add how it works explanations and documentation
- Create more test cases
- Support bidirectional connections
- Add cache for expression generation
- Support splitting by id strategy in bulk selection
- Support connections with bulk selection
Product | Versions 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 is compatible. net462 was computed. net463 was computed. net47 is compatible. 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. |
-
.NETFramework 4.6.1
- Newtonsoft.Json (>= 12.0.1)
- NHibernate (>= 5.2.2)
- System.ValueTuple (>= 4.5.0)
-
.NETFramework 4.7
- Newtonsoft.Json (>= 12.0.1)
- NHibernate (>= 5.2.2)
-
.NETStandard 2.0
- Newtonsoft.Json (>= 12.0.1)
- NHibernate (>= 5.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.