Blazor

Efficiently Perform CRUD Actions in the Blazor DataGrid with GraphQL

TLDR: GraphQL lets clients request only the needed data, improving efficiency over REST APIs. Let’s see how to connect and manage data in the Syncfusion Blazor DataGrid using GraphQL. This guide covers configuring a GraphQL server, creating a data source, and implementing CRUD operations.

Welcome to this blog post, where we’ll explore using GraphQL to connect data with the Syncfusion Blazor DataGrid component. We will cover various operations, such as sorting, filtering, grouping, paging, and CRUD (Create, Read, Update, Delete) actions. By the end of this guide, you’ll better understand how GraphQL can enhance your data management capabilities within the Blazor DataGrid.

What is GraphQL?

GraphQL is an open-source query language for APIs, enabling clients to request specific data they need. This empowers clients to control data retrieval, shifting the focus from the server. GraphQL is not tied to any particular database or storage engine and serves as a flexible and efficient alternative to traditional REST APIs.

As defined by the official GraphQL website, “GraphQL is a query language for your API and a server-side runtime for executing queries using a type system you define for your data.” Unlike conventional REST APIs, GraphQL facilitates a streamlined data querying and manipulation approach. It fosters decoupling the frontend and backend layers by enabling clients to specify precise data requirements in a single request.

Create a GraphQL server

First, we need to create a new ASP.NET Core Empty app as the GraphQL Server. To do so, open Visual Studio. In the Create a New Project window, choose the ASP.NET Core Empty project template.

Refer to the following image.

Configuring the GraphQL server app

  1. Begin by installing the latest version of the HotChocolate.AspNetCore package. For this blog, we’ve utilized version 13.3.1.
  2. Create GraphQLQuery and GraphQLMutation classes to define the GraphQL resolver and mutation methods, respectively.
  3. Implement the following configuration code to set up GraphQL query and mutation types and enable CORS.
    Program.cs
    var builder = WebApplication.CreateBuilder(args); 
    //GraphQL resolver is defined in the GraphQLQuery class, and mutation methods are defined in the GraphQLMutation class. 
    builder.Services.AddGraphQLServer().AddQueryType<GraphQLQuery>().AddMutationType<GraphQLMutation>(); 
    //CORS is enabled to access the GraphQL server from the client app. 
    builder.Services.AddCors(options => 
    { 
       options.AddPolicy("AllowSpecificOrigin", builder => 
       {
           builder.WithOrigins("https://xxxxxx") 
           .AllowAnyHeader() 
            .AllowAnyMethod() 
            .AllowCredentials().Build(); 
        });
    });
    var app = builder.Build(); 
    app.UseCors("AllowSpecificOrigin");
    app.UseRouting();
    app.UseEndpoints(endpoints => endpoints.MapGraphQL());
    app.Run();

This configuration sets up the GraphQL server, defines resolver and mutation methods, and enables CORS to facilitate seamless client-server communication. Adjust the origins of the CORS policy according to your specific requirements.

Create a data source

The following code demonstrates creating a data source with a list of Order data in the GraphQLQuery class, which will be bound to the Blazor DataGrid.

public class GraphQLQuery { 
 public static List<Order> Orders { get; set; } = GetOrdersList(); 
 private static List<Order> GetOrdersList() {
   var data = new List<Order>();
   int count = 1000; int employeeCount = 0;
   for (int i = 0; i < 10; i++) {
       data.Add(new Order() { OrderID = count + 1, EmployeeID = employeeCount + 1, CustomerID = "ALFKI", OrderDate = new DateTime(2023, 08, 23), Freight = 5.7 * 2, Address = new CustomerAddress() { ShipCity = "Berlin", ShipCountry = "Denmark" } }); 
       data.Add(new Order() { OrderID = count + 2, EmployeeID = employeeCount + 2, CustomerID = "ANANTR", OrderDate = new DateTime(1994, 08, 24), Freight = 6.7 * 2, Address = new CustomerAddress() { ShipCity = "Madrid", ShipCountry = "Brazil" } }); 
       data.Add(new Order() { OrderID = count + 3, EmployeeID = employeeCount + 3, CustomerID = "BLONP", OrderDate = new DateTime(1993, 08, 25), Freight = 7.7 * 2, Address = new CustomerAddress() { ShipCity = "Cholchester", ShipCountry = "Germany" } }); 
       data.Add(new Order() { OrderID = count + 4, EmployeeID = employeeCount + 4, CustomerID = "ANTON", OrderDate = new DateTime(1992, 08, 26), Freight = 8.7 * 2, Address = new CustomerAddress() { ShipCity = "Marseille", ShipCountry = "Austria" } }); 
       data.Add(new Order() { OrderID = count + 5, EmployeeID = employeeCount + 5, CustomerID = "BOLID", OrderDate = new DateTime(1991, 08, 27), Freight = 9.7 * 2, Address = new CustomerAddress() { ShipCity = "Tsawassen", ShipCountry = "Switzerland" } }); 
       count += 5; 
       employeeCount += 5;
   } 
   return data;
 }
} 

Return data with required format

To bind data to the Blazor DataGrid component, the resolver function should return the data with Result, Count, and optional Aggregates properties defined in the ReturnType<T> class.

public class ReturnType<T> 
{
     public int Count { get; set; }
     public IEnumerable<T> Result { get; set; }
     [GraphQLType(typeof(AnyType))]
     public IDictionary<string, object> Aggregates { get; set; } }
}

Create resolver function argument classes

The GraphQL query will be passed from the grid with the dataManager property. So, to accept this parameter in the resolver function, we have to create the DataManagerRequest class, and the necessary classes required for the DataManagerRequest properties.

Refer to the following code examples.

DataManagerRequest class

public class DataManagerRequest {
  [GraphQLName("Skip")] 
  public int Skip { get; set; }
  
  [GraphQLName("Take")] 
  public int Take { get; set; } 
  
  [GraphQLName("RequiresCounts")]
  public bool RequiresCounts { get; set; } = false; 
  
  [GraphQLName("Params")] 
  [GraphQLType(typeof(AnyType))] 
  public IDictionary<string, object> Params { get; set; } 
  
  [GraphQLName("Aggregates")]
  [GraphQLType(typeof(AnyType))]
  public List<Aggregate>? Aggregates { get; set; }
  
  [GraphQLName("Search")]
  public List<SearchFilter>? Search { get; set; }
  
  [GraphQLName("Sorted")]
  public List<Sort>? Sorted { get; set; }
  
  [GraphQLName("Where")]
  [GraphQLType(typeof(AnyType))]
  public List<WhereFilter>? Where { get; set; }
  
  [GraphQLName("Group")]
  public List<string>? Group { get; set; }
  
  [GraphQLName("antiForgery")]
  public string? antiForgery { get; set; }
  
  [GraphQLName("Table")]
  public string? Table { get; set; }
  
  [GraphQLName("IdMapping")]
  public string? IdMapping { get; set; }
  
  [GraphQLName("Select")]
  public List<string>? Select { get; set; }
  
  [GraphQLName("Expand")]
  public List<string>? Expand { get; set; }
  
  [GraphQLName("Distinct")]
  public List<string>? Distinct { get; set; }
  
  [GraphQLName("ServerSideGroup")]
  public bool? ServerSideGroup { get; set; }
  
  [GraphQLName("LazyLoad")]
  public bool? LazyLoad { get; set; }
  
  [GraphQLName("LazyExpandAllGroup")]
  public bool? LazyExpandAllGroup { get; set; }
}

Aggregate class

public class Aggregate { 
  [GraphQLName("Field")]
  public string Field { get; set; }
  
  [GraphQLName("Type")]
  public string Type { get; set; }
}

Sorting class

public class Sort { 
  [GraphQLName("Name")] 
  public string Name { get; set; }
  
  [GraphQLName("Direction")] 
  public string Direction { get; set; } 
  
  [GraphQLName("Comparer")]
  [GraphQLType(typeof(AnyType))] 
  public object Comparer { get; set; } 
}

Searching class

public class SearchFilter { 
  [GraphQLName("Fields")]
  public List<string> Fields { get; set; }
  
  [GraphQLName("Key")]
  public string Key { get; set; }
  
  [GraphQLName("Operator")]
  public string Operator { get; set; }
  
  [GraphQLName("IgnoreCase")]
  public bool IgnoreCase { get; set; } 
}

Filtering class

public class WhereFilter { 
  [GraphQLName("Field")]
  public string? Field { get; set; } 
  
  [GraphQLName("IgnoreCase")]
  public bool? IgnoreCase { get; set; }
  
  [GraphQLName("IgnoreAccent")]
  public bool? IgnoreAccent { get; set; }
  
  [GraphQLName("IsComplex")]
  public bool? IsComplex { get; set; }
  
  [GraphQLName("Operator")]
  public string? Operator { get; set; } 
  
  [GraphQLName("Condition")]
  public string? Condition { get; set; } 
  
  [GraphQLName("value")]
  [GraphQLType(typeof(AnyType))]
  public object? value { get; set; } 
  
  [GraphQLName("predicates")]
  public List<WhereFilter>? predicates { get; set; }
}

Order class

public class Order
{
    [GraphQLName("OrderID")]
    public int? OrderID { get; set; }
    
    [GraphQLName("CustomerID")]
    public string? CustomerID { get; set; }
    
    [GraphQLName("EmployeeID")]
    public int? EmployeeID { get; set; }
    
    [GraphQLName("OrderDate")]
    public DateTime? OrderDate { get; set; }
    
    [GraphQLName("Freight")]
    public double? Freight { get; set; }
    
    [GraphQLName("Address")]
    public CustomerAddress? Address { get; set; }
}

Customer class

public class CustomerAddress
{
    [GraphQLName("ShipCity")]
    public string ShipCity { get; set; }
    
    [GraphQLName("ShipCountry")]
    public string ShipCountry { get; set; }
}

Performing data operations

The following code demonstrates the resolver function used in the GraphQL server to bind data and perform data operations like paging, sorting, filtering, and more.

public class GraphQLQuery 
{ 
  public ReturnType<Order> OrdersData(DataManagerRequest dataManager) { 
    IEnumerable<Order> result = Orders;
    if (dataManager.Search != null) {
        //Perform searching here. 
    } 
    if (dataManager.Sorted != null) { 
        //Perform sorting here. 
    } 
    if (dataManager.Where != null) { 
        //Perform filtering here. 
    } 
    int count = result.Count(); 
    if (dataManager.Skip != 0) { 
        //Perform paging here. 
    } 
    if (dataManager.Take != 0) {
        //Perform Paging here. 
    } 
    if (dataManager.Aggregates != null) {
        //Perform total aggregate here. 
        IDictionary<string, object> aggregates = new Dictionary<string, object>();; 
        return new ReturnType<Order>() { Count = count, Result = result, Aggregates = aggregates }; 
    }
    return dataManager.RequiresCounts ? new ReturnType<Order>() { Result = result, Count = count } : new ReturnType<Order>() { Result = result };
}

Performing CRUD operations

The following code demonstrates the mutation methods used in the GraphQL server to perform CRUD operations.

public class GraphQLMutation { 
 public Order CreateOrder(Order order, int index, string action, [GraphQLType(typeof(AnyType))]   IDictionary<string, object> additionalParameters) 
 { 
   GraphQLQuery.Orders.Insert(index, order); 
   return order; 
 }
 public Order UpdateOrder(Order order, string action, string primaryColumnName, int  primaryColumnValue, [GraphQLType(typeof(AnyType))] IDictionary<string, object> additionalParameters)
 { 
   Order updatedOrder = GraphQLQuery.Orders.Where(x => x.OrderID == primaryColumnValue).FirstOrDefault(); 
   updatedOrder.OrderID = order.OrderID; 
   updatedOrder.CustomerID = order.CustomerID; 
   updatedOrder.Freight = order.Freight; 
   updatedOrder.OrderDate = order.OrderDate; 
   return updatedOrder; 
 }
 public Order DeleteOrder(int primaryColumnValue, string action, string primaryColumnName, [GraphQLType(typeof(AnyType))] IDictionary<string, object> additionalParameters) 
 { 
   Order deletedOrder = GraphQLQuery.Orders.Where(x => x.OrderID == primaryColumnValue).FirstOrDefault(); 
   GraphQLQuery.Orders.Remove(deletedOrder);
   return deletedOrder; 
 } 
}

Run the GraphQL server app

To run the GraphQL server app, use the Banana Cake Pop IDE. Alternatively, you can use any other IDE that supports running GraphQL queries.

Add Syncfusion Blazor DataGrid component

To add the Syncfusion Blazor DataGrid component and leverage its capabilities with the GraphQL Adaptor, follow these detailed steps:

  1. Create a Blazor application and add Syncfusion.Blazor NuGet package.
  2. Now, bind data in the Syncfusion Blazor DataGrid using the GraphQL Adaptor:
    • Connect your GraphQL service data to the Blazor DataGrid by providing the GraphQL query string via the Query property of the GraphQLAdaptorOptions class.
    • Set the ResolverName property of GraphQLAdaptorOptions to efficiently map the response from your GraphQL service to the Blazor DataGrid.
    • Implement a Resolver function within your GraphQL server to handle data operations such as paging, sorting, and filtering.
    • Configure mutation queries in the Mutation property of the GraphQLAdaptorOptions to enable CRUD operations within the DataGrid.

Fetching data from the GraphQL service

Follow these steps to bind data from a GraphQL service to the Blazor DataGrid:

  1. Provide the GraphQL query string via the Query property of the GraphQLAdaptorOptions.
  2. Set the ResolverName property of GraphQLAdaptorOptions to map the response efficiently.
  3. The GraphQLAdaptor expects the response in JSON format with properties such as Result, Count, and Aggregates, containing the collection of entities, total number of records, and aggregate values, respectively.
  4. Ensure that the GraphQL response is returned in JSON format with the structure { “data”: { … } } with the query name as a field.

Refer to the following code example.

@using Syncfusion.Blazor 
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="Order" AllowPaging=true AllowSorting=true AllowFiltering=true>
    <SfDataManager Url="https://xxxxxx" GraphQLAdaptorOptions=@adaptorOptions Adaptor="Adaptors.GraphQLAdaptor"></SfDataManager>
    <GridColumns>
        <GridColumn Field=@nameof(Order.OrderID) HeaderText="Order ID" IsPrimaryKey=true TextAlign="TextAlign.Right" Width="120"></GridColumn>
        <GridColumn Field=@nameof(Order.CustomerID) HeaderText="Customer Name" Width="150"></GridColumn>
        <GridColumn Field=@nameof(Order.OrderDate) HeaderText="Order Date" Format="d" TextAlign="TextAlign.Right" Width="130"></GridColumn>
        <GridColumn Field=@nameof(Order.Freight) HeaderText="Freight" Format="C2" TextAlign="TextAlign.Right" Width="120"></GridColumn>
    </GridColumns>
</SfGrid>
@code{
    private GraphQLAdaptorOptions adaptorOptions { get; set; } = new GraphQLAdaptorOptions
    {
        Query = @"
            query ordersData($dataManager: DataManagerRequestInput!){
                ordersData(dataManager: $dataManager) {
                    count, result { OrderID, CustomerID, OrderDate, Freight } , aggregates
                }
            }",
        ResolverName = "OrdersData"
    };
    public class Order
    {
        public int? OrderID { get; set; }
        public string CustomerID { get; set; }
        public DateTime? OrderDate { get; set; }
        public double? Freight { get; set; }
    }
}

Performing CRUD operations using mutation

You can perform the CRUD operations by setting the mutation queries in the Mutation property of the GraphQLAdaptorOptions class.

Set the Insert mutation query in the Insert property of Mutation in the GraphQLAdaptorOptions. Similarly, you have to set the Update and Delete mutation queries in the Update and Delete properties of Mutation in the GraphQLAdaptorOptions, respectively.

The following variables are passed as parameters to the mutation method written for the Insert operation on the server side.

Properties

Description

record

The new record needs to be inserted.

index

Specifies the index at which the newly added record will be inserted.

action

Indicates the type of operation being performed. When the same method is used for all CRUD actions, the respective argument distinguishes the actions, such as Add, Delete, and Update.

additionalParameters

An optional parameter that can be used to perform any operations.

The following variables are passed as parameters to the mutation method written for the Update operation on the server side.

Properties

Description

record

The new record needs to be updated.

action

Indicates the type of operation being performed. When the same method is used for all CRUD actions, the respective argument distinguishes the actions, such as Add, Delete, and Update.

primaryColumnName

Specifies the field name of the primary column.

primaryColumnValue

Specifies the primary column value that needs to be updated in the collection.

additionalParameters

An optional parameter that can be used to perform any operations.

The following variables are passed as parameters to the mutation method written for the Delete operation on the server side.

Properties

Description

primaryColumnValue

Specifies the primary column value that needs to be removed from the collection.

action

 Indicates the type of operation being performed. When the same method is used for all CRUD actions, the respective argument distinguishes the actions, such as Add, Delete, and Update.

primaryColumnName

Specifies the field name of the primary column.

additionalParameters

An optional parameter that can be used to perform any operations.

Refer to the following code example.

@using Syncfusion.Blazor

@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids

<SfGrid TValue="Order" AllowPaging=true AllowSorting=true AllowFiltering=true
    Toolbar="@(new List<string>() { "Search", "Add", "Edit", "Delete", "Cancel", "Update" })">
    <GridEditSettings AllowAdding=true AllowEditing=true AllowDeleting=true Mode="EditMode.Normal"></GridEditSettings>
    <SfDataManager Url="https://xxxxxx" GraphQLAdaptorOptions=@adaptorOptions Adaptor="Adaptors.GraphQLAdaptor"></SfDataManager>
    <GridColumns>
        <GridColumn Field=@nameof(Order.OrderID) HeaderText="Order ID" IsPrimaryKey=true TextAlign="TextAlign.Right" Width="120"></GridColumn>
        <GridColumn Field=@nameof(Order.CustomerID) HeaderText="Customer Name" Width="150"></GridColumn>
        <GridColumn Field=@nameof(Order.OrderDate) HeaderText="Order Date" Format="d" TextAlign="TextAlign.Right" Width="130"></GridColumn>
        <GridColumn Field=@nameof(Order.Freight) HeaderText="Freight" Format="C2" TextAlign="TextAlign.Right" Width="120"></GridColumn>
    </GridColumns>
</SfGrid>

@code{
    private GraphQLAdaptorOptions adaptorOptions { get; set; } = new GraphQLAdaptorOptions
    {
        Query = @"
            query ordersData($dataManager: DataManagerRequestInput!){
                ordersData(dataManager: $dataManager) {
                    count, result { OrderID, CustomerID, OrderDate, Freight } , aggregates
                }
            }",
        Mutation = new GraphQLMutation
        {
            Insert = @"
                mutation create($record: OrderInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
                  createOrder(order: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
                    OrderID, CustomerID, OrderDate, Freight
                  }
                }",
            Update = @"
                mutation update($record: OrderInput!, $action: String!, $primaryColumnName: String! , $primaryColumnValue: Int!, $additionalParameters: Any) {
                  updateOrder(order: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
                    OrderID, CustomerID, OrderDate, Freight
                  }
                }",
            Delete = @"
                mutation delete($primaryColumnValue: Int!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
                  deleteOrder(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
                    OrderID, CustomerID, OrderDate, Freight
                  }
                }"
        },
        ResolverName = "OrdersData"
    };
    
    public class Order
    {
        public int? OrderID { get; set; }
        public string CustomerID { get; set; }
        public DateTime? OrderDate { get; set; }
        public double? Freight { get; set; }
    }
}

After executing the above code examples, we’ll get the following output.

Performing CRUD actions using GraphQL in the Blazor DataGrid

Syncfusion DataManager with GraphQL adaptor

The GraphQLAdaptor integrated into our Syncfusion Blazor DataManager empowers efficient data management by facilitating CRUD operations and advanced data manipulations like paging, sorting, and filtering. The precise transmission of required parameters to the GraphQL server enables tailored data retrieval based on client requirements, seamlessly integrating with various data-bound components for displaying the retrieved data.

GitHub reference

Also, explore performing CRUD actions in the Blazor DataGrid using GraphQL GitHub demo for more details.

Conclusion

Thanks for reading! By now, you should have a comprehensive understanding of leveraging the GraphQL server within our Syncfusion Blazor DataGrid component for data binding and CRUD operations. With GraphQL’s single endpoint, you can save time and significantly boost productivity while fetching data. Try the steps outlined in this blog and share your valuable feedback in the comments section below!

Existing Syncfusion users can access the latest version of Essential Studio from the License and  Downloads page. If you’re new to Syncfusion, use our 30-day free trial to discover these features.

For any inquiries or assistance, please contact us through our support forum, support portal, or feedback portal. We are always here to help you!

Related blogs

Maithiliy K

Maithiliy is a Product Manager for web components at Syncfusion. She has 9 years of experience in developing software components and has started her career in Syncfusion in 2010. She is an expert in providing solutions for web products related requirements.