diff --git a/blazor/datagrid/connecting-to-adaptors/graphql-adaptor.md b/blazor/datagrid/connecting-to-adaptors/graphql-adaptor.md
index 17535fe12f..d62204aa54 100644
--- a/blazor/datagrid/connecting-to-adaptors/graphql-adaptor.md
+++ b/blazor/datagrid/connecting-to-adaptors/graphql-adaptor.md
@@ -1,169 +1,309 @@
---
layout: post
-title: Bind GraphQL Data in Blazor DataGrid | Syncfusion
-description: Learn how to bind data from a GraphQL API to the Syncfusion Blazor DataGrid, including querying, mutation, and integration techniques.
-platform: Blazor
+title: Blazor Data Grid with GraphQL: CRUD & Data Operations | Syncfusion
+description: Build Blazor apps with GraphQL and Syncfusion Data Grid to enable CRUD, filtering, sorting, paging, and grouping for seamless data operations.
control: DataGrid
+platform: blazor
documentation: ug
---
-# GraphQL Adaptor in Blazor DataGrid
+# Connect Syncfusion Blazor DataGrid with GraphQL using Hot Chocolate
-GraphQL is a powerful query language for APIs, designed to provide a more efficient alternative to traditional REST APIs. It allows you to precisely fetch the data you need, reducing over-fetching and under-fetching of data. GraphQL provides a flexible and expressive syntax for querying, enabling clients to request only the specific data they require.
+GraphQL is a query language that allows applications to request exactly the data needed, nothing more and nothing less. Unlike traditional REST APIs that return fixed data structures, GraphQL enables the client to specify the shape and content of the response.
-Syncfusion’s Blazor DataGrid seamlessly integrates with GraphQL servers using the GraphQLAdaptor in the [SfDataManager](https://blazor.syncfusion.com/documentation/data/getting-started-with-web-app). This specialized adaptor simplifies the interaction between the Grid and GraphQL servers, allowing efficient data retrieval with support for various operations like CRUD (Create, Read, Update and Delete), paging, sorting, and filtering.
+**Traditional REST APIs** and **GraphQL** differ mainly in how data is requested and returned: **REST APIs expose** multiple endpoints that return fixed data structures, often including unnecessary fields and requiring several requests to fetch related data, while **GraphQL** uses a single endpoint where queries define the exact fields needed, enabling precise responses and allowing related data to be retrieved efficiently in one request. This makes **GraphQL** especially useful for **Blazor DataGrid integration**, the **reason** is data‑centric UI components require well‑structured and selective datasets to support efficient filtering, reduce network calls, and improve overall performance.
-This section describes a step-by-step process for retrieving data from a GraphQL service using GraphQLAdaptor, then binding it to the Grid to facilitate data and CRUD operations.
+**Key GraphQL Concepts**
-## Configure a GraphQL server
+- **Queries**: A query is a request to read data. Queries do not modify data; they only retrieve it.
+- **Mutations**: A mutation is a request to modify data. Mutations create, update, or delete records.
+- **Resolvers**: Each query or mutation is handled by a resolver, which is a function responsible for fetching data or executing an operation. **Query resolvers** handle **read operations**, while **mutation resolvers** handle **write operations**.
+- **Schema**: Defines the structure of the API. The schema describes available data types, the fields within those types, and the operations that can be executed. Query definitions specify how data can be retrieved, and mutation definitions specify how data can be modified.
-To configure a GraphQL server using Hot Chocolate with the Syncfusion® Blazor DataGrid, follow these steps:
+[Hot Chocolate](https://chillicream.com/docs/hotchocolate/v15) is an open‑source GraphQL server framework for .NET. Hot Chocolate enables the creation of GraphQL APIs using ASP.NET Core and integrates seamlessly with modern .NET applications, including Blazor.
-**Step 1: Create a new ASP.NET Core application**
+## Prerequisites
-- Open Visual Studio and select **Create a new project**.
-- Choose **ASP.NET Core Web App** and name the project `GraphQLServer`.
-- Alternatively, you can use a terminal or command prompt to create the project:
- ```bash
- dotnet new web -n GraphQLServer
- ```
-- Navigate to the project directory:
- ```bash
- cd GraphQLServer
- ```
+Install the following software and packages before starting the process:
-**Step 2: Add the Hot Chocolate NuGet package**
+| Software/Package | Version | Purpose |
+|-----------------|---------|---------|
+| Visual Studio 2022 | 17.0 or later | Development IDE with Blazor workload |
+| .NET SDK | net8.0 or compatible | Runtime and build tools |
+| HotChocolate.AspNetCore | 15.1 or later | GraphQL server framework |
+| Syncfusion.Blazor.Grids | {{site.blazorversion}} | DataGrid component |
+| Syncfusion.Blazor.Themes | {{site.blazorversion}} | Styling for DataGrid |
-- Open the **NuGet Package Manager** by right-clicking on the project in the **Solution Explorer** and selecting **Manage NuGet Packages**.
-- Go to the **Browse** tab, search for `HotChocolate.AspNetCore`, and select the package.
-- Click **Install** to add it to your project.
+## Setting Up the GraphQL Backend
-Alternatively, you can use the **Package Manager Console** to install the package by running the following command:
+### Step 1: Install Required NuGet Packages and Configure Launch Settings
+
+Before installing NuGet packages, a new Blazor Web Application must be created using the default template. The template automatically generates essential starter files—such as **Program.cs, appsettings.json, launchSettings.json, the wwwroot folder, and the Components folder**.
+
+For this guide, a Blazor application named **Grid_GraphQLAdaptor** has been created.
+
+**Install NuGet Packages**
+
+NuGet packages are software libraries that add functionality to applications. The following packages enable GraphQL server functionality+ and Syncfusion DataGrid components.
+
+**Required Packages:**
+
+- **HotChocolate.AspNetCore** (version 15.1 or later) - GraphQL server framework
+- **Syncfusion.Blazor.Grids** (version {{site.blazorversion}}) - DataGrid component
+- **Syncfusion.Blazor.Themes** (version {{site.blazorversion}}) - Styling for DataGrid
+
+**Method 1: Using Package Manager Console**
+
+1. Open Visual Studio 2022.
+2. Navigate to **Tools → NuGet Package Manager → Package Manager Console**.
+3. Run the following commands:
```powershell
-Install-Package HotChocolate.AspNetCore
+Install-Package HotChocolate.AspNetCore -Version 15.1.12
+Install-Package Syncfusion.Blazor.Grids -Version {{site.blazorversion}}
+Install-Package Syncfusion.Blazor.Themes -Version {{site.blazorversion}}
```
-**Step 3: Create a model class**
+**Method 2: Using NuGet Package Manager UI**
-Add a new folder named **Models**. Then, add a model class named **OrderData.cs** in the **Models** folder to represent the order data.
+1. Open **Visual Studio 2022 → Tools → NuGet Package Manager → Manage NuGet Packages for Solution**.
+2. Search for and install each package individually:
+ - **HotChocolate.AspNetCore** (version 15.1.12 or later)
+ - **Syncfusion.Blazor.Grids** (version {{site.blazorversion}})
+ - **Syncfusion.Blazor.Themes** (version {{site.blazorversion}})
-{% tabs %}
-{% highlight cs tabtitle="OrderData.cs" %}
+All required packages are now installed.
-using System.Text.Json.Serialization;
+---
-namespace GraphQLServer.Models
-{
- public class OrderData
- {
- public static List Orders = new List();
+### Step 2: Register Hot Chocolate Services in Program.cs
- public OrderData() { }
+The `Program.cs` file configures and registers the GraphQL services.
- public OrderData(
- int orderID, string customerId, int employeeId, double freight, bool verified,
- DateTime orderDate, string shipCity, string shipName, string shipCountry,
- DateTime shippedDate, string shipAddress)
- {
- OrderID = orderID;
- CustomerID = customerId;
- EmployeeID = employeeId;
- Freight = freight;
- Verified = verified;
- OrderDate = orderDate;
- ShipCity = shipCity;
- ShipName = shipName;
- ShipCountry = shipCountry;
- ShippedDate = shippedDate;
- ShipAddress = shipAddress;
- }
+**Instructions:**
- public static List GetAllRecords()
- {
- if (Orders.Count == 0)
- {
- int code = 10000;
- for (int i = 1; i < 10; i++)
- {
- Orders.Add(new OrderData(code + 1, "ALFKI", i, 2.3 * i, false, new DateTime(1991, 05, 15), "Berlin", "Simons Bistro", "Denmark", new DateTime(1996, 7, 16), "Kirchgasse 6"));
- Orders.Add(new OrderData(code + 2, "ANATR", i + 2, 3.3 * i, true, new DateTime(1990, 04, 04), "Madrid", "Queen Cozinha", "Brazil", new DateTime(1996, 9, 11), "Avda. Azteca 123"));
- Orders.Add(new OrderData(code + 3, "ANTON", i + 1, 4.3 * i, true, new DateTime(1957, 11, 30), "Cholchester", "Frankenversand", "Germany", new DateTime(1996, 10, 7), "Carrera 52 con Ave. Bolívar #65-98 Llano Largo"));
- Orders.Add(new OrderData(code + 4, "BLONP", i + 3, 5.3 * i, false, new DateTime(1930, 10, 22), "Marseille", "Ernst Handel", "Austria", new DateTime(1996, 12, 30), "Magazinweg 7"));
- Orders.Add(new OrderData(code + 5, "BOLID", i + 4, 6.3 * i, true, new DateTime(1953, 02, 18), "Tsawassen", "Hanari Carnes", "Switzerland", new DateTime(1997, 12, 3), "1029 - 12th Ave. S."));
- code += 5;
- }
- }
- return Orders;
- }
+1. Open the `Program.cs` file at the project root.
+2. Add the following code after `var builder = WebApplication.CreateBuilder(args);`:
+
+```csharp
+[Program.cs]
+
+using Grid_GraphQLAdaptor.Models;
+using HotChocolate.Execution.Configuration;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Register Hot Chocolate GraphQL services
+builder.Services
+ .AddGraphQLServer()
+ .AddQueryType()
+ .AddMutationType();
+
+var app = builder.Build();
+
+// Map the GraphQL endpoint (default: /graphql)
+app.MapGraphQL();
+
+app.Run();
+```
+
+**Details:**
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
+- `AddGraphQLServer()` - Initializes the Hot Chocolate GraphQL server
+- `AddQueryType()` - Registers query resolvers for read operations
+- `AddMutationType()` - Registers mutation resolvers for write operations
+- `MapGraphQL()` - Exposes the GraphQL endpoint at `/graphql`
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
+The GraphQL backend is now configured and ready. The GraphQL endpoint is accessible at `https://localhost:xxxx/graphql`.
- [JsonPropertyName("employeeID")]
- public int? EmployeeID { get; set; }
+---
+
+### Step 3: Configure Launch Settings (Port Configuration)
+
+The **launchsettings.json** file controls the port number where the application runs. This file is located in the **Properties** folder at **Properties/launchsettings.json**.
+
+**Instructions to Change the Port:**
+
+1. Open the **Properties** folder in the project root.
+2. Double-click **launchsettings.json** to open the file.
+3. Locate the `https` profile section:
+
+```json
+"https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7001;http://localhost:5001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+}
+```
- [JsonPropertyName("freight")]
- public double? Freight { get; set; }
+4. Modify the `applicationUrl` property to change the port numbers:
+ - `https://localhost:7001` - HTTPS port (change 7001 to desired port)
+ - `http://localhost:5001` - HTTP port (change 5001 to desired port)
+
+5. Example configuration with custom ports:
+
+```json
+"https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7777;http://localhost:5555",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+}
+```
+
+6. Save the file and restart the application for the changes to take effect.
+
+**Important Notes:**
+
+- Port numbers must be between 1024 and 65535.
+- Avoid using ports already in use by other applications.
+- The GraphQL endpoint will be accessible at the configured HTTPS URL (e.g., `https://localhost:7777/graphql`).
+
+All configuration steps are now complete.
+
+---
- [JsonPropertyName("verified")]
- public bool? Verified { get; set; }
+### Step 4: Create the Data Model
- [JsonPropertyName("orderDate")]
- public DateTime? OrderDate { get; set; }
+A data model represents the structure of data that the application stores. It defines the properties (fields) that make up a record. Each property corresponds to a column in the database table. The data model acts as the blueprint for how data is organized and accessed throughout the application.
- [JsonPropertyName("shipCity")]
- public string ShipCity { get; set; }
+In the context of an expense tracker, the data model defines what information is stored for each expense entry. Properties include the expense identifier, employee details, department, category, and financial amounts.
- [JsonPropertyName("shipName")]
- public string? ShipName { get; set; }
+**Instructions**:
- [JsonPropertyName("shipCountry")]
- public string ShipCountry { get; set; }
+1. Create a new folder named **Models** in the project root directory.
+2. Inside the **Models** folder, create a new file named **ExpenseRecord.cs**.
+3. Define the **ExpenseRecord** class with the following code:
- [JsonPropertyName("shippedDate")]
- public DateTime? ShippedDate { get; set; }
+**File Location:** `Models/ExpenseRecord.cs`
- [JsonPropertyName("shipAddress")]
- public string? ShipAddress { get; set; }
+```csharp
+namespace Grid_GraphQLAdaptor.Models
+{
+ ///
+ /// Represents a single expense record stored in the database.
+ /// Each property corresponds to a database column.
+ ///
+ public class ExpenseRecord
+ {
+ ///
+ /// Unique identifier for each expense record.
+ /// This is the primary key in the database.
+ ///
+ public string ExpenseId { get; set; }
+
+ ///
+ /// Name of the employee who submitted the expense.
+ ///
+ public string EmployeeName { get; set; }
+
+ ///
+ /// Email address of the employee.
+ ///
+ public string EmployeeEmail { get; set; }
+
+ ///
+ /// Department to which the employee belongs.
+ ///
+ public string Department { get; set; }
+
+ ///
+ /// Category of the expense (e.g., Travel, Meals, Office Supplies).
+ ///
+ public string Category { get; set; }
+
+ ///
+ /// Base amount of the expense before tax.
+ ///
+ public decimal Amount { get; set; }
+
+ ///
+ /// Tax percentage applied to the expense.
+ ///
+ public decimal TaxPct { get; set; }
+
+ ///
+ /// Total amount including tax (calculated: Amount + (Amount * TaxPct / 100)).
+ ///
+ public decimal TotalAmount { get; set; }
}
}
+```
+**Property Mapping Reference**
-{% endhighlight %}
-{% endtabs %}
+The following table shows how C# properties map to database columns and GraphQL field names:
-**Step 4: Define the GraphQL query**
+| Property Name (C#) | Database Column | GraphQL Field Name | Data Type | Purpose |
+|---|---|---|---|---|
+| `ExpenseId` | `expense_id` | `expenseId` | `String` | Unique identifier for the expense record |
+| `EmployeeName` | `employee_name` | `employeeName` | `String` | Name of the employee submitting the expense |
+| `EmployeeEmail` | `employee_email` | `employeeEmail` | `String` | Email address for contact purposes |
+| `Department` | `department` | `department` | `String` | Organizational department |
+| `Category` | `category` | `category` | `String` | Type or classification of expense |
+| `Amount` | `amount` | `amount` | `Decimal` | Base expense amount before tax |
+| `TaxPct` | `tax_pct` | `taxPct` | `Decimal` | Tax percentage applied |
+| `TotalAmount` | `total_amount` | `totalAmount` | `Decimal` | Final amount including tax |
-Create a `GraphQLQuery` class to define the query resolver for fetching order data. This class will handle the logic for retrieving data from the `OrderData` model. The following code demonstrates the `DataManagerRequestInput` class, which is passed as an argument to the resolver function.
+**Important Convention: Camel Case Conversion**
-{% tabs %}
-{% highlight cs tabtitle="GraphQLQuery.cs" %}
+**Hot Chocolate GraphQL** automatically converts C# property names (**PascalCase**) to GraphQL field names (**camelCase**). This convention ensures consistent naming in the GraphQL schema:
-using GraphQLServer.Models;
+- C# Property: `EmployeeName` → GraphQL Field: `employeeName`
+- C# Property: `ExpenseId` → GraphQL Field: `expenseId`
+- C# Property: `TotalAmount` → GraphQL Field: `totalAmount`
+
+**Explanation**:
+
+- The [Key] attribute marks the `ExpenseId` property as the primary key (a unique identifier for each record).
+- Each property represents a column in the database table.
+- The model provides the data structure that GraphQL uses to process queries and mutations.
+
+The expense data model has been successfully created.
+
+---
+
+### Step 5: GraphQL Query Resolvers
+
+A query resolver is a method in the backend that handles read requests from the client. When the Blazor DataGrid needs to fetch data, it sends a GraphQL query to the server. The query resolver receives this request, processes it, and returns the appropriate data. Query resolvers do not modify data; they only retrieve and return it.
+
+In simple terms, a **GraphQL query** asks a question,
+and a **resolver** is the one who answers it.
+
+**Instructions:**
+
+1. Inside the **Models** folder, create a new file named **GraphQLQuery.cs**.
+2. Add the following code to define the query resolver:
+
+```csharp
+[Models/GraphQLQuery.cs]
+
+using Grid_GraphQLAdaptor.Models;
-///
-/// Represents the GraphQL query resolver for fetching order data.
-///
public class GraphQLQuery
{
///
- /// Retrieves all order data and returns it along with the total record count.
+ /// Query resolver for fetching expense record data with data operations support.
///
- /// The data manager request input containing query parameters.
- /// An instance of containing the order data and count.
- public OrdersDataResponse GetOrdersData(DataManagerRequestInput dataManager)
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
{
- // Retrieve all order records from the data source.
- List dataSource = OrderData.GetAllRecords();
+ // Retrieve all expense records from the data source.
+ List dataSource = ExpenseRecord.GetAllRecords();
+
+ // Apply search, filter, sort, and paging operations as provided by the DataGrid.
+ // Operations are applied sequentially: search → filter → sort → paging.
- // Calculate the total number of records in the data source.
+ // Store the total count before paging.
int totalRecords = dataSource.Count;
- // Return the response containing the total count and the data records.
- return new OrdersDataResponse
+ // Return response with total count and paginated data.
+ return new ExpenseRecordDataResponse
{
Count = totalRecords,
Result = dataSource
@@ -172,1629 +312,2045 @@ public class GraphQLQuery
}
///
-/// Represents the response structure for order data queries.
+/// Response structure for query results. Must include Count (total records) and Result (current page).
///
-public class OrdersDataResponse
+public class ExpenseRecordDataResponse
{
- ///
- /// Gets or sets the total count of records.
- ///
public int Count { get; set; }
-
- ///
- /// Gets or sets the list of order data records.
- ///
- public List Result { get; set; } = new List();
+ public List Result { get; set; } = new List();
}
+```
-{% endhighlight %}
-
-{% highlight cs tabtitle="DataManagerRequest.cs" %}
-
-namespace GraphQLServer.Models
-{
- ///
- /// Represents the input structure for data manager requests.
- ///
- public class DataManagerRequestInput
- {
- [GraphQLName("Skip")]
- public int Skip { get; set; }
+**Details:**
- [GraphQLName("Take")]
- public int Take { get; set; }
+- The `GetExpenseRecordData` method receives `DataManagerRequestInput`, which contains filter, sort, search, and paging parameters from the DataGrid
+- Hot Chocolate automatically converts the method name `GetExpenseRecordData` to camelCase: `expenseRecordData` in the GraphQL schema
+- The response must contain `Count` (total records) and `Result` (current page data) for the DataGrid to process pagination
- [GraphQLName("RequiresCounts")]
- public bool RequiresCounts { get; set; } = false;
+The query resolver has been created successfully.
- [GraphQLName("Params")]
- [GraphQLType(typeof(AnyType))]
- public IDictionary Params { get; set; }
+---
- [GraphQLName("Aggregates")]
- [GraphQLType(typeof(AnyType))]
- public List? Aggregates { get; set; }
+### Step 6: Create the DataManagerRequestInput Class
- [GraphQLName("Search")]
- public List? Search { get; set; }
+A **DataManagerRequestInput** class is a GraphQL input type that represents all the parameters the Syncfusion Blazor DataGrid sends to the backend when requesting data. This class acts as a container for filtering, sorting, searching, paging, and other data operation parameters.
- [GraphQLName("Sorted")]
- public List? Sorted { get; set; }
+**Purpose**
+When the DataGrid performs operations like pagination, sorting, filtering, or searching, it packages all these parameters into a `DataManagerRequestInput` object and sends it to the GraphQL backend. The backend then uses these parameters to fetch and return only the data the grid needs.
- [GraphQLName("Where")]
- [GraphQLType(typeof(AnyType))]
- public List? Where { get; set; }
+**Instructions**:
- [GraphQLName("Group")]
- public List? Group { get; set; }
+1. Inside the **Models** folder, create a new file named **DataManagerRequestInput.cs**.
+2. Define the **DataManagerRequestInput** class and supporting classes with the following code:
- [GraphQLName("antiForgery")]
- public string? antiForgery { get; set; }
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
- [GraphQLName("Table")]
- public string? Table { get; set; }
+///
+/// Represents the input structure for data manager requests from the Syncfusion Blazor DataGrid.
+/// Contains all parameters needed for data operations like filtering, sorting, paging, and searching.
+///
+public class DataManagerRequestInput
+{
+ ///
+ /// Number of records to skip from the beginning (used for pagination).
+ /// Example: Skip=10 means start from the 11th record.
+ ///
+ [GraphQLName("Skip")]
+ public int Skip { get; set; }
- [GraphQLName("IdMapping")]
- public string? IdMapping { get; set; }
+ ///
+ /// Number of records to retrieve (page size).
+ /// Example: Take=10 means retrieve 10 records per page.
+ ///
+ [GraphQLName("Take")]
+ public int Take { get; set; }
- [GraphQLName("Select")]
- public List? Select { get; set; }
+ ///
+ /// Indicates whether the total record count should be calculated.
+ /// Set to true when pagination requires knowing the total number of records.
+ ///
+ [GraphQLName("RequiresCounts")]
+ public bool RequiresCounts { get; set; } = false;
- [GraphQLName("Expand")]
- public List? Expand { get; set; }
+ ///
+ /// Search criteria for finding specific records.
+ /// Contains the search text and which fields to search in.
+ ///
+ [GraphQLName("Search")]
+ public List? Search { get; set; }
- [GraphQLName("Distinct")]
- public List? Distinct { get; set; }
+ // Add other parameters
- [GraphQLName("ServerSideGroup")]
- public bool? ServerSideGroup { get; set; }
+}
- [GraphQLName("LazyLoad")]
- public bool? LazyLoad { get; set; }
+///
+/// Represents an aggregate operation (Sum, Average, Min, Max, Count).
+/// Used to calculate aggregate values on specified fields.
+///
+public class Aggregate
+{
+ ///
+ /// Field name to aggregate (e.g., "Amount", "TotalAmount").
+ ///
+ [GraphQLName("Field")]
+ public string? Field { get; set; }
- [GraphQLName("LazyExpandAllGroup")]
- public bool? LazyExpandAllGroup { get; set; }
- }
+ ///
+ /// Type of aggregation: Sum, Average, Min, Max, Count.
+ ///
+ [GraphQLName("Type")]
+ public string? Type { get; set; }
+}
+///
+/// Represents search criteria for finding records.
+/// Allows searching across multiple fields with specified operators.
+///
+public class SearchFilter
+{
///
- /// Represents an aggregate operation in the data manager request.
+ /// Fields to search in (e.g., ["EmployeeName", "Department"]).
///
- public class Aggregate
- {
- [GraphQLName("Field")]
- public string Field { get; set; }
+ [GraphQLName("Fields")]
+ public List? Fields { get; set; }
- [GraphQLName("Type")]
- public string Type { get; set; }
- }
+ ///
+ /// The search keyword entered by the user.
+ ///
+ [GraphQLName("Key")]
+ public string? Key { get; set; }
///
- /// Represents a search filter in the data manager request.
+ /// Comparison operator (contains, equals, startsWith, etc.).
///
- public class SearchFilter
- {
- [GraphQLName("Fields")]
- public List Fields { get; set; }
+ [GraphQLName("Operator")]
+ public string? Operator { get; set; }
- [GraphQLName("Key")]
- public string Key { get; set; }
+ ///
+ /// Whether the search should ignore case (case-insensitive search).
+ ///
+ [GraphQLName("IgnoreCase")]
+ public bool IgnoreCase { get; set; }
- [GraphQLName("Operator")]
- public string Operator { get; set; }
+ ///
+ /// Indicates whether to ignore accent marks and diacritic characters during search operations
+ ///
+ [GraphQLName("IgnoreAccent")]
+ public bool IgnoreAccent { get; set; }
+}
- [GraphQLName("IgnoreCase")]
- public bool IgnoreCase { get; set; }
- }
+///
+/// Represents sorting instructions for ordering records.
+/// Defines which field to sort by and in which direction.
+///
+public class Sort
+{
+ ///
+ /// Field name to sort by (e.g., "Amount", "EmployeeName").
+ ///
+ [GraphQLName("Name")]
+ public string? Name { get; set; }
///
- /// Represents a sorting operation in the data manager request.
+ /// Sort direction: "Ascending" or "Descending".
///
- public class Sort
- {
- [GraphQLName("Name")]
- public string Name { get; set; }
+ [GraphQLName("Direction")]
+ public string? Direction { get; set; }
- [GraphQLName("Direction")]
- public string Direction { get; set; }
+ // Add other properties
+}
- [GraphQLName("Comparer")]
- [GraphQLType(typeof(AnyType))]
- public object Comparer { get; set; }
- }
+///
+/// Represents a filter condition for narrowing down records.
+/// Supports complex nested conditions (AND/OR logic) for advanced filtering.
+///
+public class WhereFilter
+{
+ ///
+ /// Field name to filter by (e.g., "Department", "Amount").
+ ///
+ [GraphQLName("Field")]
+ public string? Field { get; set; }
///
- /// Represents a filter condition in the data manager request.
+ /// Ignore case sensitivity in comparisons.
///
- public class WhereFilter
- {
- [GraphQLName("Field")]
- public string? Field { get; set; }
+ [GraphQLName("IgnoreCase")]
+ public bool? IgnoreCase { get; set; }
- [GraphQLName("IgnoreCase")]
- public bool? IgnoreCase { get; set; }
+ // Add other properties
+}
- [GraphQLName("IgnoreAccent")]
- public bool? IgnoreAccent { get; set; }
+// Add other classes
- [GraphQLName("IsComplex")]
- public bool? IsComplex { get; set; }
+```
- [GraphQLName("Operator")]
- public string? Operator { get; set; }
+**Understanding the DataManagerRequestInput Class**
- [GraphQLName("Condition")]
- public string? Condition { get; set; }
+**Example Scenario:** A sequence of operations is performed on the DataGrid as follows:
- [GraphQLName("Value")]
- [GraphQLType(typeof(AnyType))]
- public object? Value { get; set; }
+- Searches for **"Finance"** in the Department column.
+- Filters for amounts greater than 1000.
+- Sorts by Amount in descending order.
+- Navigates to page 2 (showing records 11-20).
+- Resulting **DataManagerRequestInput** Parameters:
- [GraphQLName("predicates")]
- public List? predicates { get; set; }
+```json
+{
+ "dataManager": {
+ "Skip": 10,
+ "Take": 10,
+ "RequiresCounts": true,
+ "Search": [
+ {
+ "Fields": ["Department"],
+ "Key": "Finance",
+ "Operator": "contains",
+ "IgnoreCase": true,
+ "IgnoreAccent": true
+ }
+ ],
+ "Where": [
+ {
+ "Condition": "and",
+ "Predicates": [
+ {
+ "Field": "Amount",
+ "Operator": "greaterThan",
+ "Value": 1000,
+ "Predicates": []
+ }
+ ]
+ }
+ ],
+ "Sorted": [
+ {
+ "Name": "Amount",
+ "Direction": "Descending"
+ }
+ ]
}
}
+```
-{% endhighlight %}
-{% endtabs %}
-
-**Step 5: Configure the GraphQL server**
-Update the `Program.cs` file to configure the GraphQL server. This configuration ensures that the server can handle GraphQL requests effectively.
+**DataManagerRequestInput Properties:**
-{% tabs %}
-{% highlight cs tabtitle="Program.cs" %}
+| Property | Purpose | Type | Example |
+|----------|---------|------|---------|
+| `Skip` | Number of records to skip (used for pagination) | `int` | `10` (skip first 10 records) |
+| `Take` | Number of records to retrieve per page | `int` | `20` (fetch next 20 records) |
+| `Search` | Search filter configuration | `List` | Search term and target fields |
+| `Where` | Filter conditions for column filtering | `List` | Field name, operator, and value |
+| `Sorted` | Sort specifications for ordering records | `List` | Field name and direction (asc/desc) |
+| `Group` | Grouping configuration | `List` | Field names to group by |
-var builder = WebApplication.CreateBuilder(args);
+**Key Attributes Explained**
+[GraphQLName]: Maps C# property names to GraphQL schema field names. **Hot Chocolate** automatically converts PascalCase to camelCase.
-// Register GraphQL services.
-builder.Services.AddGraphQLServer()
- .AddQueryType();
+Example: **RequiresCounts → requiresCounts**
+[GraphQLType(typeof(AnyType))]: Allows flexible typing for complex nested structures that can contain various data types.
-var app = builder.Build();
+### Step 7: GraphQL Mutation Resolvers
-// Use routing middleware.
-app.UseRouting();
+A **GraphQL mutation resolver** is a method in the backend that handles write requests (data modifications) from the client. While queries only read data, mutations create, update, or delete records. When the Blazor DataGrid performs add, edit, or delete operations, it sends a GraphQL mutation to the server. The mutation resolver receives this request, processes it, and persists the changes to the data source.
-// Map endpoints.
-app.MapGet("/", () => "Hello, World!");
-app.MapGraphQL(); // Maps the /graphql endpoint by default.
+In simple terms, a **GraphQL mutation** asks for a change, and a **resolver** is the one who makes it.
-app.Run();
+**Instructions:**
+1. Inside the Models folder, create a new file named **GraphQLMutation.cs**.
+2. Define the **GraphQLMutation** class with the following code:
-{% endhighlight %}
-{% endtabs %}
+```csharp
+using Grid_GraphQLAdaptor.Models;
+using HotChocolate.Types;
-**Step 6: Test the GraphQL endpoint**
+namespace Grid_GraphQLAdaptor.Models
+{
+ ///
+ /// GraphQL Mutation class that handles all write operations (Create, Update, Delete).
+ /// Each method is a resolver that processes data modification requests from the DataGrid.
+ ///
+ public class GraphQLMutation
+ {
+ ///
+ /// Mutation resolver for creating a new expense record.
+ /// Called when a user clicks the "Add" button in the DataGrid and submits a new record.
+ ///
+ public ExpenseRecord CreateExpense(
+ ExpenseRecord record,
+ int index,
+ string action,
+ [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ {
+ // Add logic to create new expense record
+ return record;
+ }
-To verify that the GraphQL server is functioning correctly, use the following example query in a GraphQL client or playground:
+ ///
+ /// Mutation resolver for updating an existing expense record.
+ /// Called when a user clicks the "Edit" button, modifies values, and submits the changes.
+ ///
+ public ExpenseRecord UpdateExpense(
+ ExpenseRecord record,
+ string action,
+ string primaryColumnName,
+ string primaryColumnValue,
+ [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ {
+ // Add logic to update existing expense record
+
+ return record;
+ }
-```
-{
- ordersData {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
+ ///
+ /// Mutation resolver for deleting an expense record.
+ /// Called when a user clicks the "Delete" button and confirms the deletion.
+ ///
+ public bool DeleteExpense(
+ string primaryColumnValue,
+ [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ {
+ // Add logic to delete existing expense record
+
+ return true;
}
}
}
```
-This query will return the total count of orders and a list of order details. Ensure the server is running and accessible at `http://localhost:xxxx/graphql` before testing. Here, `xxxx` represents the port number.
+A mutation resolver is a C# method decorated with GraphQL attributes that:
-For more details, refer to the [Hot Chocolate documentation](https://chillicream.com/docs/hotchocolate).
+- **Receives input parameters** from the DataGrid (record data, primary keys, etc.).
+- **Processes the operation** (validation, calculation, data modification).
+- **Persists changes** to the data source (database, file, memory).
+- **Returns results** to the client (modified record or success/failure status).
-## Connecting Syncfusion® Blazor DataGrid to an GraphQL service
-
-To integrate the Syncfusion® Blazor DataGrid into your project using Visual Studio, follow the below steps:
+The GraphQL Mutation class has been successfully created and is ready to handle all data modification operations from the Syncfusion Blazor DataGrid.
-**Step 1: Create a Blazor Web App**
+---
+
+## Integrating Syncfusion Blazor DataGrid
-Create a **Blazor Web App** named **BlazorGrid** using Visual Studio 2022. You can use either [Microsoft Templates](https://learn.microsoft.com/en-us/aspnet/core/blazor/tooling?view=aspnetcore-8.0) or the [Syncfusion® Blazor Extension](https://blazor.syncfusion.com/documentation/visual-studio-integration/template-studio). Ensure you configure the appropriate [interactive render mode](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0#render-modes) and [interactivity location](https://learn.microsoft.com/en-us/aspnet/core/blazor/tooling?view=aspnetcore-8.0&pivots=windows).
+### Step 1: Install and Configure Blazor DataGrid Components with GraphQL
-**Step 2: Install Syncfusion® Blazor DataGrid and Themes NuGet packages**
+Syncfusion is a library that provides pre-built UI components like DataGrid, which is used to display data in a table format.
-To add the Syncfusion® Blazor DataGrid to your app, open the NuGet Package Manager in Visual Studio (*Tools → NuGet Package Manager → Manage NuGet Packages for Solution*). Search for and install the following packages:
+**Instructions:**
-- [Syncfusion.Blazor.Grid](https://www.nuget.org/packages/Syncfusion.Blazor.Grid/)
-- [Syncfusion.Blazor.Themes](https://www.nuget.org/packages/Syncfusion.Blazor.Themes/)
+1. The Syncfusion.Blazor.Grids package was installed in [**Step 1**](#step-1-install-and-configure-blazor-datagrid-components-with-graphql) of the previous heading.
+2. Import the required namespaces in the `Components/_Imports.razor` file:
-If your Blazor Web App uses `WebAssembly` or `Auto` render modes, install these packages in the client project.
+```csharp
+@using Syncfusion.Blazor.Grids
+@using Syncfusion.Blazor.Data
+```
-Alternatively, use the following Package Manager commands:
+3. Add the Syncfusion stylesheet and scripts in the `Components/App.razor` file. Find the `` section and add:
-```powershell
-Install-Package Syncfusion.Blazor.Grid -Version {{ site.releaseversion }}
-Install-Package Syncfusion.Blazor.Themes -Version {{ site.releaseversion }}
+```html
+
+
+
+
+
```
+For this project, the tailwind3 theme is used. A different theme can be selected or the existing theme can be customized based on project requirements. Refer to the [Syncfusion Blazor Components Appearance](https://sfblazor.azurewebsites.net/staging/documentation/appearance/themes) documentation to learn more about theming and customization options.
-> Syncfusion® Blazor components are available on [nuget.org](https://www.nuget.org/packages?q=syncfusion.blazor). Refer to the [NuGet packages](https://blazor.syncfusion.com/documentation/nuget-packages) topic for a complete list of available packages.
+Syncfusion components are now configured and ready to use. For additional guidance, refer to the Grid component’s [getting‑started](https://blazor.syncfusion.com/documentation/datagrid/getting-started-with-web-app) documentation.
-**Step 3: Register Syncfusion® Blazor service**
+### Step 2: Update the Blazor DataGrid
-- Open the **~/_Imports.razor** file and import the required namespaces:
+The `Home.razor` component will display the expense data in a Syncfusion Blazor DataGrid with search, filter, sort, editing and pagination capabilities.
-```cs
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.Data
-```
+**Instructions:**
-- Register the Syncfusion® Blazor service in the **~/Program.cs** file:
+1. Open the file named `Home.razor` in the `Components/Pages` folder.
+2. Add the following code to create a basic DataGrid:
-```csharp
-using Syncfusion.Blazor;
+```cshtml
+@page "/"
+@rendermode InteractiveServer
+
+Expense Tracker System
+
+
+
+
Expense Tracker System
+
Manage and view all expenses from the database.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-builder.Services.AddSyncfusionBlazor();
+@code {
+ // GraphQLAdaptorOptions will be added in the next step
+}
```
-For apps using `WebAssembly` or `Auto (Server and WebAssembly)` render modes, register the service in both **~/Program.cs** files.
+**Component Explanation:**
-**Step 4: Add stylesheet and script resources**
+- **`@rendermode InteractiveServer`**: Enables interactive server-side rendering for the component.
+- **``**: The DataGrid component that displays data in rows and columns.
+- **``**: Defines individual columns in the DataGrid.
+- **``**: Configures pagination with 10 records per page.
+- **``**: Enable editing functionality directly within the Grid by setting the AllowEditing, AllowAdding, and AllowDeleting properties within the GridEditSettings to **true**.
+- **``**: Allows customization of the default dialog editing behavior by defining custom editors for grid rows within a dialog..
-Include the theme stylesheet and script references in the **~/Components/App.razor** file:
+The `SfDataManager` component connects the DataGrid to the GraphQL backend using the adaptor options configured below:
-```html
-
- ...
-
-
-...
-
- ...
-
-
+```cshtml
+
+
```
-> - Refer to the [Blazor Themes](https://blazor.syncfusion.com/documentation/appearance/themes) topic for various methods to include themes (e.g., Static Web Assets, CDN, or CRG).
-> - Set the `rendermode` to **InteractiveServer** or **InteractiveAuto** in your Blazor Web App configuration.
+**Component Attributes Explained:**
-**Step 5: Add Blazor DataGrid and Configure with server**
+| Attribute | Purpose | Value |
+|-----------|---------|-------|
+| `Url` | GraphQL endpoint location | `http://localhost:5272/graphql` (must match backend port) |
+| `GraphQLAdaptorOptions` | References the adaptor configuration object | `@adaptorOptions` (defined in next heading) |
+| `Adaptor` | Specifies adaptor type to use | `Adaptors.GraphQLAdaptor` (tells Syncfusion to use GraphQL adaptor) |
-To bind GraphQL service data to the Grid, provide the GraphQL query string using the [Query](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Query) property of the [GraphQLAdaptorOptions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html). Additionally, set the [ResolverName](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_ResolverName) property to map the response. The GraphQLAdaptor expects the response as a JSON object with properties `Result`, `Count`, and `Aggregates`, which contain the collection of entities, total number of records, and aggregate values, respectively. The GraphQL response should be returned in JSON format like `{ "data": { ... } }` with the query name as the field.
+**Important Notes:**
-{% tabs %}
-{% highlight cs tabtitle="Home.razor" %}
+- The `Url` must match the port configured in `launchSettings.json`.
+- If backend runs on port 5272, then URL must be `https://localhost:5272/graphql`.
+- The `/graphql` path is set by `app.MapGraphQL()` in Program.cs.
-@page "/"
+---
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
+### Step 3: Configure GraphQL Adaptor and Data Binding
+
+The GraphQL adaptor is a bridge that connects the Syncfusion Blazor DataGrid with the GraphQL backend. The adaptor translates DataGrid operations (filtering, sorting, paging, searching) into GraphQL queries and mutations. When the user interacts with the grid, the adaptor automatically sends the appropriate GraphQL request to the backend, receives the response, and updates the grid display.
+
+**What is a GraphQL Adaptor?**
+
+An adaptor is a translator between two different systems. The GraphQL adaptor specifically:
+
+- Receives interaction events generated by the DataGrid, including Add, Edit, Delete actions, as well as sorting and filtering operations.
+- Converts these actions into GraphQL query or mutation syntax.
+- Sends the **GraphQL request** to the backend **GraphQL endpoint**.
+- Receives the response data from the backend.
+- Formats the response back into a structure the DataGrid understands.
+- Updates the grid display with the new data.
+
+The adaptor enables bidirectional communication between the frontend (DataGrid) and backend (GraphQL server).
+
+---
+
+**GraphQL Adaptor Configuration**
+
+The `@code` block in `Home.razor` contains C# code that configures how the adaptor behaves. This configuration is critical because it defines:
+
+- Which GraphQL query to use for reading data.
+- Which GraphQL mutations to use for creating, updating, and deleting data.
+- How to connect to the GraphQL backend endpoint.
+**Instructions:**
+
+1. Open the `Home.razor` file located at `Components/Pages/Home.razor`.
+2. Scroll to the `@code` block at the bottom of the file.
+3. Add the following complete configuration:
+
+```csharp
@code {
+ ///
+ /// GraphQLAdaptorOptions configures how the DataGrid communicates with the GraphQL backend.
+ /// This object contains the query, mutation operations, and endpoint URL.
+ ///
private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
+ {
+ Query = @"query expenseRecordData($dataManager: DataManagerRequestInput!) {
+ expenseRecordData(dataManager: $dataManager) {
count
result {
- orderID
- customerID
- shipCity
- shipCountry
+ expenseId
+ employeeName
+ employeeEmail
+ employeeAvatarUrl
+ department
+ category
+ description
+ amount
+ taxPct
+ totalAmount
+ expenseDate
+ paymentMethod
+ currency
+ reimbursementStatus
+ isPolicyCompliant
+ tags
}
}
}",
- ResolverName = "ordersData"
- };
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
-
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
+ ResolverName = "expenseRecordData",
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
+ Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
+ {
+ Insert = @"mutation create($record: ExpenseRecordInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
+ createExpense(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ // Add Other fields
+ }
+ }",
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
-}
+ Update = @"mutation update($record: ExpenseRecordInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
+ updateExpense(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ // Add Other fields
+ }
+ }",
-{% endhighlight %}
-{% endtabs %}
+ Delete = @"mutation delete($primaryColumnValue: String!, $additionalParameters: Any) {
+ deleteExpense(primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters)
+ }",
-> Replace `https://localhost:xxxx/graphql` with the actual URL of your API endpoint that provides the data in a consumable format (e.g., JSON).
+ Batch = @"mutation batch($changed: [ExpenseRecordInput!], $added: [ExpenseRecordInput!], $deleted: [ExpenseRecordInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) {
+ batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) {
+ expenseId
+ employeeName
+ // Add Other fields
+ }
+ }"
+ },
+ };
+}
+```
-**Step 6: Enable CORS Policy**
+**GraphQL Query Structure Explained in Detail**
-To allow your Blazor application to access the GraphQL server, you need to enable Cross-Origin Resource Sharing (CORS) in your server application. Add the following code to your `Program.cs` file:
+The Query property is critical for understanding how data flows. Let's break down each component:
-```csharp
-// Add CORS policy
-builder.Services.AddCors(options =>
-{
- options.AddPolicy("AllowSpecificOrigin", policy =>
- {
- // Replace with your Blazor app's URL.
- policy.WithOrigins("https://localhost:xxxx/")
- .AllowAnyHeader()
- .AllowAnyMethod()
- .AllowCredentials();
- });
-});
-
-// Use CORS.
-app.UseCors("AllowSpecificOrigin");
+```graphql
+query expenseRecordData($dataManager: DataManagerRequestInput!) {}
```
-This configuration ensures that your Blazor application can communicate with the GraphQL server without encountering CORS-related issues.
+**Line Breakdown:**
+- `query` - GraphQL keyword indicating a read operation
+- `expenseRecordData` - Name of the query (must match resolver name with camelCase)
+- `($dataManager: DataManagerRequestInput!)` - Parameter declaration
+ - `$dataManager` - Variable name (referenced as $dataManager throughout the query)
+ - `: DataManagerRequestInput!` - Type specification
+ - `!` - Exclamation mark means this parameter is **required** (not optional)
-**Step 7: Run the Application**
+```graphql
+expenseRecordData(dataManager: $dataManager) {}
+```
-After completing the setup, run your application. The Grid will fetch and display data from the configured GraphQL API. Ensure that both the Blazor application and the GraphQL server are running and accessible.
-
-
+**Line Breakdown:**
+- `expenseRecordData(...)` - Calls the resolver method in backend
+- `dataManager: $dataManager` - Passes the $dataManager variable to the resolver
+- The resolver receives this object and uses it to apply filters, sorts, searches, and pagination
-**Understanding DataManagerRequestInput Class**
+```graphql
+count
+result {
+ expenseId
+ employeeName
+ ...
+}
+```
+- `count` - Returns total number of records (used for pagination)
+ - Example: If 500 total expense records exist, count = 500
+ - DataGrid uses this to calculate how many pages exist
+- `result` - Contains the array of expense records
+ - `{ ... }` - List of fields to return for each record
+ - Each field must exist in the ExpenseRecord class
+ - Only requested fields are returned (no over-fetching)
-Before you dive into specific data operations like search, sorting, or filtering, it's essential to understand the request structure that the Syncfusion® Blazor DataGrid sends to the GraphQL server.
+---
-The following code demonstrates the `DataManagerRequestInput` class, which encapsulates parameters such as pagination (Skip, Take), search filters (Search), sorting (Sorted), and more. These parameters are passed as arguments to the resolver function for processing.
+**Response Structure Example**
-{% tabs %}
-{% highlight cs tabtitle="DataManagerRequest.cs" %}
+When the backend executes the query, it returns a **JSON response** in this exact structure:
-namespace GraphQLServer.Models
+```json
{
- ///
- /// Represents the input structure for data manager requests.
- ///
- public class DataManagerRequestInput
- {
- [GraphQLName("Skip")]
- public int Skip { get; set; }
+ "data": {
+ "expenseRecordData": {
+ "count": 2,
+ "result": [
+ {
+ "expenseId": "EXP001",
+ "employeeName": "John Smith",
+ "employeeEmail": "john.smith@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/john.png",
+ "department": "Sales",
+ "category": "Travel & Mileage",
+ "description": "Flight to client meeting in New York",
+ "amount": 450.00,
+ "taxPct": 0.10,
+ "totalAmount": 495.00,
+ "expenseDate": "2024-01-15",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Approved",
+ "isPolicyCompliant": true,
+ "tags": ["Client-Billable", "Travel"]
+ },
+ {
+ "expenseId": "EXP002",
+ "employeeName": "Sarah Johnson",
+ "employeeEmail": "sarah.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/sarah.png",
+ "department": "Engineering",
+ "category": "Office Supplies",
+ "description": "Laptop peripherals and desk accessories",
+ "amount": 125.50,
+ "taxPct": 0.05,
+ "totalAmount": 131.78,
+ "expenseDate": "2024-01-14",
+ "paymentMethod": "Personal Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Under Review",
+ "isPolicyCompliant": true,
+ "tags": ["Office", "Equipment"]
+ }
+ ]
+ }
+ }
+}
+```
- [GraphQLName("Take")]
- public int Take { get; set; }
+**Response Structure Explanation:**
- [GraphQLName("RequiresCounts")]
- public bool RequiresCounts { get; set; } = false;
+| Part | Purpose | Example |
+|------|---------|---------|
+| `data` | Root object containing the query result | Always present in successful response |
+| `expenseRecordData` | Matches the query name (camelCase) | Contains count and result |
+| `count` | Total number of records available | 2 (in this example) |
+| `result` | Array of ExpenseRecord objects | [ {...}, {...} ] |
+| Each field in result | Matches GraphQL query field names | Field values from database |
- [GraphQLName("Params")]
- [GraphQLType(typeof(AnyType))]
- public IDictionary Params { get; set; }
+---
- [GraphQLName("Aggregates")]
- [GraphQLType(typeof(AnyType))]
- public List? Aggregates { get; set; }
+### Step 4: Add Toolbar with CRUD and search options
- [GraphQLName("Search")]
- public List? Search { get; set; }
+The toolbar provides buttons for adding, editing, deleting records, and searching the data.
- [GraphQLName("Sorted")]
- public List? Sorted { get; set; }
+**Instructions:**
- [GraphQLName("Where")]
- [GraphQLType(typeof(AnyType))]
- public List? Where { get; set; }
+1. Open the `Components/Pages/Home.razor` file.
+2. Update the `` component to include the [Toolbar](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_Toolbar) property with CRUD and search options:
- [GraphQLName("Group")]
- public List? Group { get; set; }
+```cshtml
+
+
+
+
+
+```
- [GraphQLName("antiForgery")]
- public string? antiForgery { get; set; }
+3. Add the toolbar items list in the `@code` block:
- [GraphQLName("Table")]
- public string? Table { get; set; }
+```csharp
+@code {
+ private List ToolbarItems = new List { "Add", "Edit", "Delete", "Update", "Cancel", "Search"};
+}
+```
- [GraphQLName("IdMapping")]
- public string? IdMapping { get; set; }
+### Step 5: Implement Paging Feature
- [GraphQLName("Select")]
- public List? Select { get; set; }
+Paging divides large datasets into smaller pages to improve performance and usability.
- [GraphQLName("Expand")]
- public List? Expand { get; set; }
+**Instructions:**
- [GraphQLName("Distinct")]
- public List? Distinct { get; set; }
+1. The paging feature is already partially enabled in the `` component with [AllowPaging="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_AllowPaging).
+2. The page size is configured with [GridPageSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridPageSettings.html).
+3. No additional code changes are required from the previous steps.
- [GraphQLName("ServerSideGroup")]
- public bool? ServerSideGroup { get; set; }
- [GraphQLName("LazyLoad")]
- public bool? LazyLoad { get; set; }
+```cshtml
+
+
+
+
+
+```
- [GraphQLName("LazyExpandAllGroup")]
- public bool? LazyExpandAllGroup { get; set; }
- }
+4. Update the `GetExpenseRecordData` method in the `GraphQLQuery` class to handle paging:
- ///
- /// Represents an aggregate operation in the data manager request.
- ///
- public class Aggregate
- {
- [GraphQLName("Field")]
- public string Field { get; set; }
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
- [GraphQLName("Type")]
- public string Type { get; set; }
- }
-
- ///
- /// Represents a search filter in the data manager request.
- ///
- public class SearchFilter
+public class GraphQLQuery
+{
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
{
- [GraphQLName("Fields")]
- public List Fields { get; set; }
-
- [GraphQLName("Key")]
- public string Key { get; set; }
-
- [GraphQLName("Operator")]
- public string Operator { get; set; }
+ List dataSource = ExpenseRecord.GetAllRecords();
- [GraphQLName("IgnoreCase")]
- public bool IgnoreCase { get; set; }
- }
-
- ///
- /// Represents a sorting operation in the data manager request.
- ///
- public class Sort
- {
- [GraphQLName("Name")]
- public string Name { get; set; }
+ int totalRecords = dataSource.Count;
- [GraphQLName("Direction")]
- public string Direction { get; set; }
+ if (dataManager.Skip != 0)
+ {
+ dataSource = dataSource.Skip(dataManager.Skip).ToList();
+ }
+ if (dataManager.Take != 0)
+ {
+ dataSource = dataSource.Take(dataManager.Take).ToList();
+ }
- [GraphQLName("Comparer")]
- [GraphQLType(typeof(AnyType))]
- public object Comparer { get; set; }
+ return new ExpenseRecordDataResponse
+ {
+ Count = totalRecords,
+ Result = dataSource
+ };
}
+}
+```
- ///
- /// Represents a filter condition in the data manager request.
- ///
- public class WhereFilter
- {
- [GraphQLName("Field")]
- public string? Field { get; set; }
-
- [GraphQLName("IgnoreCase")]
- public bool? IgnoreCase { get; set; }
+Fetches expenses data by calling the **GetAllRecords** method, which is implemented in the **ExpenseRecord.cs** file.
- [GraphQLName("IgnoreAccent")]
- public bool? IgnoreAccent { get; set; }
+```csharp
+private static List _expenseStore = null;
+public static List GetAllRecords()
+{
+ // Add code to return a list of "ExpenseRecord" to process it further.
+ return _expenseStore;
+}
- [GraphQLName("IsComplex")]
- public bool? IsComplex { get; set; }
+```
- [GraphQLName("Operator")]
- public string? Operator { get; set; }
- [GraphQLName("Condition")]
- public string? Condition { get; set; }
+| Part | Purpose |
+|------|---------|
+| `dataManager.Skip` | Number of records to skip from the start (used to jump to the correct page) |
+| `dataManager.Take` | Number of records to return for the current page (page size) |
+| `dataManager.RequiresCounts` | Indicates whether the server should also return the total record count |
- [GraphQLName("Value")]
- [GraphQLType(typeof(AnyType))]
- public object? Value { get; set; }
+**How Paging Variables are Passed:**
- [GraphQLName("predicates")]
- public List? predicates { get; set; }
- }
+When the grid requests a specific page, it automatically sends:
+```json
+{
+ "dataManager": {
+ "Skip": 10,
+ "Take": 10,
+ "RequiresCounts": true
+ }
}
+```
+The backend resolver applies **Skip** and **Take**, then returns `count` and the paged `result`. Paging feature is now active with 10 records per page.
-{% endhighlight %}
-{% endtabs %}
+---
-## Handling searching operation
-To handle search operations in the Syncfusion® Blazor DataGrid using the GraphQLAdaptor, you can make use of the `dataManager.Search` parameters and apply the search logic on the server side. This allows users to efficiently filter and retrieve relevant records from the Grid based on the provided search criteria.
+### Step 6: Implement Searching feature
-When a search is performed in the Grid, the DataManager sends the search parameters to the server, which include the search keyword and the list of fields to search against. The server then processes these parameters and filters the data accordingly.
+Searching provides the capability to find specific records by entering keywords into the search box.
-
+**Instructions:**
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
+1. Ensure the toolbar includes the "Search" item.
-@page "/"
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
+```cshtml
+
+
+
+
+```
-@code {
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- ResolverName = "ordersData"
- };
-
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
-
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
-
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
-
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
-}
-
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
+2. Update the `GetExpenseRecordData` method in the `GraphQLQuery` class to handle searching:
-using GraphQLServer.Models;
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-// Defines the GraphQL resolver for handling Grid data requests.
public class GraphQLQuery
{
- // Returns order data based on the DataManager request from the Grid.
- public OrdersDataResponse GetOrdersData(DataManagerRequestInput dataManager)
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
{
- List dataSource = OrderData.GetAllRecords();
+ List dataSource = ExpenseRecord.GetAllRecords();
- // Apply search if search filters are provided.
if (dataManager.Search != null && dataManager.Search.Count > 0)
{
foreach (var searchFilter in dataManager.Search)
{
- // Filter the data based on search key and fields.
- dataSource = dataSource.Where(order =>
+ dataSource = dataSource.Where(expense =>
searchFilter.Fields.Any(field =>
- order.GetType().GetProperty(field)?.GetValue(order)?.ToString()
- .IndexOf(searchFilter.Key, StringComparison.OrdinalIgnoreCase) >= 0
+ expense.GetType().GetProperty(field)?.GetValue(expense)?.ToString().IndexOf(searchFilter.Key, StringComparison.OrdinalIgnoreCase) >= 0
)
).ToList();
}
}
- // Get total record count after filtering.
int totalRecords = dataSource.Count;
- // Return the filtered data and count.
- return new OrdersDataResponse
+ if (dataManager.Skip != 0)
+ {
+ dataSource = dataSource.Skip(dataManager.Skip).ToList();
+ }
+ if (dataManager.Take != 0)
+ {
+ dataSource = dataSource.Take(dataManager.Take).ToList();
+ }
+
+ return new ExpenseRecordDataResponse
{
Count = totalRecords,
Result = dataSource
};
}
}
+```
-// Defines the response structure with data and count.
-public class OrdersDataResponse
-{
- public int Count { get; set; }
- public List Result { get; set; } = new List();
-}
-
-{% endhighlight %}
-{% endtabs %}
-
-## Handling filtering operation
-
-To handle filtering operations in the Syncfusion® Blazor DataGrid using the GraphQLAdaptor, you can make use of the `dataManager.Where` parameters and apply the filter logic on the server side. This enables users to refine the Grid data by specifying one or more filter conditions based on column values.
-
-When a filter is applied in the Grid, the DataManager sends the filtering criteria to the server through the `Where` property. Each filter condition includes the target field, operator, filter value, and other optional settings such as case sensitivity or nested predicates.
-
-On the server, these parameters are parsed and used to filter the data source accordingly before returning the results to the Grid.
-
-
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-
-@page "/"
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
-
+**How Search Variables are Passed:**
-@code {
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+When search text is entered, the DataGrid automatically sends:
+```json
+{
+ "dataManager": {
+ "Search": [
{
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- ResolverName = "ordersData"
- };
+ "Fields": ["EmployeeName", "Department", "Category", "Description"],
+ "Key": "finance",
+ "Operator": "contains",
+ "IgnoreCase": true,
+ "IgnoreAccent": true
+ }
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
+ }
+ }
+```
+The backend resolver receives this and processes the search filter in the `GetExpenseRecordData` method. Searching feature is now active.
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
+---
+### Step 7: Implement Sorting feature
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
+Sorting enables organizing records by selecting column headers, arranging the data in ascending or descending order.
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
+**Instructions:**
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
-}
+1. Ensure the `` component has [AllowSorting="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_AllowSorting).
-{% endhighlight %}
+```cshtml
+
+
+
+
+
+```
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
+2. Update the `GetExpenseRecordData` method in the `GraphQLQuery` class to handle sorting:
-using GraphQLServer.Models;
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-// Defines the GraphQL resolver for handling Grid requests.
public class GraphQLQuery
{
- // Returns order data based on the DataManager request received from the Grid.
- public OrdersDataResponse GetOrdersData(DataManagerRequestInput dataManager)
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
{
- List dataSource = OrderData.GetAllRecords();
+ List dataSource = ExpenseRecord.GetAllRecords();
- // Apply filtering if filter conditions are provided.
- if (dataManager.Where != null && dataManager.Where.Count > 0)
+ if (dataManager.Sorted != null && dataManager.Sorted.Count > 0)
{
- foreach (var filter in dataManager.Where)
+ foreach (var sort in dataManager.Sorted)
{
- dataSource = dataSource.Where(order =>
+ var property = typeof(ExpenseRecord).GetProperty(sort.Name);
+ if (property != null)
{
- bool match = true;
- foreach (var predicate in filter.predicates)
- {
- string fieldName = predicate.Field;
- object fieldValue = predicate.Value;
- string operation = predicate.Operator.ToLower();
-
- var property = order.GetType().GetProperty(fieldName);
- if (property == null) return false;
-
- var propertyValue = property.GetValue(order);
- if (propertyValue == null) return false;
-
- // Apply filter logic based on the operator type.
- switch (operation)
- {
- case "equal":
- match &= propertyValue.ToString().Equals(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
- break;
- case "notequal":
- match &= !propertyValue.ToString().Equals(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
- break;
- case "contains":
- match &= propertyValue.ToString().IndexOf(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
- break;
- case "startswith":
- match &= propertyValue.ToString().StartsWith(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
- break;
- case "endswith":
- match &= propertyValue.ToString().EndsWith(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
- break;
- case "greaterthan":
- match &= Convert.ToDouble(propertyValue) > Convert.ToDouble(fieldValue);
- break;
- case "lessthan":
- match &= Convert.ToDouble(propertyValue) < Convert.ToDouble(fieldValue);
- break;
- case "greaterthanequal":
- match &= Convert.ToDouble(propertyValue) >= Convert.ToDouble(fieldValue);
- break;
- case "lessthanequal":
- match &= Convert.ToDouble(propertyValue) <= Convert.ToDouble(fieldValue);
- break;
- default:
- return false;
- }
- }
- return match;
- }).ToList();
+ dataSource = sort.Direction?.ToLower() == "descending"
+ ? dataSource.OrderByDescending(x => property.GetValue(x)).ToList()
+ : dataSource.OrderBy(x => property.GetValue(x)).ToList();
+ }
}
}
- // Get total record count after applying filtering.
int totalRecords = dataSource.Count;
- // Return the filtered result along with total count.
- return new OrdersDataResponse
+ if (dataManager.Skip != 0)
+ {
+ dataSource = dataSource.Skip(dataManager.Skip).ToList();
+ }
+ if (dataManager.Take != 0)
+ {
+ dataSource = dataSource.Take(dataManager.Take).ToList();
+ }
+
+ return new ExpenseRecordDataResponse
{
Count = totalRecords,
Result = dataSource
};
}
}
+```
-// Defines the structure for returning filtered data and total record count.
-public class OrdersDataResponse
-{
- public int Count { get; set; }
- public List Result { get; set; } = new List();
-}
-
-{% endhighlight %}
-{% endtabs %}
-
-## Handling sorting operation
-
-To handle sorting operations in the Syncfusion® Blazor DataGrid using the GraphQLAdaptor, the sorting logic can be implemented on the server side by utilizing the `dataManager.Sorted` parameter. This enables the Grid to send sorting instructions to the server, specifying the fields and sort directions to apply.
-
-When a sort action is triggered in the Grid, the DataManager sends the sorting configuration in the `Sorted` property. This includes the field name to sort and the direction (Ascending or Descending). The server processes this parameter and sorts the data accordingly before returning it to the Grid.
-
-
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-
-@page "/"
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
-
+**How Sort Variables are Passed:**
-@code {
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+When a column header is selected for sorting, the DataGrid automatically sends:
+```json
+{
+ "dataManager": {
+ "Sorted": [
{
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- ResolverName = "ordersData"
- };
-
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
-
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
-
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
-
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
+ "Name": "Amount",
+ "Direction": "Descending"
+ }
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
}
}
+```
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
-
-using GraphQLServer.Models;
+The backend resolver receives this and processes the sort specification in the `GetExpenseRecordData` method. Multiple sorting conditions can be applied sequentially by holding the **Ctrl** key and selecting additional column headers. Sorting feature is now active.
-// Defines the GraphQL resolver for handling Grid requests.
-public class GraphQLQuery
-{
- // Returns order data based on the DataManager request received from the Grid.
- public OrdersDataResponse GetOrdersData(DataManagerRequestInput dataManager)
- {
- // Retrieve all records from the data source.
- List dataSource = OrderData.GetAllRecords();
+---
- // Apply sorting if sorting criteria are provided.
- if (dataManager.Sorted != null && dataManager.Sorted.Count > 0)
- {
- foreach (var sort in dataManager.Sorted)
+ ### Step 8: Implement Filtering feature
+
+ Filtering enables narrowing down records by applying conditions to column values. Filtering can be performed by selecting checkbox-based filters or by using comparison operators such as equals, greater than, less than, and other supported operators.
+
+ **Instructions:**
+
+ 1. Ensure the ```` component has [AllowFiltering="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_AllowFiltering).
+
+ ```cshtml
+
+
+
+
+
+
+ ```
+
+ 2. Update the ``GetExpenseRecordData`` method in the ``GraphQLQuery`` class to handle filtering:
+
+ ```csharp
+ namespace Grid_GraphQLAdaptor.Models;
+
+ public class GraphQLQuery
+ {
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
+ {
+ List dataSource = ExpenseRecord.GetAllRecords();
+
+ if (dataManager.Where != null && dataManager.Where.Count > 0)
+ {
+ foreach (var filter in dataManager.Where)
+ {
+ dataSource = dataSource.Where(expense => EvaluateFilterGroup(expense, filter.Predicates)).ToList();
+ }
+ }
+
+ int totalRecords = dataSource.Count;
+
+ if (dataManager.Skip != 0)
+ {
+ dataSource = dataSource.Skip(dataManager.Skip).ToList();
+ }
+ if (dataManager.Take != 0)
+ {
+ dataSource = dataSource.Take(dataManager.Take).ToList();
+ }
+
+ return new ExpenseRecordDataResponse
+ {
+ Count = totalRecords,
+ Result = dataSource
+ };
+ }
+
+ private bool EvaluateFilterGroup(ExpenseRecord expense, List predicates)
+ {
+ bool match = true;
+
+ foreach (var predicate in predicates)
+ {
+ if (predicate.Predicates != null && predicate.Predicates.Count > 0)
+ {
+ bool nestedMatch = false;
+ foreach (var nestedPredicate in predicate.Predicates)
+ {
+ nestedMatch |= EvaluatePredicate(expense, nestedPredicate);
+ }
+ match &= nestedMatch;
+ }
+ else
+ {
+ match &= EvaluateSinglePredicate(expense, predicate);
+ }
+ }
+
+ return match;
+ }
+
+ private bool EvaluatePredicate(ExpenseRecord expense, WhereFilter predicate)
+ {
+ if (predicate.Predicates != null && predicate.Predicates.Count > 0)
+ {
+ bool nestedMatch = false;
+ foreach (var nestedPredicate in predicate.Predicates)
+ {
+ nestedMatch |= EvaluatePredicate(expense, nestedPredicate);
+ }
+ return nestedMatch;
+ }
+ else
+ {
+ return EvaluateSinglePredicate(expense, predicate);
+ }
+ }
+
+ private bool EvaluateSinglePredicate(ExpenseRecord expense, WhereFilter predicate)
+ {
+ if (string.IsNullOrEmpty(predicate.Field) || string.IsNullOrEmpty(predicate.Operator))
+ return false;
+
+ string fieldName = predicate.Field;
+ object fieldValue = predicate.Value;
+ string operation = predicate.Operator.ToLower();
+
+ var property = expense.GetType().GetProperty(fieldName);
+ if (property == null) return false;
+
+ var propertyValue = property.GetValue(expense);
+ if (propertyValue == null) return false;
+
+ switch (operation)
+ {
+ case "equal":
+ return propertyValue.ToString().Equals(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
+ case "notequal":
+ return !propertyValue.ToString().Equals(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
+ case "contains":
+ return propertyValue.ToString().IndexOf(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
+ case "startswith":
+ return propertyValue.ToString().StartsWith(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
+ case "endswith":
+ return propertyValue.ToString().EndsWith(fieldValue.ToString(), StringComparison.OrdinalIgnoreCase);
+ case "greaterthan":
+ return Convert.ToDouble(propertyValue) > Convert.ToDouble(fieldValue);
+ case "lessthan":
+ return Convert.ToDouble(propertyValue) < Convert.ToDouble(fieldValue);
+ case "greaterthanorequal":
+ return Convert.ToDouble(propertyValue) >= Convert.ToDouble(fieldValue);
+ case "lessthanorequal":
+ return Convert.ToDouble(propertyValue) <= Convert.ToDouble(fieldValue);
+ default:
+ return false;
+ }
+ }
+ }
+ ```
+
+ **Supported Filter Operators:**
+
+ | Operator | Purpose | Example |
+ |----------|---------|---------|
+ | ``equal`` | Exact match | Amount equals 500 |
+ | ``notequal`` | Not equal to value | Status not equal to "Rejected" |
+ | ``contains`` | Contains substring (case-insensitive) | Description contains "travel" |
+ | ``startswith`` | Starts with value | EmployeeName starts with "John" |
+ | ``endswith`` | Ends with value | Category ends with "Supplies" |
+ | ``greaterthan`` | Greater than numeric value | Amount > 1000 |
+ | ``lessthan`` | Less than numeric value | TaxPct < 0.15 |
+ | ``greaterthanorequal`` | Greater than or equal | Amount >= 500 |
+ | ``lessthanorequal`` | Less than or equal | TaxPct <= 0.10 |
+
+ **How Filter Variables are Passed:**
+
+ When filter conditions are applied, the DataGrid automatically sends:
+ ```json
+ {
+ "dataManager": {
+ "Where": [
+ {
+ "Condition": "and",
+ "Predicates":[
{
- dataSource = sort.Direction.ToLower() == "ascending"
- ? dataSource.OrderBy(order => order.GetType().GetProperty(sort.Name)?.GetValue(order)).ToList()
- : dataSource.OrderByDescending(order => order.GetType().GetProperty(sort.Name)?.GetValue(order)).ToList();
+ "Operator":"or",
+ "Predicates": [
+ {
+ "Field": "Department",
+ "Value": "Finance",
+ "Operator": "equal"
+ },
+ {
+ "Field": "Department",
+ "Value": "HR & People",
+ "Operator": "equal"
+ }
+ ]
}
- }
-
- // Get total record count after applying sorting.
- int totalRecords = dataSource.Count;
-
- // Return the sorted data along with the total record count.
- return new OrdersDataResponse
- {
- Count = totalRecords,
- Result = dataSource
- };
+ ]
+ }
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
}
}
+ ```
-// Defines the structure for returning processed data and its total count.
-public class OrdersDataResponse
-{
- public int Count { get; set; }
- public List Result { get; set; } = new List();
-}
+ **Filter Logic with Multiple Checkbox Selections:**
-{% endhighlight %}
-{% endtabs %}
+ When multiple checkboxes are selected for the same column (for example, Department = "Finance" OR "HR & People"), the nested predicates structure processes these selections using **OR** logic:
-## Handling paging operation
+ - Top-level predicates are combined with **AND** logic.
+ - Nested predicates within a field are combined with **OR** logic.
+ - This allows expressions like: (Department = "Finance" OR "HR & People").
-To handle paging operations in the Syncfusion® Blazor DataGrid using the GraphQLAdaptor, you can make use of the `dataManager.Skip` and `dataManager.Take` parameters. These parameters allow you to retrieve data in pages, helping to manage large datasets efficiently by loading only a subset of records at a time.
+ The backend resolver receives this and processes the filter conditions in the `GetExpenseRecordData` method using recursive evaluation to handle any depth of nesting. Filtering feature is now active.
-When paging is applied, the DataManager sends the **Skip** and **Take** values to the server. The **Skip** parameter specifies the number of records to be skipped, while the **Take** parameter defines how many records to retrieve in the current page.
+ ---
+### Step 9: Implement Grouping feature
-On the server side, the data is sliced based on the **Skip** and **Take** values, and the total record count is returned to enable proper pagination in the Grid.
+Grouping enables organizing and displaying records based on column values. This feature arranges data into expandable and collapsible group hierarchies, improving the ability to analyze and interpret data patterns.
-
+**Instructions:**
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
+1. Ensure the `` component has [AllowGrouping="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_AllowGrouping).
-@page "/"
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
+```cshtml
+
+
+
+
+ @{
+ var groupData = context as CaptionTemplateContext;
+
+ @groupData.HeaderText - @groupData.Key : @groupData.Count item@(groupData.Count == 1 ? "" : "s")
+
+ }
+
+
+
+
+```
-@code {
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- ResolverName = "ordersData"
- };
-
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
-
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
-
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
+**Group Caption Customization:**
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
-}
+The `` in the grid allows customization of group header text. Access group metadata using `CaptionTemplateContext`:
-{% endhighlight %}
+| Property | Purpose | Example |
+|----------|---------|---------|
+| `groupData.HeaderText` | Display text for the group field | "Department" |
+| `groupData.Key` | The group value/key | "Finance" |
+| `groupData.Count` | Number of records in the group | 5 |
+| `groupData.Field` | The field name being grouped | "Department" |
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
+2. Update the `GetExpenseRecordData` method in the `GraphQLQuery` class to handle grouping:
-using GraphQLServer.Models;
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-// Defines the GraphQL resolver for handling Grid requests.
public class GraphQLQuery
{
- // Returns order data based on the DataManager request received from the Grid.
- public OrdersDataResponse GetOrdersData(DataManagerRequestInput dataManager)
+ public ExpenseRecordDataResponse GetExpenseRecordData(DataManagerRequestInput dataManager)
{
- // Retrieve all records from the data source.
- List dataSource = OrderData.GetAllRecords();
+ List dataSource = ExpenseRecord.GetAllRecords();
+
+ if (dataManager.Group != null && dataManager.Group.Count > 0)
+ {
+ dataSource = ApplyGrouping(dataSource, dataManager.Group);
+ }
- // Store the total number of records before applying pagination.
int totalRecords = dataSource.Count;
- // Apply paging by skipping the specified number of records and taking the required count.
- dataSource = dataSource.Skip(dataManager.Skip).Take(dataManager.Take).ToList();
+ if (dataManager.Skip != 0)
+ {
+ dataSource = dataSource.Skip(dataManager.Skip).ToList();
+ }
+ if (dataManager.Take != 0)
+ {
+ dataSource = dataSource.Take(dataManager.Take).ToList();
+ }
- // Return the paged data along with the total record count.
- return new OrdersDataResponse
+ return new ExpenseRecordDataResponse
{
Count = totalRecords,
Result = dataSource
};
}
+
+ private List ApplyGrouping(List dataSource, List groupFields)
+ {
+ if (groupFields == null || groupFields.Count == 0)
+ return dataSource;
+
+ var sortedData = dataSource;
+ foreach (var groupField in groupFields)
+ {
+ var property = typeof(ExpenseRecord).GetProperty(groupField);
+ if (property != null)
+ {
+ sortedData = sortedData.OrderBy(x => property.GetValue(x) ?? string.Empty).ToList();
+ }
+ }
+
+ return sortedData;
+ }
}
+```
+
+**How Group Variables are Passed:**
-// Defines the structure for returning processed data and its total count.
-public class OrdersDataResponse
+When a column header is dragged into the grouping area, the DataGrid automatically sends:
+```json
{
- public int Count { get; set; }
- public List Result { get; set; } = new List();
+ "dataManager": {
+ "Group": [
+ "Department",
+ "ReimbursementStatus"
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
+ }
}
+```
-{% endhighlight %}
-{% endtabs %}
-
-## Handling CRUD operation using mutation
+**Backend Processing Order:**
-The Syncfusion® Blazor DataGrid integrates seamlessly with GraphQL APIs using the GraphQLAdaptor, enabling support for CRUD (Create, Read, Update, and Delete) and Batch operations. This adaptor maps Grid actions to GraphQL queries and mutations for real-time data interaction.
+When multiple data operations are combined with grouping:
-This section demonstrates how to configure the Grid with actual code to bind data and perform CRUD actions using the GraphQLAdaptor.
+1. **Search** - Filter records by search keywords
+2. **Filter** - Apply column filters
+3. **Sort** - Apply additional sorting within groups
+4. **Group** - Sort records by group fields
+5. **Paging** - Slice results for current page
-**Set Up Mutation Queries**
+This sequence ensures optimal performance and predictable results.
-Define GraphQL mutation queries for Insert, Update, Delete, and Batch operations in the [GraphQLAdaptorOptions.Mutation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Mutation) property. Below are the required queries for each operation:
+The backend resolver receives the group specifications in the `GetExpenseRecordData` method and sorts the data accordingly, enabling the DataGrid to display properly grouped records. Grouping feature is now active.
-* **Insert Mutation:** A GraphQL mutation that allows adding new records.
+---
-* **Update Mutation:** A GraphQL mutation for updating existing records.
+### Perform CRUD Operations
+
+ CRUD operations (Create, Read, Update, Delete) provide complete data‑management capabilities within the DataGrid. The DataGrid offers built‑in dialogs and action buttons to perform these operations, while backend resolvers execute the corresponding data modifications.
-* **Delete Mutation:** A GraphQL mutation that removes records.
+ Add the Grid `GridEditSettings` and `Toolbar` configuration to enable create, read, update, and delete (CRUD) operations.
+
+ ```cshtml
+
+
+
+
+
+
+ ```
+
+Add the toolbar items list in the `@code` block:
-* **Batch Mutation:** Handles multiple operations (Insert, Update, and Delete) in a single request.
+```csharp
+@code {
+ private List ToolbarItems = new List { "Add", "Edit", "Delete", "Update", "Cancel", "Search"};
-**Configuration in GraphQL server application**
+ // GraphQLAdaptorOptions code...
+}
+```
-The following code is the configuration in GraphQL server application to set GraphQL query and mutation type and to enable CORS.
+**Insert**
+
+ The Insert operation enables adding new expense records to the system. When the Add button in the toolbar is selected, the DataGrid displays a dialog containing the required input fields. After the data is entered and submitted, a GraphQL mutation transmits the new record to the backend for creation.
+
+ **Instructions:**
+
+ 1. Update the ``GraphQLAdaptorOptions`` in the ``@code`` block to include the Insert mutation:
+
+ ```csharp
+ @code {
+ private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+ {
+ Query = @"query expenseRecordData($dataManager: DataManagerRequestInput!) { ... }",
+ ResolverName = "expenseRecordData",
+ Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
+ {
+ Insert = @"mutation create($record: ExpenseRecordInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
+ createExpense(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ // Add other fields
+ }
+ }"
+ }
+ };
+ }
+ ```
+
+ 2. Implement the ``CreateExpense`` method in the ``GraphQLMutation`` class:
+
+ ```csharp
+ namespace Grid_GraphQLAdaptor.Models;
+
+ public class GraphQLMutation
+ {
+ public ExpenseRecord CreateExpense(ExpenseRecord record, int index, string action, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ {
+ var expenses = ExpenseRecord.GetAllRecords();
+ if (string.IsNullOrWhiteSpace(record.ExpenseId))
+ {
+ record.ExpenseId = GenerateExpenseId(expenses);
+ }
+
+ record.TotalAmount = record.Amount + (record.Amount * record.TaxPct);
+
+ if (index >= 0 && index <= expenses.Count)
+ {
+ expenses.Insert(index, record);
+ }
+ else
+ {
+ expenses.Add(record);
+ }
+
+ return record;
+ }
+
+ ///
+ /// Generates a unique ExpenseId by extracting prefix from existing IDs and incrementing the sequence number.
+ ///
+ /// The list of existing expense records.
+ /// A newly generated unique ExpenseId.
+ private string GenerateExpenseId(List expenses)
+ {
+ string detectedPrefix = "EXP";
+ var firstWithLetters = expenses
+ .Select(e => e.ExpenseId)
+ .FirstOrDefault(id => !string.IsNullOrWhiteSpace(id) && char.IsLetter(id[0]));
+ if (!string.IsNullOrWhiteSpace(firstWithLetters))
+ {
+ int i = 0;
+ while (i < firstWithLetters.Length && char.IsLetter(firstWithLetters[i])) i++;
+ if (i > 0) detectedPrefix = firstWithLetters.Substring(0, i);
+ }
+
+ int maxSeq = expenses
+ .Select(e => e.ExpenseId)
+ .Where(id => !string.IsNullOrWhiteSpace(id))
+ .Select(id =>
+ {
+ int j = id.Length - 1;
+ while (j >= 0 && char.IsDigit(id[j])) j--;
+ var numPart = id.Substring(j + 1);
+ return int.TryParse(numPart, out var n) ? n : 0;
+ })
+ .DefaultIfEmpty(1000) // start sequence at 1001 if nothing found
+ .Max();
+
+ return $"{detectedPrefix}{maxSeq + 1}";
+ }
+ }
+ ```
+
+**Helper Method Explanation:**
+- The `GenerateExpenseId()` helper method extracts the alphabetic prefix (e.g., "EXP") from existing expense IDs and increments the numeric sequence number to create a unique identifier.
+- This method is reused in both `CreateExpense()` and `BatchUpdate()` operations to ensure consistent ID generation across all add operations.
+
+ **Insert Operation Logic Breakdown:**
+
+ | Step | Purpose | Implementation |
+ |------|---------|-----------------|
+ | **1. Receive Input** | Backend receives new record data from client | ``CreateExpense`` method parameter ``record`` contains all field values |
+ | **2. Generate ID** | Auto-generate unique ExpenseId if not provided | Extract prefix from existing IDs, find max sequence number, increment by 1 |
+ | **3. Calculate Totals** | Compute derived fields based on input values | ``TotalAmount = Amount + (Amount * TaxPct)`` |
+ | **4. Insert Record** | Add new record to data source at specified position or at end | Use ``Insert(index, record)`` for specific position or ``Add(record)`` for end |
+ | **5. Return Created** | Send back the created record with generated values | Return ``record`` object with populated ExpenseId and TotalAmount |
+
+ **How Insert Mutation Parameters are Passed:**
+
+ Unlike data operations such as searching, filtering, and sorting—which rely on the **DataManagerRequestInput** structure—CRUD operations pass values directly to the corresponding **GraphQL mutation**. When the Add action is triggered, the dialog is completed, and the form is submitted, the GraphQL adaptor constructs the mutation using the provided field values and sends the following parameters:
+
+ **GraphQL Mutation Request:**
+
+ ```graphql
+ mutation create($record: ExpenseRecordInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
+ createExpense(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ employeeEmail
+ employeeAvatarUrl
+ department
+ category
+ description
+ amount
+ taxPct
+ totalAmount
+ expenseDate
+ paymentMethod
+ currency
+ reimbursementStatus
+ isPolicyCompliant
+ tags
+ }
+ }
+ ```
+
+ **Variables Sent with the Request:**
+
+ ```json
+ {
+ "record": {
+ "expenseId": null,
+ "employeeName": "Alice Johnson",
+ "employeeEmail": "alice.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/alice.png",
+ "department": "Finance",
+ "category": "Travel & Mileage",
+ "description": "Business trip to client office",
+ "amount": 750.00,
+ "taxPct": 0.10,
+ "totalAmount": 0,
+ "expenseDate": "2026-01-20T00:00:00Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Submitted",
+ "isPolicyCompliant": true,
+ "tags": ["Travel", "Client-Billable"]
+ },
+ "index": 0,
+ "action": "add",
+ "additionalParameters": {}
+ }
+ ```
+
+ **Parameter Explanation:**
+
+ | Parameter | Type | Purpose | Example |
+ |-----------|------|---------|---------|
+ | `record` | `ExpenseRecord` | The new expense record object with all field values | Expense data filled in the dialog |
+ | `index` | `int` | The position where the new record should be inserted (0 = top) | `0` for insert at beginning, `-1` or higher than count for append |
+ | `action` | `string` | Type of action being performed (usually "add" for insert) | `"add"` |
+ | `additionalParameters` | `Any` | Extra context or custom parameters from the DataGrid | Empty object `{}` or additional metadata |
+
+ **Backend Response:**
+
+ The mutation returns the created record directly:
+
+ ```json
+ {
+ "data": {
+ "createExpense": {
+ "expenseId": "EXP1501",
+ "employeeName": "Alice Johnson",
+ "employeeEmail": "alice.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/alice.png",
+ "department": "Finance",
+ "category": "Travel & Mileage",
+ "description": "Business trip to client office",
+ "amount": 750,
+ "taxPct": 0.1,
+ "totalAmount": 825,
+ "expenseDate": "2026-01-20T00:00:00.000Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Submitted",
+ "isPolicyCompliant": true,
+ "tags": [
+ "Travel",
+ "Client-Billable"
+ ]
+ }
+ }
+}
+ ```
-```cshtml
+**Update**
+The Update operation enables modifying existing expense records. When the Edit action is selected from the toolbar and a row is chosen, the DataGrid displays a dialog populated with the current record values. After the data is updated and the form is submitted, a GraphQL mutation transmits the modified record to the backend for processing.
-var builder = WebApplication.CreateBuilder(args);
+**Instructions:**
-//GraphQL resolver is defined in GraphQLQuery class and mutation methods are defined in GraphQLMutation class
-builder.Services.AddGraphQLServer().AddQueryType().AddMutationType();
+1. Update the `GraphQLAdaptorOptions` in the `@code` block to include the Update mutation:
-//CORS is enabled to access the GraphQL server from the client application
-builder.Services.AddCors(options =>
-{
- options.AddPolicy("AllowSpecificOrigin", builder =>
+```csharp
+@code {
+ private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
{
- builder.WithOrigins("https://xxxxxx")
- .AllowAnyHeader()
- .AllowAnyMethod()
- .AllowCredentials().Build();
- });
-});
-
+ Query = @"query expenseRecordData($dataManager: DataManagerRequestInput!) { ... }",
+ ResolverName = "expenseRecordData",
+ Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
+ {
+ Update = @"mutation update($record: ExpenseRecordInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
+ updateExpense(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ // Add other fields
+ }
+ }"
+ }
+ };
+}
```
-The following steps outline how to set up these operations in the Grid.
-
-**1. Insert Operation:**
+2. Implement the `UpdateExpense` method in the `GraphQLMutation` class:
-To insert a new record into the GraphQL server, define the mutation query in the [Insert](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLMutation.html#Syncfusion_Blazor_Data_GraphQLMutation_Insert) property of the [Mutation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Mutation) object within [GraphQLAdaptorOptions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html).
-
-This mutation query is executed when a new row is added to the Syncfusion® Blazor DataGrid. The adaptor sends the necessary parameters to the GraphQL server to perform the insertion.
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-**Mutation query configuration**
+public class GraphQLMutation
+{
+ public ExpenseRecord UpdateExpense(ExpenseRecord record, string action, string primaryColumnName, string primaryColumnValue, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ {
+ var expenses = ExpenseRecord.GetAllRecords();
+ var existingExpense = expenses.FirstOrDefault(x => x.ExpenseId == primaryColumnValue);
+
+ if (existingExpense != null)
+ {
+ UpdateExpenseProperties(existingExpense, record);
+ }
-The `Insert` mutation should be configured as shown below:
+ return existingExpense;
+ }
-```cs
-Mutation = new GraphQLMutation
-{
- Insert = @"mutation create($record: OrderDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
- createOrder(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
-},
+ ///
+ /// Updates all properties of an existing expense record with values from a source record.
+ ///
+ /// The existing expense record to update.
+ /// The source record containing new values.
+ private void UpdateExpenseProperties(ExpenseRecord target, ExpenseRecord source)
+ {
+ target.EmployeeName = source.EmployeeName;
+ target.EmployeeEmail = source.EmployeeEmail;
+ target.EmployeeAvatarUrl = source.EmployeeAvatarUrl;
+ target.Department = source.Department;
+ target.Category = source.Category;
+ target.Description = source.Description;
+ target.Amount = source.Amount;
+ target.TaxPct = source.TaxPct;
+ target.Currency = source.Currency;
+ target.PaymentMethod = source.PaymentMethod;
+ target.ReimbursementStatus = source.ReimbursementStatus;
+ target.IsPolicyCompliant = source.IsPolicyCompliant;
+ target.Tags = source.Tags;
+ target.ExpenseDate = source.ExpenseDate;
+
+ target.TotalAmount = target.Amount + (target.Amount * target.TaxPct);
+ }
+}
```
-**Parameters Sent to the Server**
+**Helper Method Explanation:**
+- The `UpdateExpenseProperties()` helper method encapsulates all property assignment logic, making the update operation cleaner and more maintainable.
+- This method is reused in both `UpdateExpense()` and `BatchUpdate()` operations to ensure consistent property updates and total amount recalculation across all edit operations.
+
+**Update Operation Logic Breakdown:**
+
+| Step | Purpose | Implementation |
+|------|---------|-----------------|
+| **1. Find Record** | Locate the existing record using primary key value | `FirstOrDefault(x => x.ExpenseId == primaryColumnValue)` |
+| **2. Validate Existence** | Ensure the record exists before updating | `if (existingExpense != null)` check |
+| **3. Update Properties** | Replace all property values with modified data | Assign each property from `record` parameter to `existingExpense` |
+| **4. Recalculate Totals** | Compute derived fields based on updated values | `TotalAmount = Amount + (Amount * TaxPct)` |
+| **5. Preserve ID** | Keep original ExpenseId unchanged | ExpenseId is not updated, only retrieved for lookup |
+| **6. Return Updated** | Send back the modified record with new values | Return `existingExpense` object with all updates applied |
+
+**How Update Mutation Parameters are Passed:**
+
+When the Edit action is invoked, the dialog is modified, and the changes are submitted, the **GraphQL adaptor** constructs the **mutation** using the following parameters:
+
+**GraphQL Mutation Request:**
+
+```graphql
+mutation update($record: ExpenseRecordInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
+ updateExpense(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
+ expenseId
+ employeeName
+ employeeEmail
+ employeeAvatarUrl
+ department
+ category
+ description
+ amount
+ taxPct
+ totalAmount
+ expenseDate
+ paymentMethod
+ currency
+ reimbursementStatus
+ isPolicyCompliant
+ tags
+ }
+}
+```
-The following variables are passed as a parameter to the mutation method written for **Insert** operation in server side.
+**Variables Sent with the Request:**
-| Properties | Description |
-|--------|----------------|
-| record | The new record which is need 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, this argument serves to distinguish the action, such as **Add, Delete and Update** |
-| additionalParameters | An optional parameter that can be used to perform any operations. |
+```json
+{
+ "record": {
+ "expenseId": "EXP1001",
+ "employeeName": "Alice Johnson",
+ "employeeEmail": "alice.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/alice.png",
+ "department": "Finance",
+ "category": "Travel & Mileage",
+ "description": "Updated: Flight to New York for client meeting",
+ "amount": 850.00,
+ "taxPct": 0.12,
+ "totalAmount": 0,
+ "expenseDate": "2026-01-20T00:00:00Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Under Review",
+ "isPolicyCompliant": true,
+ "tags": ["Travel", "Client-Billable", "Updated"]
+ },
+ "action": "save",
+ "primaryColumnName": "ExpenseId",
+ "primaryColumnValue": "EXP1001",
+ "additionalParameters": {}
+}
+```
-**Server-Side Mutation Implementation**
+**Parameter Explanation:**
-The following example demonstrates how to implement the insert logic on the GraphQL server using C# with HotChocolate:
+| Parameter | Type | Purpose | Example |
+|-----------|------|---------|---------|
+| `record` | `ExpenseRecord` | The modified expense record object with updated field values | Expense data with changes made in the dialog |
+| `action` | `string` | Type of action being performed (usually "save" for update) | `"save"` |
+| `primaryColumnName` | `string` | Name of the primary key column used to identify the record | `"ExpenseId"` |
+| `primaryColumnValue` | `string` | Value of the primary key to locate which record to update | `"EXP1001"` |
+| `additionalParameters` | `Any` | Extra context or custom parameters from the DataGrid | Empty object `{}` or additional metadata |
-```cs
+**Backend Response:**
-using GraphQLServer.Models;
+The mutation returns the updated record with all changes applied:
-namespace GraphQLServer.GraphQL
+```json
{
- public class GraphQLMutation
- {
- public OrderData CreateOrder(OrderData record, int index, string action, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
- {
- var orders = OrderData.GetAllRecords();
- if (index >= 0 && index <= orders.Count)
- {
- orders.Insert(index, record);
- }
- else
- {
- orders.Add(record);
- }
- return record;
- }
+ "data": {
+ "updateExpense": {
+ "expenseId": "EXP1001",
+ "employeeName": "Alice Johnson",
+ "employeeEmail": "alice.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/alice.png",
+ "department": "Finance",
+ "category": "Travel & Mileage",
+ "description": "Updated: Flight to New York for client meeting",
+ "amount": 850,
+ "taxPct": 0.12,
+ "totalAmount": 952,
+ "expenseDate": "2026-01-20T00:00:00.000Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Under Review",
+ "isPolicyCompliant": true,
+ "tags": [
+ "Travel",
+ "Client-Billable",
+ "Updated"
+ ]
}
+ }
}
-
```
-**2. Update Operation:**
+---
-To update an existing record on the GraphQL server, define the mutation query in the [Update](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLMutation.html#Syncfusion_Blazor_Data_GraphQLMutation_Update) property of the [Mutation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Mutation) object within [GraphQLAdaptorOptions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html).
+**Delete**
-This mutation query is triggered when an existing row in the Syncfusion® Blazor DataGrid is modified. The adaptor sends the updated data and relevant parameters to the GraphQL server for processing.
+The Delete operation enables removing expense records from the system. When the Delete action is selected from the toolbar, a GraphQL mutation issues a delete request to the backend using only the primary key value.
-**Mutation query configuration**
+**Instructions:**
-The Update mutation should be configured as shown below:
+1. Update the `GraphQLAdaptorOptions` in the `@code` block to include the Delete mutation:
-```cs
-Mutation = new GraphQLMutation
-{
- Update = @"mutation update($record: OrderDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: Int!, $additionalParameters: Any) {
- updateOrder(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
-},
+```csharp
+@code {
+ private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+ {
+ Query = @"query expenseRecordData($dataManager: DataManagerRequestInput!) { ... }",
+ ResolverName = "expenseRecordData",
+ Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
+ {
+ Delete = @"mutation delete($primaryColumnValue: String!, $additionalParameters: Any) {
+ deleteExpense(primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters)
+ }"
+ }
+ };
+}
```
-**Parameters Sent to the Server**
-
-The following variables are passed as a parameter to the mutation method written for **Update** operation in server side.
-
-| Properties | Description |
-|--------|----------------|
-| record | The new record which is need to be updated. |
-| action | Indicates the type of operation being performed. When the same method is used for all CRUD actions, this argument serves to distinguish the action, such as **Add, Delete and Update** |
-| primaryColumnName | Specifies the field name of the primary column. |
-| primaryColumnValue | Specifies the primary column value which is needs to be updated in the collection. |
-| additionalParameters | An optional parameter that can be used to perform any operations. |
-
-**Server-Side Mutation Implementation**
+2. Implement the `DeleteExpense` method in the `GraphQLMutation` class:
-The following example demonstrates how to implement the update logic on the GraphQL server using C# with HotChocolate:
-
-```cs
-using GraphQLServer.Models;
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-namespace GraphQLServer.GraphQL
+public class GraphQLMutation
{
- public class GraphQLMutation
+ public bool DeleteExpense(string primaryColumnValue, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
{
- public OrderData UpdateOrder(OrderData record, string action, string primaryColumnName, int primaryColumnValue, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ var expenses = ExpenseRecord.GetAllRecords();
+ var expenseToDelete = expenses.FirstOrDefault(x => x.ExpenseId == primaryColumnValue);
+
+ if (expenseToDelete != null)
{
- var existingOrder = OrderData.GetAllRecords().FirstOrDefault(x => x.OrderID == primaryColumnValue);
- if (existingOrder != null)
- {
- existingOrder.CustomerID = record.CustomerID;
- existingOrder.EmployeeID = record.EmployeeID;
- existingOrder.ShipCity = record.ShipCity;
- existingOrder.ShipCountry = record.ShipCountry;
- }
- return existingOrder;
+ expenses.Remove(expenseToDelete);
+ return true;
}
+
+ return false;
}
}
-
```
-**3. Delete Operation:**
+**Delete Operation Logic Breakdown:**
-To delete an existing record from the GraphQL server, define the mutation query in the [Delete](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLMutation.html#Syncfusion_Blazor_Data_GraphQLMutation_Delete) property of the [Mutation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Mutation) object within [GraphQLAdaptorOptions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html).
+| Step | Purpose | Implementation |
+|------|---------|-----------------|
+| **1. Receive Key** | Backend receives only the primary key value from client | `DeleteExpense` method parameter `primaryColumnValue` contains the record ID |
+| **2. Find Record** | Locate the record to delete using the primary key | `FirstOrDefault(x => x.ExpenseId == primaryColumnValue)` |
+| **3. Validate Existence** | Ensure the record exists before attempting deletion | `if (expenseToDelete != null)` check |
+| **4. Remove Record** | Delete the record from the data source | `expenses.Remove(expenseToDelete)` |
+| **5. Return Status** | Send success/failure confirmation to client | Return `true` if deleted, `false` if not found |
-This mutation query is executed when a row is removed from the Syncfusion® Blazor DataGrid. The adaptor passes the required parameters to the GraphQL server to process the deletion.
+**GraphQL Mutation Request:**
-**Mutation query configuration**
+```graphql
+mutation delete($primaryColumnValue: String!, $additionalParameters: Any) {
+ deleteExpense(primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters)
+}
+```
-The Delete mutation should be configured as shown below:
+**Variables Sent with the Request:**
-```cs
-Mutation = new GraphQLMutation
+```json
{
- Delete = @"mutation delete($primaryColumnValue: Int!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
- deleteOrder(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
-},
+ "primaryColumnValue": "EXP1001",
+ "additionalParameters": {}
+}
```
-**Parameters Sent to the Server**
+**Parameter Explanation:**
-The following variables are passed as a parameter to the mutation method written for **Delete** operation in server side.
+| Parameter | Type | Purpose | Example |
+|-----------|------|---------|---------|
+| `primaryColumnValue` | `string` | Value of the primary key identifying which record to delete | `"EXP1001"` |
+| `additionalParameters` | `Any` | Extra context or custom parameters from the DataGrid | Empty object `{}` or additional metadata |
-| Properties | Description |
-|--------|----------------|
-| primaryColumnValue | Specifies the primary column value which is 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, this argument serves to distinguish the action, 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. |
+**Backend Response:**
-**Server-Side Mutation Implementation**
+The mutation returns a boolean success/failure indicator:
-The following example demonstrates how to implement the delete logic on the GraphQL server using C# with HotChocolate:
+```json
+{
+ "data": {
+ "deleteExpense": true
+ }
+}
+```
-```cs
-using GraphQLServer.Models;
+If the record doesn't exist:
-namespace GraphQLServer.GraphQL
+```json
{
- public class GraphQLMutation
- {
- public OrderData DeleteOrder(int primaryColumnValue, string action, string primaryColumnName, [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
- {
- var orders = OrderData.GetAllRecords();
- var orderToDelete = orders.FirstOrDefault(x => x.OrderID == primaryColumnValue);
- if (orderToDelete != null)
- {
- orders.Remove(orderToDelete);
- }
- return orderToDelete;
- }
- }
+ "data": {
+ "deleteExpense": false
+ }
}
-
```
-The following code shows how to bind the Grid with a GraphQL service and enable CRUD operations.
+**Batch Update**
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
+The Batch Update operation allows adding, updating, and deleting multiple records in a single request.
-@page "/"
+**Instructions**:
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
-
-
+1. Update the `GraphQLAdaptorOptions` in the `@code` block to include the Batch mutation:
+```csharp
@code {
private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- Mutation = new GraphQLMutation
- {
- Insert = @"mutation create($record: OrderDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
- createOrder(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
- Update = @"mutation update($record: OrderDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: Int!, $additionalParameters: Any) {
- updateOrder(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
- Delete = @"mutation delete($primaryColumnValue: Int!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
- deleteOrder(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }",
- },
- ResolverName = "ordersData"
- };
-
- public class OrderData
{
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
-
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
-
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
-
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
+ Query = @"query expenseRecordData($dataManager: DataManagerRequestInput!) { ... }",
+ ResolverName = "expenseRecordData",
+ Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
+ {
+ Batch = @"mutation batch($changed: [ExpenseRecordInput!], $added: [ExpenseRecordInput!], $deleted: [ExpenseRecordInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) {
+ batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) {
+ expenseId
+ employeeName
+ // Add other fields
+ }
+ }"
+ }
+ };
}
+```
-{% endhighlight %}
+2. Implement the `BatchUpdate` method in the `GraphQLMutation` class:
-{% highlight c# tabtitle="GraphQLMutation.cs" %}
+```csharp
+namespace Grid_GraphQLAdaptor.Models;
-using GraphQLServer.Models;
+using HotChocolate.Types;
-namespace GraphQLServer.GraphQL
+public class GraphQLMutation
{
- public class GraphQLMutation
+ public List BatchUpdate(List? changed, List? added,
+ List? deleted, string action, string primaryColumnName,
+ [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, int? dropIndex)
{
- public OrderData CreateOrder(OrderData record, int index, string action,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ var expenses = ExpenseRecord.GetAllRecords();
+
+ // Update existing expenses
+ if (changed != null)
{
- var orders = OrderData.GetAllRecords();
- if (index >= 0 && index <= orders.Count)
- {
- orders.Insert(index, record);
- }
- else
+ foreach (var changedItem in changed)
{
- orders.Add(record);
+ var existing = expenses.FirstOrDefault(e => e.ExpenseId == changedItem.ExpenseId);
+ if (existing != null)
+ {
+ UpdateExpenseProperties(existing, changedItem);
+ }
}
- return record;
}
- public OrderData UpdateOrder(OrderData record, string action, string primaryColumnName, int primaryColumnValue,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ // Add new expenses
+ if (added != null)
{
- var existingOrder = OrderData.GetAllRecords().FirstOrDefault(x => x.OrderID == primaryColumnValue);
- if (existingOrder != null)
+ foreach (var newItem in added)
{
- existingOrder.CustomerID = record.CustomerID;
- existingOrder.EmployeeID = record.EmployeeID;
- existingOrder.ShipCity = record.ShipCity;
- existingOrder.ShipCountry = record.ShipCountry;
- }
- return existingOrder;
+ if (string.IsNullOrWhiteSpace(newItem.ExpenseId))
+ {
+ newItem.ExpenseId = GenerateExpenseId(expenses);
+ }
+
+ newItem.TotalAmount = newItem.Amount + (newItem.Amount * newItem.TaxPct);
+
+ if (dropIndex.HasValue && dropIndex >= 0 && dropIndex <= expenses.Count)
+ expenses.Insert(dropIndex.Value, newItem);
+ else
+ expenses.Add(newItem);
+ }
}
- public OrderData DeleteOrder(int primaryColumnValue, string action, string primaryColumnName,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ // Delete expenses
+ if (deleted != null)
{
- var orders = OrderData.GetAllRecords();
- var orderToDelete = orders.FirstOrDefault(x => x.OrderID == primaryColumnValue);
- if (orderToDelete != null)
+ foreach (var del in deleted)
{
- orders.Remove(orderToDelete);
+ var toRemove = expenses.FirstOrDefault(e => e.ExpenseId == del.ExpenseId);
+ if (toRemove != null) expenses.Remove(toRemove);
}
- return orderToDelete;
}
+
+ return expenses;
}
}
-
-{% endhighlight %}
-{% endtabs %}
-
-
-
-**Batch operation:**
-
-To perform multiple data operations in a single request to the GraphQL server, define the mutation query in the [Batch](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLMutation.html#Syncfusion_Blazor_Data_GraphQLMutation_Batch) property of the [Mutation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html#Syncfusion_Blazor_Data_GraphQLAdaptorOptions_Mutation) object within [GraphQLAdaptorOptions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.GraphQLAdaptorOptions.html).
-
-This `Batch` mutation is triggered when multiple changes (add, edit, delete) are made in batch editing mode of the Syncfusion® Blazor DataGrid and then committed using the Update toolbar button.
-
-**Mutation query configuration**
-
-The Batch mutation should be configured as shown below:
-
-```cs
-
-Mutation = new GraphQLMutation
-{
- Batch = @"
- mutation batch($changed: [OrderDataInput!], $added: [OrderDataInput!], $deleted: [OrderDataInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) {
- batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName :$primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }"
-},
-
```
-**Parameters Sent to the Server**
-
-The following variables are passed as a parameter to the mutation method written for **Batch** operation in server side.
-
-| Properties | Description |
-|--------|----------------|
-| changed | Specifies the collection of record to be updated. |
-| added | Specifies the collection of record to be inserted. |
-| deleted | Specifies the collection of record to be removed. |
-| action | Indicates the type of operation being performed. |
-| primaryColumnName | Specifies the field name of the primary column. |
-| additionalParameters | An optional parameter that can be used to perform any operations. |
-| dropIndex | Specifies the record position, from which new records will be added while performing drag and drop. |
-
-**Server-Side Mutation Implementation**
-
-The following example demonstrates how to implement batch logic on the GraphQL server using C# with HotChocolate:
+**How Batch Mutation Parameters are Passed:**
+
+GraphQL Mutation Request:
+
+```graphql
+mutation batch($changed: [ExpenseRecordInput!], $added: [ExpenseRecordInput!], $deleted: [ExpenseRecordInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) {
+ batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) {
+ expenseId
+ employeeName
+ employeeEmail
+ employeeAvatarUrl
+ department
+ category
+ description
+ amount
+ taxPct
+ totalAmount
+ expenseDate
+ paymentMethod
+ currency
+ reimbursementStatus
+ isPolicyCompliant
+ tags
+ }
+}
+```
-```cs
-using GraphQLServer.Models;
+**Variables Sent with the Request:**
-namespace GraphQLServer.GraphQL
+```json
{
- public class GraphQLMutation
+ "changed": [
{
- public List BatchUpdate(List? changed, List? added,
- List? deleted, string action, string primaryColumnName,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, int? dropIndex)
- {
- var orders = OrderData.GetAllRecords();
+ "expenseId": "EXP1002",
+ "employeeName": "Alice Johnson",
+ "employeeEmail": "alice.johnson@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/alice.png",
+ "department": "Finance",
+ "category": "Travel & Mileage",
+ "description": "Updated hotel charges",
+ "amount": 620.00,
+ "taxPct": 0.1,
+ "totalAmount": 0,
+ "expenseDate": "2026-01-22T00:00:00Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Approved",
+ "isPolicyCompliant": true,
+ "tags": ["Travel"]
+ }
+ ],
+ "added": [
+ {
+ "expenseId": null,
+ "employeeName": "Bob Smith",
+ "employeeEmail": "bob.smith@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/bob.png",
+ "department": "Engineering",
+ "category": "Software & SaaS",
+ "description": "API monitoring subscription",
+ "amount": 199.99,
+ "taxPct": 0.07,
+ "totalAmount": 0,
+ "expenseDate": "2026-01-25T00:00:00Z",
+ "paymentMethod": "Personal Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Submitted",
+ "isPolicyCompliant": true,
+ "tags": ["SaaS"]
+ }
+ ],
+ "deleted": [
+ {
+ "expenseId": "EXP1001",
+ "amount": 0.00,
+ "taxPct": 0.00,
+ "totalAmount": 0.00,
+ "isPolicyCompliant":false,
+ "tags": []
+ }
+ ],
+ "action": "batch",
+ "primaryColumnName": "ExpenseId",
+ "additionalParameters": {},
+ "dropIndex": 0
+}
+```
- // Update existing orders.
- if (changed != null)
- {
- foreach (var changedOrder in changed)
- {
- var order = orders.FirstOrDefault(e => e.OrderID == changedOrder.OrderID);
- if (order != null)
- {
- order.CustomerID = changedOrder.CustomerID;
- order.OrderDate = changedOrder.OrderDate;
- order.Freight = changedOrder.Freight;
- }
- }
- }
+**Parameter Explanation:**
- // Add new orders.
- if (added != null)
- {
- if (dropIndex.HasValue && dropIndex >= 0 && dropIndex <= orders.Count)
- {
- orders.InsertRange(dropIndex.Value, added);
- }
- else
- {
- orders.AddRange(added);
- }
- }
+| Parameter | Type | Purpose | Example |
+|-----------|------|---------|---------|
+| **changed** | [ExpenseRecord] | Records to update | Modified rows with existing ExpenseId |
+| **added** | [ExpenseRecord] | Records to insert | New rows, ExpenseId optional |
+| **deleted** | [ExpenseRecord] | Records to delete | Objects with ExpenseId only |
+| **action** | string | Batch action indicator | "batch" |
+| **primaryColumnName** | string | Name of primary key column | "ExpenseId" |
+| **additionalParameters** | Any | Extra context from grid | {} |
+| **dropIndex** | Int | Target index for insertion/reorder | 0 |
- // Delete orders.
- if (deleted != null)
- {
- foreach (var deletedOrder in deleted)
- {
- var orderToRemove = orders.FirstOrDefault(e => e.OrderID == deletedOrder.OrderID);
- if (orderToRemove != null)
- {
- orders.Remove(orderToRemove);
- }
- }
- }
+**Backend Response:**
- return orders;
- }
- }
+```json
+{
+ "data": {
+ "batchUpdate": [
+ {
+ "expenseId": "EXP1001",
+ "employeeName": "Carol Davis",
+ "employeeEmail": "carol.davis@company.com",
+ "employeeAvatarUrl": "https://example.com/avatars/carol.png",
+ "department": "Finance",
+ "category": "Office Supplies",
+ "description": "Printer ink cartridges",
+ "amount": 85.5,
+ "taxPct": 0.05,
+ "totalAmount": 89.78,
+ "expenseDate": "2026-01-15T00:00:00.000Z",
+ "paymentMethod": "Corporate Card",
+ "currency": "USD - US Dollar",
+ "reimbursementStatus": "Approved",
+ "isPolicyCompliant": true,
+ "tags": ["Office"]
+ }
+ ]
+ }
}
-
```
-The following sections explain how to configure each CRUD and batch operation in the Grid using GraphQL.
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-
-@page "/"
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using System.Text.Json.Serialization;
-
-
-
-
-
-
-
-
-
-
-
-
+## Running the Application
-@code {
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = @"query ordersData($dataManager: DataManagerRequestInput!) {
- ordersData(dataManager: $dataManager) {
- count
- result {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }
- }",
- Mutation = new GraphQLMutation
- {
- Batch = @"
- mutation batch($changed: [OrderDataInput!], $added: [OrderDataInput!], $deleted: [OrderDataInput!], $action: String!, $primaryColumnName: String!, $additionalParameters: Any, $dropIndex: Int) {
- batchUpdate(changed: $changed, added: $added, deleted: $deleted, action: $action, primaryColumnName :$primaryColumnName, additionalParameters: $additionalParameters, dropIndex: $dropIndex) {
- orderID
- customerID
- shipCity
- shipCountry
- }
- }"
- },
- ResolverName = "ordersData"
- };
+**Step 1: Build the Application**
- public class OrderData
- {
- [JsonPropertyName("orderID")]
- public int OrderID { get; set; }
+1. Open the terminal or Package Manager Console.
+2. Navigate to the project directory.
+3. Run the following command:
- [JsonPropertyName("customerID")]
- public string? CustomerID { get; set; }
+```powershell
+dotnet build
+```
- [JsonPropertyName("shipCity")]
- public string? ShipCity { get; set; }
+**Step 2: Run the Application**
- [JsonPropertyName("shipCountry")]
- public string? ShipCountry { get; set; }
- }
-}
+Execute the following command:
-{% endhighlight %}
+```powershell
+dotnet run
+```
-{% highlight c# tabtitle="GraphQLMutation.cs" %}
+**Step 3: Access the Application**
-using GraphQLServer.Models;
+1. Open a web browser.
+2. Navigate to `https://localhost:5272` (or the port shown in the terminal).
+3. The Expense Tracker System is now running and ready to use.
-namespace GraphQLServer.GraphQL
-{
- public class GraphQLMutation
- {
- public List BatchUpdate(List? changed, List? added,
- List? deleted, string action, string primaryColumnName,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters, int? dropIndex)
- {
- var orders = OrderData.GetAllRecords();
+---
- // Update existing orders.
- if (changed != null)
- {
- foreach (var changedOrder in changed)
- {
- var order = orders.FirstOrDefault(e => e.OrderID == changedOrder.OrderID);
- if (order != null)
- {
- order.CustomerID = changedOrder.CustomerID;
- order.OrderDate = changedOrder.OrderDate;
- order.Freight = changedOrder.Freight;
- }
- }
- }
+## Complete Sample Repository
- // Add new orders.
- if (added != null)
- {
- if (dropIndex.HasValue && dropIndex >= 0 && dropIndex <= orders.Count)
- {
- orders.InsertRange(dropIndex.Value, added);
- }
- else
- {
- orders.AddRange(added);
- }
- }
+A complete, working sample implementation is available in the [GitHub repository](https://github.com/SyncfusionExamples/Binding-data-from-remote-service-to-blazor-data-grid/tree/master/GraphQLAdaptor).
- // Delete orders.
- if (deleted != null)
- {
- foreach (var deletedOrder in deleted)
- {
- var orderToRemove = orders.FirstOrDefault(e => e.OrderID == deletedOrder.OrderID);
- if (orderToRemove != null)
- {
- orders.Remove(orderToRemove);
- }
- }
- }
+---
+## Summary
- return orders;
- }
- }
-}
+This guide demonstrates how to:
-{% endhighlight %}
-{% endtabs %}
+1. Install required NuGet packages for Hot Chocolate and Syncfusion Blazor. [🔗](#step-1-install-required-nuget-packages-and-configure-launch-settings)
+2. Register Hot Chocolate services and expose the GraphQL endpoint. [🔗](#step-2-register-hot-chocolate-services-in-programcs)
+3. Configure launch settings and ports for the GraphQL endpoint. [🔗](#step-3-configure-launch-settings-port-configuration)
+4. Create the ExpenseRecord data model used across the GraphQL schema. [🔗](#step-4-create-the-data-model)
+5. Implement GraphQL query resolvers to read data. [🔗](#step-5-graphql-query-resolvers)
+6. Create the DataManagerRequestInput input type to carry grid operations. [🔗](#step-6-create-the-datamanagerrequestinput-class)
+7. Define GraphQL mutation resolvers for Create, Update, and Delete. [🔗](#step-7-define-graphql-mutation-resolvers)
+8. Integrate Syncfusion Blazor DataGrid and configure the GraphQL adaptor. [🔗](#step-3-configure-graphql-adaptor-and-data-binding)
+9. Perform CRUD operations from the grid using GraphQL mutations. [🔗](#perform-crud-operations)
-You can get the entire code in the [github](https://github.com/SyncfusionExamples/Binding-data-from-remote-service-to-blazor-data-grid/tree/master/GraphQLAdaptor) sample.
+The application now provides a complete solution for managing expenses with a modern Syncfusion Blazor DataGrid integrated with a Hot Chocolate GraphQL backend.
diff --git a/blazor/datagrid/connecting-to-database/microsoft-sql-server.md b/blazor/datagrid/connecting-to-database/microsoft-sql-server.md
index 6f842de5f4..13072435c0 100644
--- a/blazor/datagrid/connecting-to-database/microsoft-sql-server.md
+++ b/blazor/datagrid/connecting-to-database/microsoft-sql-server.md
@@ -1,1341 +1,2147 @@
---
layout: post
-title: Microsoft SQL Data Binding in Blazor DataGrid Component | Syncfusion
-description: Learn about consuming data from SQL Server using Microsoft SQL Client, binding it to Syncfusion Component, and performing CRUD operations.
+title: Blazor Data Grid connected to SQL via Entity Framework | Syncfusion
+description: Bind SQL Server data to Blazor Data Grid using Entity Framework Core with complete CRUD, filtering, sorting, paging, and advanced data operations.
platform: Blazor
control: DataGrid
documentation: ug
---
-# Connecting Microsoft SQL Server data to Blazor DataGrid
+# Connecting SQL Server to Blazor Data Grid Using Entity Framework
+
+The [Syncfusion® Blazor DataGrid](https://www.syncfusion.com/blazor-components/blazor-datagrid) supports binding data from a SQL Server database using Entity Framework Core (EF Core). This modern approach provides a more maintainable and type-safe alternative to raw SQL queries.
+
+**What is Entity Framework Core?**
+
+Entity Framework Core (EF Core) is a software tool that simplifies database operations in .NET applications. It serves as a bridge between C# code and databases like SQL Server.
+
+**Key Benefits of Entity Framework Core**
+
+- **Automatic SQL Generation**: Entity Framework Core generates optimized SQL queries automatically, eliminating the need to write raw SQL code.
+- **Type Safety**: Work with strongly-typed objects instead of raw SQL strings, reducing errors.
+- **Built-in Security**: Automatic parameterization prevents SQL injection attacks.
+- **Version Control for Databases**: Manage database schema changes version-by-version through migrations.
+- **Familiar Syntax**: Use LINQ (Language Integrated Query) syntax, which is more intuitive than raw SQL strings.
+
+**What is Entity Framework Core SQL Server Provider?**
+
+The **Microsoft.EntityFrameworkCore.SqlServer** package is the official Entity Framework Core provider for SQL Server. It acts as a bridge between Entity Framework Core and SQL Server, allowing applications to read, write, update, and delete data in a SQL Server database.
+
+## Prerequisites
+
+Ensure the following software and packages are installed before proceeding:
+
+| Software/Package | Version | Purpose |
+|-----------------|---------|---------|
+| Visual Studio 2022 | 17.0 or later | Development IDE with Blazor workload |
+| .NET SDK | net8.0 or compatible | Runtime and build tools |
+| SQL Server | 2019 or later | Database server |
+| Syncfusion.Blazor.Grids | {{site.blazorversion}} | DataGrid and UI components |
+| Syncfusion.Blazor.Themes | {{site.blazorversion}} | Styling for DataGrid components |
+| Microsoft.EntityFrameworkCore | 9.0.0 or later | Core framework for database operations |
+| Microsoft.EntityFrameworkCore.Tools | 9.0.0 or later | Tools for managing database migrations |
+| Microsoft.EntityFrameworkCore.SqlServer | 9.0.0 or later | SQL Server provider for Entity Framework Core |
+
+## Setting Up the SQL Server Environment for Entity Framework Core
+
+### Step 1: Create the database and Table in SQL Server
+
+First, the **SQL Server database** structure must be created to store ticket records.
+
+**Instructions:**
+1. Open SQL Server Management Studio (SSMS) or any SQL Server client.
+2. Create a new database named `NetworkSupportDB`.
+3. Define a `Tickets` table with the specified schema.
+4. Insert sample data for testing.
+
+Run the following SQL script:
+
+```sql
+-- Create Database
+IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'NetworkSupportDB')
+BEGIN
+ CREATE DATABASE NetworkSupportDB;
+END
+GO
+
+USE NetworkSupportDB;
+GO
+
+-- Create Tickets Table
+IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Tickets')
+BEGIN
+ CREATE TABLE dbo.Tickets (
+ TicketId INT PRIMARY KEY IDENTITY(1,1),
+ PublicTicketId VARCHAR(50) NOT NULL UNIQUE,
+ Title VARCHAR(200) NULL,
+ Description TEXT NULL,
+ Category VARCHAR(100) NULL,
+ Department VARCHAR(100) NULL,
+ Assignee VARCHAR(100) NULL,
+ CreatedBy VARCHAR(100) NULL,
+ Status VARCHAR(50) NOT NULL DEFAULT 'Open',
+ Priority VARCHAR(50) NOT NULL DEFAULT 'Medium',
+ ResponseDue DATETIME2 NULL,
+ DueDate DATETIME2 NULL,
+ CreatedAt DATETIME2 NOT NULL DEFAULT GETDATE(),
+ UpdatedAt DATETIME2 NOT NULL DEFAULT GETDATE()
+ );
+END
+GO
+
+-- Insert Sample Data (Optional)
+INSERT INTO dbo.Tickets (PublicTicketId, Title, Description, Category, Department, Assignee, CreatedBy, Status, Priority, ResponseDue, DueDate, CreatedAt, UpdatedAt)
+VALUES
+('NET-1001', 'Network Connectivity Issue', 'Users unable to connect to the VPN', 'Network Issue', 'Network Ops', 'John Doe', 'Alice Smith', 'Open', 'High', '2026-01-14 10:00:00', '2026-01-15 17:00:00', '2026-01-13 10:15:30', '2026-01-13 10:15:30'),
+('NET-1002', 'Server Performance Degradation', 'Email server responding slowly', 'Performance', 'Infrastructure', 'Emily White', 'Bob Johnson', 'InProgress', 'Critical', '2026-01-13 15:00:00', '2026-01-14 17:00:00', '2026-01-13 11:20:10', '2026-01-13 11:20:10');
+GO
+```
-The Syncfusion® Blazor DataGrid component supports multiple approaches for binding data from Microsoft SQL Server:
+After executing this script, the ticket records are stored in the `Tickets` table within the `NetworkSupportDB` database. The database is now ready for integration with the Blazor application.
-* Configure the [DataSource](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_DataSource) property for local data binding.
-* Implement a [CustomAdaptor](https://blazor.syncfusion.com/documentation/datagrid/connecting-to-adaptors/custom-adaptor) for custom server-side logic.
-* Use remote data binding with adaptors such as [UrlAdaptor](https://blazor.syncfusion.com/documentation/datagrid/connecting-to-adaptors/url-adaptor).
+---
-This guide explains two primary approaches for integrating Microsoft SQL Server data with the Blazor DataGrid:
+### Step 2: Install Required NuGet Packages
-**Using UrlAdaptor**: Enables communication between the DataGrid and a remote API service connected to SQL Server. This approach is suitable when the API implements custom logic for data operations and returns results in the **result** and **count** format.
+Before installing the necessary NuGet packages, a new Blazor Web Application must be created using the default template.
+This template automatically generates essential starter files—such as **Program.cs, appsettings.json, the wwwroot folder, and the Components folder**.
-{% youtube "youtube:https://www.youtube.com/watch?v=Y3grzt0ZdLk" %}
+For this guide, a Blazor application named **Grid_MSSQL** has been created. Once the project is set up, the next step involves installing the required NuGet packages. NuGet packages are software libraries that add functionality to the application. These packages enable Entity Framework Core and SQL Server integration.
-**Using CustomAdaptor**: Provides full control over data operations and CRUD functionality. It allows implementing custom logic for **searching**, **filtering**, **sorting**, **paging**, and **grouping** directly in server-side code.
+**Method 1: Using Package Manager Console**
-{% youtube "youtube:https://www.youtube.com/watch?v=8yLpSCJLcXI" %}
+1. Open Visual Studio 2022.
+2. Navigate to **Tools → NuGet Package Manager → Package Manager Console**.
+3. Run the following commands:
-Both approaches support CRUD operations and can be customized to meet application-specific requirements.
+```powershell
+Install-Package Microsoft.EntityFrameworkCore -Version 9.0.0;
+Install-Package Microsoft.EntityFrameworkCore.Tools -Version 9.0.0;
+Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 9.0.0;
+Install-Package Syncfusion.Blazor.Grids -Version {{site.blazorversion}};
+Install-Package Syncfusion.Blazor.Themes -Version {{site.blazorversion}}
+```
-## Microsoft SQL Server Overview
+**Method 2: Using NuGet Package Manager UI**
-Microsoft SQL Server is a relational database management system (**RDBMS**) developed by Microsoft. It is designed to store, retrieve, and manage data efficiently for enterprise applications. SQL Server uses Structured Query Language (**SQL**) for querying and manipulating data.
+1. Open **Visual Studio 2022 → Tools → NuGet Package Manager → Manage NuGet Packages for Solution**.
+2. Search for and install each package individually:
+ - **Microsoft.EntityFrameworkCore** (version 9.0.0 or later)
+ - **Microsoft.EntityFrameworkCore.Tools** (version 9.0.0 or later)
+ - **Microsoft.EntityFrameworkCore.SqlServer** (version 9.0.0 or later)
+ - **Syncfusion.Blazor.Grids** (version {{site.blazorversion}})
+ - **Syncfusion.Blazor.Themes** (version {{site.blazorversion}})
-**Key Features**
+All required packages are now installed.
-- **Relational Database Model**: Organizes data into tables with rows and columns.
-- **T-SQL Support**: Provides **Transact-SQL** for advanced querying and procedural programming.
-- **High Availability**: Features like Always On Availability Groups for failover and disaster recovery.
-- **Security**: Includes **encryption**, **authentication**, and **role-based access** control.
-- **Integration**: Works with .NET applications, Azure services, and supports REST APIs.
-- **Scalability**: Handles large datasets and supports both on-premises and cloud deployments.
+### Step 3: Create the Data Model
-For more details, refer to the official [Microsoft documentation](https://learn.microsoft.com/en-us/sql/sql-server/what-is-sql-server?view=sql-server-ver17).
+A data model is a C# class that represents the structure of a database table. This model defines the properties that correspond to the columns in the `Tickets` table.
+**Instructions:**
-## Binding data from Microsoft SQL Server using an API service
+1. Create a new folder named `Data` in the Blazor application project.
+2. Inside the `Data` folder, create a new file named **Tickets.cs**.
+3. Define the **Tickets** class with the following code:
-Data from Microsoft SQL Server can be retrieved through an ASP.NET Core Web API and bound to the Syncfusion® Blazor DataGrid using UrlAdaptor. This approach enables server-side operations such as paging, sorting, and filtering.
+```csharp
+using System.ComponentModel.DataAnnotations;
-### Creating an API service
+namespace Grid_MSSQL.Data
+{
+ ///
+ /// Represents a ticket record mapped to the 'Tickets' table in the database.
+ /// This model defines the structure of ticket-related data used throughout the application.
+ ///
+ public class Tickets
+ {
+ ///
+ /// Gets or sets the unique identifier for the ticket record.
+ ///
+ [Key]
+ public int TicketId { get; set; }
-**Step 1: Create an ASP.NET Core Web API Project**
+ ///
+ /// Gets or sets the public-facing ticket identifier (e.g., NET-1001).
+ ///
+ public string? PublicTicketId { get; set; }
-Create a new **ASP.NET Core Web API** project in Visual Studio. Refer to [Microsoft documentation](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-aspnet-core?view=vs-2022) for detailed instructions.
+ ///
+ /// Gets or sets the ticket title or subject.
+ ///
+ public string? Title { get; set; }
-**Step 2: Install SQL Client Package**
+ ///
+ /// Gets or sets a detailed description of the ticket.
+ ///
+ public string? Description { get; set; }
-* Install the **System.Data.SqlClient** package using **NuGet Package Manager**:
+ ///
+ /// Gets or sets the category of the issue (e.g., Network, Hardware, Software).
+ ///
+ public string? Category { get; set; }
- *Tools → NuGet Package Manager → Manage NuGet Packages for Solution*.
+ ///
+ /// Gets or sets the department responsible for handling the ticket.
+ ///
+ public string? Department { get; set; }
-* Alternatively, use the **Package Manager Console**:
+ ///
+ /// Gets or sets the name of the agent assigned to the ticket.
+ ///
+ public string? Assignee { get; set; }
-```powershell
-Install-Package System.Data.SqlClient
-```
+ ///
+ /// Gets or sets the name of the person who created the ticket.
+ ///
+ public string? CreatedBy { get; set; }
-**Step 3: Add API Controller**
+ ///
+ /// Gets or sets the current status of the ticket (e.g., Open, In Progress, Resolved, Closed).
+ ///
+ public string? Status { get; set; }
-Create a controller named **GridController.cs** under the Controllers folder. Implement logic to fetch data from SQL Server and return it as a collection of **Order** objects.
+ ///
+ /// Gets or sets the priority level of the ticket (e.g., Critical, High, Medium, Low).
+ ///
+ public string? Priority { get; set; }
-{% tabs %}
-{% highlight razor tabtitle="GridController.cs" %}
-using Microsoft.AspNetCore.Mvc;
-using System.Data;
-using System.Data.SqlClient;
-using Syncfusion.Blazor;
-using Syncfusion.Blazor.Data;
-using System.ComponentModel.DataAnnotations;
-using Newtonsoft.Json;
-namespace MyWebService.Controllers
-{
- [ApiController]
- public class GridController : ControllerBase
- {
- public static List Orders { get; set; }
+ ///
+ /// Gets or sets the deadline for responding to the ticket.
+ ///
+ public DateTime? ResponseDue { get; set; }
- public class Order
- {
- [Key]
- public int? OrderID { get; set; }
- public string? CustomerID { get; set; }
- public int? EmployeeID { get; set; }
- public decimal? Freight { get; set; }
- public string? ShipCity { get; set; }
- }
+ ///
+ /// Gets or sets the deadline for resolving the ticket.
+ ///
+ public DateTime? DueDate { get; set; }
- [Route("api/[controller]")]
- public List GetOrderData()
- {
- //TODO: Enter the connectionstring of database
- string ConnectionString = @"";
- string QueryStr = "SELECT * FROM dbo.Orders ORDER BY OrderID;";
- SqlConnection sqlConnection = new(ConnectionString);
- sqlConnection.Open();
- //Initialize the SqlCommand
- SqlCommand SqlCommand = new(QueryStr, sqlConnection);
- //Initialize the SqlDataAdapter
- SqlDataAdapter DataAdapter = new(SqlCommand);
- DataTable DataTable = new();
- // Using SqlDataAdapter, process the query string and fill the data into the dataset
- DataAdapter.Fill(DataTable);
- sqlConnection.Close();
- //Cast the data fetched from SqlDataAdapter to List
- var DataSource = (from DataRow Data in DataTable.Rows
- select new Order()
- {
- OrderID = Convert.ToInt32(Data["OrderID"]),
- CustomerID = Data["CustomerID"].ToString(),
- EmployeeID = Convert.IsDBNull(Data["EmployeeID"]) ? 0 : Convert.ToUInt16(Data["EmployeeID"]),
- ShipCity = Data["ShipCity"].ToString(),
- Freight = Convert.ToDecimal(Data["Freight"])
- }).ToList();
- return DataSource;
- }
+ ///
+ /// Gets or sets the timestamp indicating when the ticket was created.
+ ///
+ public DateTime? CreatedAt { get; set; }
+
+ ///
+ /// Gets or sets the timestamp indicating when the ticket was last updated.
+ ///
+ public DateTime? UpdatedAt { get; set; }
}
}
-{% endhighlight %}
-{% endtabs %}
-
-**Step 4: Run and test the API**
-
-Start the API and verify the endpoint (e.g., **https://localhost:xxxx/api/Grid**) returns data.
+```
-
+**Explanation:**
+- The `[Key]` attribute marks the `TicketId` property as the primary key (a unique identifier for each record).
+- Each property represents a column in the database table.
+- The `?` symbol indicates that a property is nullable (can be empty).
+- The model includes comprehensive XML documentation for each property.
-### Connecting Blazor DataGrid to an API service
+The data model has been successfully created.
-After creating and testing the API, configure the Syncfusion® Blazor DataGrid to bind data from the API endpoint using [UrlAdaptor](https://blazor.syncfusion.com/documentation/datagrid/connecting-to-adaptors/url-adaptor).
+### Step 4: Configure the DbContext
-**Prerequisites**
+A `DbContext` is a special class that manages the connection between the application and the SQL Server database. It handles all database operations such as saving, updating, deleting, and retrieving data.
-* [System requirements for Blazor components](https://blazor.syncfusion.com/documentation/system-requirements)
+**Instructions:**
-**Step 1: Create a Blazor Web App**
+1. Inside the `Data` folder, create a new file named **TicketsDbContext.cs**.
+2. Define the `TicketsDbContext` class with the following code:
-Create a Blazor Web App in Visual Studio 2022 using [Microsoft templates](https://learn.microsoft.com/en-us/aspnet/core/blazor/tooling?view=aspnetcore-10.0&pivots=vs) or the Syncfusion® Blazor Extension.
-Configure:
+```csharp
+using Microsoft.EntityFrameworkCore;
-* [Interactive render mode](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-10.0#render-modes)
-* [Interactivity location](https://learn.microsoft.com/en-us/aspnet/core/blazor/tooling?view=aspnetcore-10.0&pivots=vs)
+namespace Grid_MSSQL.Data
+{
+ ///
+ /// DbContext for Tickets entity
+ /// Manages database connections and entity configurations for the Network Support Ticket System
+ ///
+ public class TicketsDbContext : DbContext
+ {
+ public TicketsDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
-**Step 2: Install Syncfusion Packages**
+ ///
+ /// DbSet for Ticket entities
+ ///
+ public DbSet Tickets => Set();
-1. Open **NuGet Package Manager** in Visual Studio:
+ ///
+ /// Configures the entity mappings and constraints
+ ///
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
- *Tools → NuGet Package Manager → Manage NuGet Packages for Solution*.
+ // Configure Tickets entity
+ modelBuilder.Entity(entity =>
+ {
+ // Primary Key
+ entity.HasKey(e => e.TicketId);
+
+ // Auto-increment for Primary Key
+ entity.Property(e => e.TicketId)
+ .ValueGeneratedOnAdd();
+
+ // Column configurations
+ entity.Property(e => e.PublicTicketId)
+ .HasMaxLength(50)
+ .IsRequired(true);
+
+ entity.Property(e => e.Title)
+ .HasMaxLength(200)
+ .IsRequired(false);
+
+ entity.Property(e => e.Description)
+ .HasMaxLength(int.MaxValue) // For MAX type
+ .IsRequired(false);
+
+ entity.Property(e => e.Category)
+ .HasMaxLength(100)
+ .IsRequired(false);
+
+ entity.Property(e => e.Department)
+ .HasMaxLength(100)
+ .IsRequired(false);
+
+ entity.Property(e => e.Assignee)
+ .HasMaxLength(100)
+ .IsRequired(false);
+
+ entity.Property(e => e.CreatedBy)
+ .HasMaxLength(100)
+ .IsRequired(false);
+
+ entity.Property(e => e.Status)
+ .HasMaxLength(50)
+ .IsRequired(false)
+ .HasDefaultValue("Open");
+
+ entity.Property(e => e.Priority)
+ .HasMaxLength(50)
+ .IsRequired(false)
+ .HasDefaultValue("Medium");
+
+ // DateTime columns
+ entity.Property(e => e.ResponseDue)
+ .HasColumnType("datetime2")
+ .IsRequired(false);
+
+ entity.Property(e => e.DueDate)
+ .HasColumnType("datetime2")
+ .IsRequired(false);
+
+ entity.Property(e => e.CreatedAt)
+ .HasColumnType("datetime2")
+ .IsRequired(false)
+ .HasDefaultValueSql("GETDATE()");
+
+ entity.Property(e => e.UpdatedAt)
+ .HasColumnType("datetime2")
+ .IsRequired(false)
+ .HasDefaultValueSql("GETDATE()");
+
+ // Add indexes for frequently queried columns
+ entity.HasIndex(e => e.PublicTicketId)
+ .HasDatabaseName("IX_PublicTicketId");
+
+ entity.HasIndex(e => e.Status)
+ .HasDatabaseName("IX_Status");
+
+ entity.HasIndex(e => e.CreatedAt)
+ .HasDatabaseName("IX_CreatedAt");
+
+ // Table name and schema
+ entity.ToTable("Tickets", schema: "dbo");
+ });
+ }
+ }
+}
+```
-2. Search and install the following packages:
+**Explanation:**
+- The `DbContext` class inherits from Entity Framework's `DbContext` base class.
+- The `Tickets` property represents the `Tickets` table in the database.
+- The `OnModelCreating` method configures how the database columns should behave (maximum length, required/optional, default values, data types, indexes, etc.).
+- SQL Server-specific configurations include `datetime2` for timestamp columns and `GETDATE()` for default values.
+- Database indexes are configured for improved query performance on frequently accessed columns.
- - [Syncfusion.Blazor.Grid](https://www.nuget.org/packages/Syncfusion.Blazor.Grid/)
- - [Syncfusion.Blazor.Themes](https://www.nuget.org/packages/Syncfusion.Blazor.Themes/)
+The **TicketsDbContext** class is required because:
-3. For projects using **WebAssembly** or **Auto** interactive render modes, ensure these packages are installed in the **Client** project.
+- It **connects** the application to the database.
+- It **manages** all database operations.
+- It **maps** C# models to actual database tables.
+- It **configures** how data should look inside the database.
+- It **enables** SQL Server-specific features like indexes and default value functions.
-4. Alternatively, use the **Package Manager Console**:
+Without this class, Entity Framework Core will not know where to save data or how to create the Tickets table. The DbContext has been successfully configured.
-```powershell
-Install-Package Syncfusion.Blazor.Grid -Version {{ site.releaseversion }}
-Install-Package Syncfusion.Blazor.Themes -Version {{ site.releaseversion }}
-```
+### Step 5: Configure the Connection String
-N> Syncfusion® Blazor components are available on [nuget.org](https://www.nuget.org/packages?q=syncfusion.blazor). For a complete list of packages, refer to [NuGet packages](https://blazor.syncfusion.com/documentation/nuget-packages).
+A connection string contains the information needed to connect the application to the SQL Server database, including the server address, database name, and authentication credentials.
-**Step 3: Register Syncfusion Blazor service**
+**Instructions:**
-1. Add the required namespaces in **~/_Imports.razor**:
+1. Open the `appsettings.json` file in the project root.
+2. Add or update the `ConnectionStrings` section with the SQL Server connection details:
-```cshtml
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Grids
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Data Source=CustomSQLServer;Initial Catalog=NetworkSupportDB;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
```
-2. For apps using **WebAssembly** or **Auto** (Server and WebAssembly) render modes, register the service in both **~/Program.cs** files.
-
-```cshtml
-using Syncfusion.Blazor;
-
-builder.Services.AddSyncfusionBlazor();
+**Connection String Components:**
-```
+| Component | Description |
+|-----------|-------------|
+| Data Source | The address of the SQL Server instance (server name, IP address, or localhost) |
+| Initial Catalog | The database name (in this case, `NetworkSupportDB`) |
+| Integrated Security | Set to `True` for Windows Authentication; use `False` with Username/Password for SQL Authentication |
+| Connect Timeout | Connection timeout in seconds (default is 15) |
+| Encrypt | Enables encryption for the connection (set to `True` for production environments) |
+| Trust Server Certificate | Whether to trust the server certificate (set to `False` for security) |
+| Application Intent | Set to `ReadWrite` for normal operations or `ReadOnly` for read-only scenarios |
+| Multi Subnet Failover | Used in failover clustering scenarios (typically `False`) |
-**Step 4: Add stylesheet and script resources**
+The database connection string has been configured successfully.
-Include theme and script references in **App.razor**:
+### Step 6: Create the Repository Class
-```html
-
-
-
+A repository class is an intermediary layer that handles all database operations. This class uses Entity Framework Core to communicate with the database.
-
-
-
-```
+**Instructions:**
-N>
-* Refer to [Blazor Themes](https://blazor.syncfusion.com/documentation/appearance/themes) for various methods to reference themes in a Blazor application:
+1. Inside the `Data` folder, create a new file named **TicketRepository.cs**.
+2. Define the **TicketRepository** class with the following code:
- * [Static Web Assets](https://blazor.syncfusion.com/documentation/appearance/themes#static-web-assets)
- * [CDN](https://blazor.syncfusion.com/documentation/appearance/themes#cdn-reference)
- * [Custom Resource Generator (CRG)](https://blazor.syncfusion.com/documentation/common/custom-resource-generator)
+```csharp
+using Microsoft.EntityFrameworkCore;
-* For script reference options, see [Adding Script References](https://blazor.syncfusion.com/documentation/common/adding-script-references).
-* Set the render mode to **InteractiveServer** or **InteractiveAuto** in the Blazor Web App configuration.
+namespace Grid_MSSQL.Data
+{
+ ///
+ /// Repository pattern implementation for Tickets entity using Entity Framework Core
+ /// Handles all CRUD operations and business logic for network support tickets
+ ///
+ public class TicketRepository
+ {
+ private readonly TicketsDbContext _context;
-**Step 5: Configure DataGrid with UrlAdaptor**
+ public TicketRepository(TicketsDbContext context)
+ {
+ _context = context;
+ }
-Use [DataManager](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.SfDataManager.html) to connect the DataGrid to the API endpoint and set the [Adaptor](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Adaptors.html) property to [Adaptors.UrlAdaptor](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Adaptors.html#Syncfusion_Blazor_Adaptors_UrlAdaptor).
+ ///
+ /// Retrieves all tickets from the database ordered by ID descending
+ ///
+ /// List of all tickets
+ public async Task> GetTicketsDataAsync()
+ {
+ try
+ {
+ return await _context.Tickets
+ .OrderByDescending(t => t.TicketId)
+ .ToListAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error retrieving tickets: {ex.Message}");
+ throw;
+ }
+ }
-{% tabs %}
-{% highlight razor tabtitle="Index.razor" %}
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor
-@using Microsoft.Data.SqlClient;
-
-
-
-
-
-
-
-
-
- @{
- var aggregate = (context as AggregateTemplateContext);
-
-
Sum: @aggregate.Sum
-
- }
-
-
-
-
-
-
-
-
- @{
- var aggregate = (context as AggregateTemplateContext);
-
-
Average: @aggregate.Average
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
+ ///
+ /// Adds a new ticket to the database
+ ///
+ /// The ticket model to add
+ public async Task AddTicketAsync(Tickets value)
+ {
+ // Handle logic to add a new ticket to the database
+ }
-@code {
- SfGrid Grid { get; set; }
- public List Orders { get; set; }
+ ///
+ /// Updates an existing ticket
+ ///
+ /// The ticket model with updated values
+ public async Task UpdateTicketAsync(Tickets value)
+ {
+ // Handle logic to update an existing ticket to the database
+ }
- public class Order
- {
- public int? OrderID { get; set; }
- public string CustomerID { get; set; }
- public int EmployeeID { get; set; }
- public decimal Freight { get; set; }
- public string ShipCity { get; set; }
- }
-}
-{% endhighlight %}
-{% highlight c# tabtitle="GridController.cs" %}
- public class GridController : ControllerBase
- {
///
- /// Returns the data collection as result and count after performing data operations based on request from
+ /// Deletes a ticket from the database
///
- /// DataManagerRequest contains the information regarding searching, filtering, sorting, aggregates and paging which is handled on the Blazor DataGrid component side
- /// The data collection's type is determined by how this method has been implemented.
- [HttpPost]
- [Route("api/[controller]")]
- public object Post([FromBody] DataManagerRequest DataManagerRequest)
+ /// The ticket ID to delete
+ public async Task RemoveTicketAsync(int? key)
{
- IEnumerable DataSource = GetOrderData();
- int TotalRecordsCount = DataSource.Cast().Count();
- return new { result = DataSource, count = TotalRecordsCount };
+ // Handle logic to delete an existing ticket to the database
}
}
-{% endhighlight %}
-{% endtabs %}
-
-
+}
+```
-### Perform data operations in UrlAdaptor
+The repository class has been created.
-When `UrlAdaptor` is used, the Syncfusion® Blazor DataGrid sends operation details to the API through a [DataManagerRequest](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManagerRequest.html) object. These details can be applied to the data source using methods from the [DataOperations](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html) class.
+### Step 7: Register Services in Program.cs
-**Common Methods in DataOperations**
+The `Program.cs` file is where application services are registered and configured. This file must be updated to enable Entity Framework Core and the repository pattern.
-* [PerformSearching](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSearching__1_System_Linq_IQueryable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_SearchFilter__) - Applies search criteria to the collection.
-* [PerformFiltering](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformFiltering__1_System_Linq_IQueryable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_WhereFilter__System_String_) - Filters data based on conditions.
-* [PerformSorting](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSorting__1_System_Linq_IQueryable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_Sort__) - Sorts data by one or more fields.
-* [PerformSkip](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSkip__1_System_Linq_IQueryable___0__System_Int32_) - Skips a defined number of records for paging.
-* [PerformTake](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformTake__1_System_Linq_IQueryable___0__System_Int32_) - Retrieves a specified number of records for paging.
-* [PerformAggregation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.DataUtil.html#Syncfusion_Blazor_Data_DataUtil_PerformAggregation_System_Collections_IEnumerable_System_Collections_Generic_List_Syncfusion_Blazor_Data_Aggregate__) – Calculates aggregate values such as Sum, Average, Min, and Max.
+**Instructions:**
-These methods enable efficient handling of large datasets by performing operations on the server side. The following sections demonstrate how to manage these operations using the `UrlAdaptor`.
+1. Open the `Program.cs` file at the project root.
+2. Add the following code after the line `var builder = WebApplication.CreateBuilder(args);`:
-> * To enable these operations, add the **Syncfusion.Blazor.Data** package to the API service project using NuGet Package Manager in Visual Studio (*Tools → NuGet Package Manager → Manage NuGet Packages for Solution*).
+```csharp
+using Grid_MSSQL.Components;
+using Grid_MSSQL.Data;
+using Syncfusion.Blazor;
+using Microsoft.EntityFrameworkCore;
-### Handling searching operation
+var builder = WebApplication.CreateBuilder(args);
-When using `UrlAdaptor`, server-side searching can be implemented by applying the [PerformSearching](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSearching__1_System_Collections_Generic_IEnumerable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_SearchFilter__) method from the `DataOperations` class. This method applies search criteria from the `DataManagerRequest` object to the data collection.
+// Add services to the container.
+builder.Services.AddRazorComponents()
+ .AddInteractiveServerComponents();
+builder.Services.AddSyncfusionBlazor();
-{% highlight razor %}
+// ========== ENTITY FRAMEWORK CORE CONFIGURATION ==========
+// Get connection string from appsettings.json
+var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
-[HttpPost]
-[Route("api/[controller]")]
-public object Post([FromBody] DataManagerRequest DataManagerRequest)
+if (string.IsNullOrEmpty(connectionString))
{
- IEnumerable DataSource = GetOrderData();
- // Handling Searching in UrlAdaptor.
- if (DataManagerRequest.Search != null && DataManagerRequest.Search.Count > 0)
- {
- // Searching
- DataSource = DataOperations.PerformSearching(DataSource, DataManagerRequest.Search);
- //Add custom logic here if needed and remove above method
- }
- int TotalRecordsCount = DataSource.Cast().Count();
- return new { result = DataSource, count = TotalRecordsCount };
+ throw new InvalidOperationException("Connection string 'DefaultConnection' not found in configuration.");
}
-{% endhighlight %}
-**Key Points**
+// Register DbContext with SQL Server provider
+builder.Services.AddDbContext(options =>
+{
+ options.UseSqlServer(connectionString);
-* `request.Search` contains search descriptors sent from the DataGrid.
-* `PerformSearching` applies these descriptors to the collection.
-* Custom logic can be added if required before or after applying the built-in method.
+ // Enable detailed error messages in development
+ if (builder.Environment.IsDevelopment())
+ {
+ options.EnableSensitiveDataLogging();
+ }
+});
-### Handling filtering operation
+// Register Repository for dependency injection
+builder.Services.AddScoped();
+// ========================================================
-When using `UrlAdaptor`, server-side filtering can be implemented by applying the [PerformFiltering](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformFiltering__1_System_Collections_Generic_IEnumerable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_WhereFilter__System_String_) method from the `DataOperations` class. This method applies filter conditions from the `DataManagerRequest` object to the data collection.
+var app = builder.Build();
-{% highlight razor %}
-[HttpPost]
-[Route("api/[controller]")]
-public object Post([FromBody] DataManagerRequest DataManagerRequest)
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
{
- IEnumerable DataSource = GetOrderData();
- // Handling Filtering in UrlAdaptor.
- if (DataManagerRequest.Where != null && DataManagerRequest.Where.Count > 0)
- {
- // Filtering
- DataSource = DataOperations.PerformFiltering(DataSource, DataManagerRequest.Where, DataManagerRequest.Where[0].Operator);
- //Add custom logic here if needed and remove above method
- }
- int TotalRecordsCount = DataSource.Cast().Count();
- return new { result = DataSource, count = TotalRecordsCount };
+ app.UseExceptionHandler("/Error", createScopeForErrors: true);
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
}
-{% endhighlight %}
+app.UseHttpsRedirection();
-**Key Points**
+app.UseAntiforgery();
-* `request.Where` contains filter descriptors sent from the DataGrid.
-* `PerformFiltering` applies these descriptors to the collection.
-* The `Operator` property determines how conditions are combined (e.g., AND, OR).
-* Custom logic can be added before or after applying the built-in method.
+app.MapStaticAssets();
+app.MapRazorComponents()
+ .AddInteractiveServerRenderMode();
+app.Run();
+```
-### Handling sorting operation
+**Explanation:**
-When using `UrlAdaptor`, server-side sorting can be implemented by applying the [PerformSorting](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSorting__1_System_Collections_Generic_IEnumerable___0__System_Collections_Generic_List_Syncfusion_Blazor_Data_Sort__) method from the `DataOperations` class. This method applies sort descriptors from the `DataManagerRequest` object to the data collection.
+- **`AddDbContext`**: Registers the DbContext with SQL Server as the database provider using `UseSqlServer()`.
+- **`EnableSensitiveDataLogging()`**: Enabled in development to log detailed information about database operations (useful for debugging).
+- **`AddScoped`**: Registers the repository as a scoped service, creating a new instance for each HTTP request.
+- **`AddSyncfusionBlazor()`**: Registers Syncfusion Blazor components.
+- **`AddRazorComponents()` and `AddInteractiveServerComponents()`**: Enables Blazor server-side rendering with interactive components.
-{% highlight razor %}
-[HttpPost]
-[Route("api/[controller]")]
-public object Post([FromBody] DataManagerRequest DataManagerRequest)
-{
- IEnumerable DataSource = GetOrderData();
- // Handling Sorting in UrlAdaptor.
- if (DataManagerRequest.Sorted != null && DataManagerRequest.Sorted.Count > 0)
- {
- // Sorting
- DataSource = DataOperations.PerformSorting(DataSource, DataManagerRequest.Sorted);
- //Add custom logic here if needed and remove above method
- }
- int TotalRecordsCount = DataSource.Cast().Count();
- return new { result = DataSource, count = TotalRecordsCount };
-}
-{% endhighlight %}
-
-**Key Points**
-
-* `request.Sorted` contains sort descriptors sent from the DataGrid.
-* `PerformSorting` applies these descriptors to the collection.
-* Supports multiple sort fields and directions (**ascending** or **descending**).
-* Custom sorting logic can be added before or after applying the built-in method.
-
-### Handling aggregate operation
-
-Enable server-side aggregation by implementing logic in the API controller using the [PerformAggregation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.DataUtil.html#Syncfusion_Blazor_Data_DataUtil_PerformAggregation_System_Collections_IEnumerable_System_Collections_Generic_List_Syncfusion_Blazor_Data_Aggregate__) method from the [DataUtil](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.DataUtil.html) class. This method calculates aggregate values such as **Sum**, **Average**, **Min**, and **Max** for the specified fields based on the incoming [DataManagerRequest](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManagerRequest.html).
-
-{% highlight razor %}
- [HttpPost]
- [Route("api/[controller]")]
- public object Post([FromBody] DataManagerRequest DataManagerRequest)
- {
- IEnumerable DataSource = GetOrderData();
- int TotalRecordsCount = DataSource.Cast().Count();
- // Handling Aggregation in UrlAdaptor.
- IDictionary Aggregates = null;
- if (DataManagerRequest.Aggregates != null)
- {
- // Aggregation
- Aggregates = DataUtil.PerformAggregation(DataSource, DataManagerRequest.Aggregates);
- //Add custom logic here if needed and remove above method
- }
- return new { result = DataSource, count = TotalRecordsCount, aggregates = Aggregates };
- }
-{% endhighlight %}
+The service registration has been completed successfully.
-> The server-side implementation of the `PerformAggregation` method is required only for [Footer aggregates](https://blazor.syncfusion.com/documentation/datagrid/footer-aggregate). Explicit handling is not necessary for[ Group Footer aggregates](https://blazor.syncfusion.com/documentation/datagrid/group-and-caption-aggregate#group-footer-aggregates) or [Group Caption aggregates](https://blazor.syncfusion.com/documentation/datagrid/group-and-caption-aggregate#group-caption-aggregates).
+---
-### Handling Aggregation Operation
+## Integrating Syncfusion Blazor DataGrid
-When using `UrlAdaptor`, server-side aggregation can be implemented by applying the [PerformAggregation](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.DataUtil.html#Syncfusion_Blazor_Data_DataUtil_PerformAggregation_System_Collections_IEnumerable_System_Collections_Generic_List_Syncfusion_Blazor_Data_Aggregate__) method from the DataUtil class. This method calculates aggregate values such as **Sum**, **Average**, **Min**, and **Max** based on the configuration in the `DataManagerRequest` object.
+### Step 1: Install and Configure Blazor DataGrid Components
-{% highlight razor %}
-[HttpPost]
-[Route("api/[controller]")]
-public object Post([FromBody] DataManagerRequest request)
-{
- IEnumerable dataSource = GetOrderData();
- int count = dataSource.Count();
+Syncfusion is a library that provides pre-built UI components like DataGrid, which is used to display data in a table format.
- // Apply aggregation
- IDictionary aggregates = null;
- if (request.Aggregates != null)
- {
- aggregates = DataUtil.PerformAggregation(dataSource, request.Aggregates);
- }
+**Instructions:**
- return new { result = dataSource, count, aggregates };
-}
-{% endhighlight %}
+1. The Syncfusion.Blazor.Grids package was installed in **Step 2** of the previous heading.
+2. Import the required namespaces in the `Components/_Imports.razor` file:
-**Key Points**
+```csharp
+@using Syncfusion.Blazor.Grids
+@using Syncfusion.Blazor.Data
+```
-* `request.Aggregates` contains aggregate descriptors sent from the DataGrid.
-* `PerformAggregation` calculates summary values for specified fields.
-* The server-side implementation of the `PerformAggregation` method is required only for [Footer aggregates](https://blazor.syncfusion.com/documentation/datagrid/footer-aggregate).
-* [Group Footer aggregates](https://blazor.syncfusion.com/documentation/datagrid/group-and-caption-aggregate#group-footer-aggregates) and [Group Caption aggregates](https://blazor.syncfusion.com/documentation/datagrid/group-and-caption-aggregate#group-caption-aggregates) are calculated automatically by the DataGrid and do not require explicit handling.
+3. Add the Syncfusion stylesheet and scripts in the `Components/App.razor` file. Find the `` section and add:
-### Handling paging operation
+```html
+
+
-When using `UrlAdaptor`, server-side paging can be implemented by applying the [PerformSkip](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformSkip__1_System_Collections_Generic_IEnumerable___0__System_Int32_) and [PerformTake](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html#Syncfusion_Blazor_DataOperations_PerformTake__1_System_Collections_Generic_IEnumerable___0__System_Int32_) methods from the `DataOperations` class. These methods apply paging details from the `DataManagerRequest` object to the data collection.
+
+
+```
+For this project, the tailwind3 theme is used. A different theme can be selected or the existing theme can be customized based on project requirements. Refer to the [Syncfusion Blazor Components Appearance](https://blazor.syncfusion.com/documentation/appearance/themes) documentation to learn more about theming and customization options.
-{% highlight razor %}
-[HttpPost]
-[Route("api/[controller]")]
-public object Post([FromBody] DataManagerRequest DataManagerRequest)
-{
- IEnumerable DataSource = GetOrderData();
- int TotalRecordsCount = DataSource.Cast().Count();
- // Handling Paging in UrlAdaptor.
- if (DataManagerRequest.Skip != 0)
- {
- // Paging
- DataSource = DataOperations.PerformSkip(DataSource, DataManagerRequest.Skip);
- //Add custom logic here if needed and remove above method
- }
- if (DataManagerRequest.Take != 0)
- {
- DataSource = DataOperations.PerformTake(DataSource, DataManagerRequest.Take);
- //Add custom logic here if needed and remove above method
- }
- return new { result = DataSource, count = TotalRecordsCount };
-}
-{% endhighlight %}
-
-**Key Points**
-
-* **request.Skip** specifies the number of records to skip.
-* **request.Take** specifies the number of records to retrieve.
-* **PerformSkip** and **PerformTake** enable efficient server-side paging.
-* Custom paging logic can be added before or after applying these methods.
-
-> **Best Practice**:
-For optimal performance, apply operations in the following sequence on the server side:
-**Searching → Filtering → Sorting → Aggregation → Paging**
-
-### Handling CRUD operations Using UrlAdaptor
-
-The Syncfusion® Blazor DataGrid supports **Create**, **Read**, **Update**, and **Delete** (**CRUD**) operations through the [DataManager](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.SfDataManager.html) component. API endpoints for these operations are mapped using properties such as:
-
-* [InsertUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_InsertUrl) – API endpoint for inserting new records.
-* [UpdateUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_UpdateUrl) – API endpoint for updating existing records.
-* [RemoveUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_UpdateUrl) – API endpoint for deleting records.
-* [CrudUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_CrudUrl) – Single endpoint for all CRUD operations.
-* [BatchUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_BatchUrl) – API endpoint for batch editing.
-
-To enable [editing](https://blazor.syncfusion.com/documentation/datagrid/editing), configure the [Toolbar](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_Toolbar) and [GridEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridEditSettings.html) properties. Set the [Mode](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridEditSettings.html#Syncfusion_Blazor_Grids_GridEditSettings_Mode) property to [EditMode.Normal](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.EditMode.html#Syncfusion_Blazor_Grids_EditMode_Normal) for inline editing.
-
-{% tabs %}
-{% highlight razor %}
-
-
-
-
-
-
-
-
-
-
-
-{% endhighlight %}
-{% endtabs %}
+Syncfusion components are now configured and ready to use. For additional guidance, refer to the Grid component's [getting‑started](https://blazor.syncfusion.com/documentation/datagrid/getting-started-with-web-app) documentation.
-> * Set [IsPrimaryKey](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridColumn.html#Syncfusion_Blazor_Grids_GridColumn_IsPrimaryKey) to **true** for a column that contains unique values.
-> * If the database includes an **auto-generated column**, set [IsIdentity](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridColumn.html#Syncfusion_Blazor_Grids_GridColumn_IsIdentity) for that column to disable editing during **add** or **update** operations.
+### Step 2: Update the Blazor DataGrid
-**Insert Operation:**
+The `Home.razor` component will display the ticket data in a Syncfusion Blazor DataGrid with search, filter, sort, and pagination capabilities.
-To insert a new record, click **Add** in the toolbar. After entering the required values, click **Update**. This action inserts the record into the **Orders** table by calling a **POST** API method:
+**Instructions:**
-{% tabs %}
-{% highlight c# tabtitle="OrdersController.cs" %}
-[HttpPost]
-[Route("api/Grid/Insert")]
-///
-/// Inserts a new data item into the data collection.
-///
-/// The set of information along with new record detail which is need to be inserted.
-/// Returns void
-public void Insert([FromBody] CRUDModel Value)
-{
- //TODO: Enter the connectionstring of database
- string ConnectionString = @"";
- //Create query to insert the specific into the database by accessing its properties
- string Query = $"Insert into Orders(CustomerID,Freight,ShipCity,EmployeeID) values('{Value.Value.CustomerID}','{Value.Value.Freight}','{Value.Value.ShipCity}','{Value.Value.EmployeeID}')";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
+1. Open the file named `Home.razor` in the `Components/Pages` folder.
+2. Add the following code to create a basic DataGrid:
+
+```cshtml
+@page "/"
+@rendermode InteractiveServer
+@inject TicketRepository TicketService
+
+Network Support Ticket System
+
+
+
+
Network Support Ticket System
+
Manage and view all support tickets from the database.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+ // CustomAdaptor class will be added in the next step
}
-{% endhighlight %}
-{% endtabs %}
+```
-**Update Operation:**
+**Component Explanation:**
-To update an existing record, select the row and click **Edit** in the toolbar. Modify the required values in the edit form, then click **Update**. This action updates the record in the Orders table by calling a **POST** API method:
+- **`@rendermode InteractiveServer`**: Enables interactive server-side rendering for the component.
+- **`@inject TicketRepository`**: Injects the repository to access database methods.
+- **``**: The DataGrid component that displays data in rows and columns.
+- **``**: Defines individual columns in the DataGrid.
+- **``**: Configures pagination with 10 records per page.
-{% tabs %}
-{% highlight c# tabtitle="OrdersController.cs" %}
-[HttpPost]
-[Route("api/Grid/Update")]
-///
-/// Update a existing data item from the data collection.
-///
-/// The set of information along with updated record detail which is need to be updated.
-/// Returns void
-public void Update([FromBody] CRUDModel Value)
-{
- //TODO: Enter the connectionstring of database
- string ConnectionString = @"";
- //Create query to update the changes into the database by accessing its properties
- string Query = $"Update Orders set CustomerID='{Value.Value.CustomerID}', Freight='{Value.Value.Freight}',EmployeeID='{Value.Value.EmployeeID}',ShipCity='{Value.Value.ShipCity}' where OrderID='{Value.Value.OrderID}'";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- //Execute the SQL Command
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
-}
-{% endhighlight %}
-{% endtabs %}
+The Home component has been updated successfully with DataGrid.
-**Delete Operation:**
+---
-To delete a record, select the row and click **Delete** in the toolbar. This action removes the record from the Orders table by calling a **POST** API method:
+### Step 3: Implement the CustomAdaptor
-{% tabs %}
-{% highlight c# tabtitle="OrdersController.cs" %}
- [HttpPost]
-[Route("api/Grid/Delete")]
-///
-/// Remove a specific data item from the data collection.
-///
-/// The set of information along with specific record detail which is need to be removed.
-/// Returns void
-public void Delete([FromBody] CRUDModel Value)
-{
- //TODO: Enter the connectionstring of database
- string ConnectionString = @"";
- //Create query to remove the specific from database by passing the primary key column value.
- string Query = $"Delete from Orders where OrderID={Value.Key}";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- //Execute the SQL Command
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
-}
-{% endhighlight %}
-{% endtabs %}
+The Syncfusion® Blazor DataGrid can bind data from a **SQL Server** database using [DataManager](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.SfDataManager.html) and set the [Adaptor](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Adaptors.html) property to [CustomAdaptor](https://blazor.syncfusion.com/documentation/datagrid/connecting-to-adaptors/custom-adaptor) for scenarios that require full control over data operations.
-**Batch Operation:**
+The `CustomAdaptor` is a bridge between the DataGrid and the database. It handles all data operations including reading, searching, filtering, sorting, paging, and CRUD operations. Each operation in the CustomAdaptor's `ReadAsync` method handles specific grid functionality. The Syncfusion® Blazor DataGrid sends operation details to the API through a [DataManagerRequest](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManagerRequest.html) object. These details can be applied to the data source using methods from the [DataOperations](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataOperations.html) class.
-To perform batch updates, set the edit [Mode](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridEditSettings.html#Syncfusion_Blazor_Grids_GridEditSettings_Mode) to **Batch** in the [GridEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridEditSettings.html) component and configure the [BatchUrl](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.DataManager.html#Syncfusion_Blazor_DataManager_BatchUrl) property in the [DataManager](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Data.SfDataManager.html).
-In batch mode:
+**Instructions:**
-- Use the **Add** toolbar button to insert new rows.
-- Double-click a cell to edit its value.
-- Select a row and click **Delete** to remove it.
-- Click **Update** to commit all changes (insert, update, delete) in a single request from the Orders table using a single API **POST** request.
+1. Open the `Components/Pages/Home.razor` file.
+2. Add the following `CustomAdaptor` class code inside the `@code` block:
-{% highlight razor %}
- [HttpPost]
-[Route("api/Grid/Batch")]
-///
-/// Batchupdate (Insert, Update, Delete) a collection of data items from the data collection.
-///
-/// The set of information along with details about the CRUD actions to be executed from the database.
-/// Returns void
-public void Batch([FromBody] CRUDModel Value)
-{
- //TODO: Enter the connectionstring of database
- string ConnectionString = @"";
- if (Value.Changed != null)
- {
- foreach (var Record in (IEnumerable)Value.Changed)
- {
- //Create query to update the changes into the database by accessing its properties
- string Query = $"Update Orders set CustomerID='{Record.CustomerID}', Freight='{Record.Freight}',EmployeeID='{Record.EmployeeID}',ShipCity='{Record.ShipCity}' where OrderID='{Record.OrderID}'";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- //Execute the SQL Command
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
- }
+```csharp
+@code {
- }
- if (Value.Added != null)
+ private CustomAdaptor? _customAdaptor;
+
+ protected override void OnInitialized()
{
- foreach (var Record in (IEnumerable)Value.Added)
- {
- //Create query to insert the specific into the database by accessing its properties
- string Query = $"Insert into Orders(CustomerID,Freight,ShipCity,EmployeeID) values('{Record.CustomerID}','{Record.Freight}','{Record.ShipCity}','{Record.EmployeeID}')";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- //Execute the SQL Command
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
- }
+ // Initialize the CustomAdaptor with the injected TicketRepository
+ _customAdaptor = new CustomAdaptor { TicketService = TicketService };
}
- if (Value.Deleted != null)
+
+ ///
+ /// CustomAdaptor class bridges DataGrid interactions with database operations.
+ /// This adaptor handles all data retrieval and manipulation for the DataGrid.
+ ///
+ public class CustomAdaptor : DataAdaptor
{
- foreach (var Record in (IEnumerable)Value.Deleted)
+ public static TicketRepository? _ticketService { get; set; }
+
+ public TicketRepository? TicketService
{
- //Create query to remove the specific from database by passing the primary key column value.
- string Query = $"Delete from Orders where OrderID={Record.OrderID}";
- SqlConnection SqlConnection = new SqlConnection(ConnectionString);
- SqlConnection.Open();
- //Execute the SQL Command
- SqlCommand SqlCommand = new SqlCommand(Query, SqlConnection);
- //Execute this code to reflect the changes into the database
- SqlCommand.ExecuteNonQuery();
- SqlConnection.Close();
- //Add custom logic here if needed and remove above method
+ get => _ticketService;
+ set => _ticketService = value;
}
- }
-}
-{% endhighlight %}
-
+ ///
+ /// ReadAsync retrieves records from the database and applies data operations.
+ /// This method executes when the grid initializes and when filtering, searching, sorting, or paging occurs.
+ ///
+ public override async Task