Entity GraphQL is a .NET Core (netstandard 1.6) library that allows you to query your data using the GraphQL syntax.
It can also be used to execute simple LINQ-style expressions at runtime against a given object which provides powerful runtime configuration.
Please explore, give feedback or join the development.
Via Nuget https://www.nuget.org/packages/EntityGraphQL
Note: There is no dependency on EF. Queries are compiled to IQueryable or IEnumberable linq expressions. EF is not a requirement - any ORM working on LinqProvider on an in-memory object should work - although EF is tested.
public class MyDbContext : DbContext {
protected override void OnModelCreating(ModelBuilder builder) {
// Set up your relations
}
public DbSet<Property> Properties { get; set; }
public DbSet<PropertyType> PropertyTypes { get; set; }
public DbSet<Location> Locations { get; set; }
}
public class Property {
public uint Id { get; set; }
public string Name { get; set; }
public PropertyType Type { get; set; }
public Location Location { get; set; }
}
public class PropertyType {
public uint Id { get; set; }
public string Name { get; set; }
public decimal Premium { get; set; }
}
public class Location {
public uint Id { get; set; }
public string Name { get; set; }
}Using what ever API library you wish. Here is an example for a ASP.NET Core WebApi controller
public class Startup {
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(opt => opt.UseInMemoryDatabase());
// add schema provider so we don't need to create it everytime
// Also for this demo we expose all fields on MyDbContext. See below for details on building custom fields etc.
services.AddSingleton(SchemaBuilder.FromObject<MyDbContext>());
}
}
[Route("api/[controller]")]
public class QueryController : Controller
{
private readonly MyDbContext _dbContext;
private readonly MappedSchemaProvider<MyDbContext> _schemaProvider;
public QueryController(MyDbContext dbContext, MappedSchemaProvider<MyDbContext> schemaProvider)
{
this._dbContext = dbContext;
this._schemaProvider = schemaProvider;
}
[HttpPost]
public object Post([FromBody]string query)
{
try
{
return _dbContext.QueryObject(query, _schemaProvider);
}
catch (Exception)
{
return HttpStatusCode.InternalServerError;
}
}
}This sets up 1 end point:
POSTat/api/querywhere the body of the post is the GraphQL query
You can now make a request to your API. For example
POST localhost:5000/api/query
{
properties { id name }
}
Will return the following result.
{
"data": {
"properties": [
{
"id": 11,
"name": "My Beach Pad"
},
{
"id": 12,
"name": "My Other Beach Pad"
}
]
}
}Maybe you only want a specific property
{
property(id: 11) {
id name
}
}
Will return the following result.
{
"data": {
"property": {
"id": 11,
"name": "My Beach Pad"
}
}
}If you need a deeper graph or relations, just ask
{
properties {
id
name
location {
name
}
type {
premium
}
}
}
Will return the following result.
{
"data": {
"properties": [
{
"id": 11,
"name": "My Beach Pad",
"location": {
"name": "Greece"
},
"type": {
"premium": 1.2
}
},
{
"id": 12,
"name": "My Other Beach Pad",
"location": {
"name": "Spain"
},
"type": {
"premium": 1.25
}
}
]
}
}- Fields - the core part, select the fields you want, including selecting the fields of sub-objects in the object graph
- Aliases (
{ cheapProperties: properties(maxCost: 100) { id name } }) - Arguments
- By default
SchemaBuilder.FromObject<TType>()generates a non-pural field for any type with a publicIdproperty, with the argument name ofid. E.g. A fieldpeoplethat returns aIEnumerable<Person>will create aperson(id)graphql field so you can query{ person(id: 1234) { name email } }to select a single person - See
schemaProvider.AddField("name", paramTypes, selectionExpression, "description");in "Customizing the schema" below for more on custom fields
- By default
- Mutations - see
AddMutationFrom<TType>(TType mutationClassInstance)and details below under Mutation
You can customise the default schema, or create one from stratch exposing only the fields you want.
// Build from object
var schema = SchemaBuilder.FromObject<MyDbContext>();
// custom fields on existing type
schema.Type<Person>().AddField("totalChildren", p => p.Children.Count(), "Number of children");
// custom type
schema.AddType<TBaseEntity>("name", "description");
// e.g. add a new type based on Person filtered by an expression
var type = schema.AddType<Person>("peopleOnMars", "All people on mars", person => person.Location.Name == "Mars");
type.AddPublicProperties(); // add the C# properties
// or select the fields
type.AddField(p => p.Id, "The unique identifier");
// Add fields with _required_ arguments - include `using static EntityGraphQL.Schema.ArgumentHelper;`
schemaProvider.AddField("user", new {id = Required<int>()}, (ctx, param) => ctx.Users.FirstOrDefault(u => u.Id == param.id), "description");
// Here the type schema of the parameters are defined with the anonymous type allowing you to write the selection query with compile time safety
// You can also use default()
var paramTypes = new {id = Required<Guid>()};
// If you use a value, the argument will not be required and the value used as a default
var paramTypes = new {unit = "meter"};Mutations allow you to make changes to data while selecting some data to return from the result. See the GraphQL documentation for more information on the syntax.
The main concept behind this is you create a class to encapsulate all your mutations. This lets you break them up into multiple classes by functionality or just entity type.
public class PropertyMutations
{
[GraphQLMutation]
public Property AddProperty(MyDbContext db, PropertyArgs args)
{
// do your magic here. e.g. with EF
var property = new Property { Name = args.Name, ... };
db.Properties.Add(property);
db.SaveChanges();
return property;
}
}
public class PropertyArgs
{
public string Name { get; set; }
public Decimal Cost { get; set; }
}To add this to you schema call
schemaProvider.AddMutationFrom(new PropertyMutations());- All
publicmethods marked with theGraphQLMutationattribute will be added to the schema - Parameters should be the base context that your schema is built from and a class that defines each available parameter (and type)
- Variables from the GraphQL request are mapped into the args parameter
You can now request a mutation
mutation AddProperty($name: String!, $cost: Float!) {
addProperty(name: $name, cost: $cost) {
id
name
}
}
With variables
{
"name": "beach pad",
"cost": 1000000.3
}This also selects the resulting id & name from the result of the mutation.
TODO - coming soon
Lets say you have a screen in your application listing properties that can be configured per customer or user to only show exactly what they are interested in. Instead of having a bunch of checkboxes and complex radio buttons etc. you can allow a simple EQL statement to configure the results shown. Or use those UI components to build the query.
// This might be a configured EQL statement for filtering the results. It has a context of Property
(type.id = 2 or type.id = 3) and type.name == 'Farm'This would compile to (Property p) => (p.Type.Id == 2 || p.Type.Id == 3) && p.Type.Name == "Farm";
This can then be used in various Linq functions either in memory or against an ORM.
// we create a schema provider to compile the statement against our Property type
var schemaProvider = SchemaBuilder.FromObject<Property>();
var compiledResult = EqlCompiler.Compile(myConfigurationEqlStatement, schemaProvider);
// you get your list of Properties from you DB
var thingsToShow = myProperties.Where(compiledResult.Expression);Another example is you want a customised calculated field. You can execute a compiled result passing in an instance of the context type.
// You'd take this from some configuration
var eql = "if location.name = 'Mars' then (cost + 5) * type.premium else (cost * type.premium) / 3"
var compiledResult = EqlCompiler.Compile(eql, schemaProvider);
var theRealPrice = compiledResult.Execute<decimal>(myPropertyInstance);On top of GraphQL syntax, any list/array supports some of the standard .NET LINQ methods.
array.where(filter)array.filter(filter)array.first(filter?)array.last(filter?)array.count(filter?)filteris an expression that can betrueorfalse, written from the context of the array item
array.take(int)array.skip(int)array.orderBy(field)array.orderByDesc(field)
e.g.
query {
cheap2BedPlaces: peroperties.where(price < 100000 && bedrooms >= 2).orderby(age) {
location { name }
price
}
}
Some larger things still on the list to complete, in no real order. Pull requests are very welcome.
- fix GetMethodContext() in methodProvider
- Add logging options
- Auto generate schema documentation page
- Add paging support (from graphql?)
- Support integration into security for controlling data access