@@ -791,10 +1146,10 @@ To bind GraphQL service data to the TreeGrid, provide the GraphQL query string u
Height="340"
RowHeight="40"
AllowPaging="true">
-
-
+
@@ -854,8 +1209,79 @@ To bind GraphQL service data to the TreeGrid, provide the GraphQL query string u
@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
+ // GraphQLAdaptorOptions will be added in the next step
+}
+```
+
+**Component Explanation:**
+
+- **`@rendermode InteractiveServer`**: Enables interactive server-side rendering for the component.
+- **`
`**: The TreeGrid component that displays data in rows and columns.
+- **``**: Defines individual columns in the TreeGrid.
+- **``**: Configures pagination with 10 records per page.
+- **``**: Enable editing functionality directly within the TreeGrid by setting the AllowEditing, AllowAdding, and AllowDeleting properties within the TreeGridEditSettings to **true**.
+
+The `SfDataManager` component connects the TreeGrid to the GraphQL backend using the adaptor options configured below:
+
+```razor
+
+
+```
+
+**Component Attributes Explained:**
+
+| Attribute | Purpose | Value |
+|-----------|---------|-------|
+| `Url` | GraphQL endpoint location | `http://localhost:7213/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) |
+
+**Important Notes:**
+
+- The `Url` must match the port configured in `launchSettings.json`.
+- If backend runs on port 7213, then URL must be `https://localhost:7213/graphql`.
+- The `/graphql` path is set by `app.MapGraphQL()` in Program.cs.
+
+---
+
+### Step 3: Configure GraphQL Adaptor and Data Binding
+
+The GraphQL adaptor is a bridge that connects the Syncfusion Blazor TreeGrid with the GraphQL backend. The adaptor translates TreeGrid operations (filtering, sorting, paging, searching) into GraphQL queries and mutations. When the user interacts with the tree grid, the adaptor automatically sends the appropriate GraphQL request to the backend, receives the response, and updates the tree 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 TreeGrid, 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 TreeGrid understands.
+- Updates the tree grid display with the new data.
+
+The adaptor enables bidirectional communication between the frontend (TreeGrid) 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 {
+
public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
private readonly List CategoryOptions = new()
@@ -916,8 +1342,8 @@ To bind GraphQL service data to the TreeGrid, provide the GraphQL query string u
private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
{
Query = HrQuery,
- // ResolverName should match the GraphQL field name (camelCase)
+ // ResolverName should match the GraphQL field name (camelCase)
ResolverName = "employeesData"
};
@@ -963,474 +1389,744 @@ To bind GraphQL service data to the TreeGrid, provide the GraphQL query string u
[JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
}
}
+```
-{% endhighlight %}
-{% endtabs %}
-
-Replace `https://localhost:xxxx/graphql` with the actual URL of the API endpoint that provides data in a consumable format (e.g., JSON).
-
-**Step 6: Enable CORS Policy**
+**GraphQL Query Structure Explained in Detail**
-To allow a Blazor application to access the GraphQL server, Cross-Origin Resource Sharing (CORS) must be enabled in the server application. Add the following code to the **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 the 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 the Blazor application can communicate with the GraphQL server without encountering CORS-related issues.
+**Line Breakdown:**
+- `query` - GraphQL keyword indicating a read operation
+- `employeesData` - 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
+query employeesData($dataManager: DataManagerRequestInput!) {}
+```
-After completing the setup, run the application. The TreeGrid 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:**
+- `employeesData(...)` - 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 {
+ employeeID
+ managerID
+ ...
+}
+```
+- `count` - Returns total number of records (used for pagination)
+ - Example: If 150 total expense records exist, count = 150
+ - TreeGrid 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 EmployeeData class
+ - Only requested fields are returned (no over-fetching)
-Before performing specific data operations such as search, sorting, or filtering, it is essential to understand the request structure that the Syncfusion® Blazor TreeGrid 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; }
-
- [GraphQLName("Take")]
- public int Take { get; set; }
+ "data": {
+ "employeesData": {
+ "count": 100,
+ "result": [
+ {
+ "employeeID": "EMP001",
+ "managerID": null,
+ "hasChild": true,
+ "name": "Ava Anderson",
+ "lastName": "Anderson",
+ "title": "Executive",
+ "location": "Seattle",
+ "dateJoined": "2016-01-05T00:00:00.000Z",
+ "salaryPerMonth": 15000,
+ "email": "ava.anderson001@company.com"
+ },
+ {
+ "employeeID": "EMP011",
+ "managerID": null,
+ "hasChild": true,
+ "name": "Mia Campbell",
+ "lastName": "Campbell",
+ "title": "Director",
+ "location": "Chicago",
+ "dateJoined": "2017-02-06T00:00:00.000Z",
+ "salaryPerMonth": 16200,
+ "email": "mia.campbell011@company.com"
+ }
+ ]
+ }
+ }
+}
+```
- [GraphQLName("RequiresCounts")]
- public bool RequiresCounts { get; set; } = false;
+**Response Structure Explanation:**
- [GraphQLName("Params")]
- [GraphQLType(typeof(AnyType))]
- public IDictionary Params { get; set; }
+| Part | Purpose | Example |
+|------|---------|---------|
+| `data` | Root object containing the query result | Always present in successful response |
+| `employeesData` | 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("Aggregates")]
- [GraphQLType(typeof(AnyType))]
- public List? Aggregates { get; set; }
+---
- [GraphQLName("Search")]
- public List? Search { get; set; }
+### Step 4: Add Toolbar with CRUD and search options
+
+The toolbar provides buttons for adding, editing, deleting records, and searching the data.
+
+**Instructions:**
+
+1. Open the `Components/Pages/Home.razor` file.
+2. Update the `` component to include the [Toolbar](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.TreeGrid.SfTreeGrid-1.html#Syncfusion_Blazor_TreeGrid_SfTreeGrid_1_Toolbar) property with CRUD and search options:
+
+```razor
+
+
+
+
+
+```
- [GraphQLName("Sorted")]
- public List? Sorted { get; set; }
+3. Add the toolbar items list in the `@code` block:
- [GraphQLName("Where")]
- [GraphQLType(typeof(AnyType))]
- public List? Where { get; set; }
+```csharp
+@code {
+ private List ToolbarItems = new List { "Add", "Edit", "Delete", "Update", "Cancel", "Search"};
+}
+```
- [GraphQLName("Group")]
- public List? Group { get; set; }
+### Step 5: Implement Paging Feature
+
+Paging divides large datasets into smaller pages to improve performance and usability.
+
+**Instructions:**
+
+1. The paging feature is already partially enabled in the `` component with [AllowPaging="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.TreeGrid.SfTreeGrid-1.html#Syncfusion_Blazor_TreeGrid_SfTreeGrid_1_AllowPaging).
+2. The page size is configured with [TreeGridPageSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.TreeGrid.TreeGridPageSettings.html).
+3. No additional code changes are required from the previous steps.
+
+
+```razor
+
+
+
+
+
+
+```
- [GraphQLName("antiForgery")]
- public string? antiForgery { get; set; }
+4. Update the `EmployeesData` method in the `GraphQLQuery` class to handle paging:
- [GraphQLName("Table")]
- public string? Table { get; set; }
+```csharp
+using System.Text.Json;
- [GraphQLName("IdMapping")]
- public string? IdMapping { get; set; }
+namespace TreeGrid_GraphQLAdaptor.Models
+{
+ // Defines the GraphQL resolver for handling TreeGrid requests.
+ public class GraphQLQuery
+ {
+ // SINGLE ENTRYPOINT: handles roots, children, expand/collapse, expandall, loadchildondemand, filtering, search, sort, paging
+ public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
+ {
+ EnsureDataLoaded();
- [GraphQLName("Select")]
- public List? Select { get; set; }
+ // Parent detection (params first, then ManagerID== in where)
+ string? parentId = TryGetParentIdFromParams(dataManager?.Params)
+ ?? TryGetParentIdFromWhere(dataManager?.Where);
- [GraphQLName("Expand")]
- public List? Expand { get; set; }
+ // CHILDREN SLICE: return only direct children of requested parent
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ var children = _data
+ .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
+ .ToList();
- [GraphQLName("Distinct")]
- public List? Distinct { get; set; }
+ return new EmployeesDataResponse
+ {
+ Count = children.Count,
+ Result = children,
+ Items = children
+ };
+ }
- [GraphQLName("ServerSideGroup")]
- public bool? ServerSideGroup { get; set; }
+ // ROOTS: proper root-only paging
+ var roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID)).ToList();
+ int total = roots.Count;
- [GraphQLName("LazyLoad")]
- public bool? LazyLoad { get; set; }
+ IEnumerable page = roots;
+ if (dataManager?.Skip is int sk && dataManager.Take is int tk)
+ {
+ page = page.Skip(sk).Take(tk);
+ }
- [GraphQLName("LazyExpandAllGroup")]
- public bool? LazyExpandAllGroup { get; set; }
- }
+ var list = page.ToList();
+ return new EmployeesDataResponse
+ {
+ Count = total,
+ Result = list,
+ Items = list
+ };
+ }
- ///
- /// Represents an aggregate operation in the data manager request.
- ///
- public class Aggregate
- {
- [GraphQLName("Field")]
- public string Field { get; set; }
+ private static List _data = EnsureDataInternal();
- [GraphQLName("Type")]
- public string Type { get; set; }
- }
+ private static List EnsureDataInternal() => EmployeeData.GetAllRecords();
- ///
- /// Represents a search filter in the data manager request.
- ///
- public class SearchFilter
- {
- [GraphQLName("Fields")]
- public List Fields { get; set; }
+ private static void EnsureDataLoaded()
+ {
+ if (_data == null || _data.Count == 0) _data = EnsureDataInternal();
+ }
+
+ private static string? TryGetParentIdFromParams(object? prms)
+ {
+ if (!TryReadFromParams(prms, "parentId", out var v) || v is null) return null;
+ return ToEmpId(v);
+ }
- [GraphQLName("Key")]
- public string Key { get; set; }
+ private static bool TryReadFromParams(object? prms, string key, out object? value)
+ {
+ value = null;
+ if (prms == null) return false;
- [GraphQLName("Operator")]
- public string Operator { get; set; }
+ // IDictionary
+ if (prms is IDictionary dictObj)
+ return dictObj.TryGetValue(key, out value);
- [GraphQLName("IgnoreCase")]
- public bool IgnoreCase { get; set; }
- }
+ // IReadOnlyDictionary
+ if (prms is IReadOnlyDictionary roDict)
+ return roDict.TryGetValue(key, out value);
- ///
- /// Represents a sorting operation in the data manager request.
- ///
- public class Sort
- {
- [GraphQLName("Name")]
- public string Name { get; set; }
+ // IDictionary
+ if (prms is IDictionary dictJson)
+ {
+ if (dictJson.TryGetValue(key, out var je)) { value = je; return true; }
+ return false;
+ }
+
+ // IEnumerable>
+ if (prms is IEnumerable> kvs)
+ {
+ foreach (var kv in kvs)
+ if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
+ { value = kv.Value; return true; }
+ }
+
+ // JsonElement object
+ if (prms is JsonElement jeObj && jeObj.ValueKind == JsonValueKind.Object)
+ {
+ if (jeObj.TryGetProperty(key, out var je))
+ { value = je; return true; }
+ }
+
+ return false;
+ }
+
+ private static string? TryGetParentIdFromWhere(List? where)
+ {
+ if (where == null || where.Count == 0) return null;
+
+ foreach (var wf in where)
+ {
+ if (!string.IsNullOrWhiteSpace(wf.Field) &&
+ wf.Field.Equals("ManagerID", StringComparison.OrdinalIgnoreCase))
+ {
+ var op = (wf.Operator ?? "equal").Trim().ToLowerInvariant();
+ if (op is "equal" or "eq")
+ {
+ if (wf.Value == null) return null;
+ return ToEmpId(wf.Value);
+ }
+ }
- [GraphQLName("Direction")]
- public string Direction { get; set; }
+ if (wf.Predicates != null && wf.Predicates.Count > 0)
+ {
+ var nested = TryGetParentIdFromWhere(wf.Predicates);
+ if (nested != null || wf.Value == null) return nested;
+ }
+ }
+ return null;
+ }
- [GraphQLName("Comparer")]
- [GraphQLType(typeof(AnyType))]
- public object Comparer { get; set; }
+ private static string? ToEmpId(object? v)
+ {
+ if (v == null) return null;
+ if (v is string s)
+ {
+ if (int.TryParse(s, out var n)) return $"EMP{n:000}";
+ return s;
+ }
+ if (v is int i) return $"EMP{i:000}";
+ if (v is long l && l >= int.MinValue && l <= int.MaxValue) return $"EMP{(int)l:000}";
+ if (v is JsonElement je)
+ {
+ return je.ValueKind switch
+ {
+ JsonValueKind.Number => je.TryGetInt32(out var j) ? $"EMP{j:000}" : null,
+ JsonValueKind.String => int.TryParse(je.GetString(), out var k) ? $"EMP{k:000}" : je.GetString(),
+ JsonValueKind.Null => null,
+ _ => null
+ };
+ }
+ return v.ToString();
+ }
}
- ///
- /// Represents a filter condition in the data manager request.
- ///
- public class WhereFilter
+ // Response type
+ public class EmployeesDataResponse
{
- [GraphQLName("Field")]
- public string? Field { get; set; }
+ [GraphQLName("count")]
+ public int Count { get; set; }
+
+ [GraphQLName("result")]
+ public List Result { get; set; } = new();
- [GraphQLName("IgnoreCase")]
- public bool? IgnoreCase { get; set; }
+ [GraphQLName("items")]
+ public List Items { get; set; } = new();
+ }
+
+}
+```
- [GraphQLName("IgnoreAccent")]
- public bool? IgnoreAccent { get; set; }
+Fetches employee data by calling the **GetAllRecords** method, which is implemented in the **EmployeeData.cs** file.
- [GraphQLName("IsComplex")]
- public bool? IsComplex { get; set; }
+```csharp
+private static List? _employeesData;
+public static List GetAllRecords()
+{
+ // Add code to return a list of "ExpenseRecord" to process it further.
+ return _employeesData;
+}
+```
- [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 tree 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 TreeGrid using the `GraphQLAdaptor`, the `dataManager.Search` parameters can be utilized and the search logic applied on the server side. This approach enables efficient filtering and retrieval of relevant records from the TreeGrid based on the provided search criteria.
+### Step 6: Implement Searching feature
-When a search is performed in the TreeGrid, 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 "/"
-@rendermode InteractiveServer
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.TreeGrid
-@using Syncfusion.Blazor.DropDowns
-@using Syncfusion.Blazor.Calendars
-@using System.Text.Json.Serialization
+```razor
+
+
+
+
+
+
+```
-
-
- Employees
- Category:
+2. Update the `EmployeesData` method in the `GraphQLQuery` class to handle searching:
-
-
-
-
-
+```csharp
+using System.Text.Json;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+namespace TreeGrid_GraphQLAdaptor.Models
+{
+ // Defines the GraphQL resolver for handling TreeGrid data requests.
+ public class GraphQLQuery
+ {
+ // - Roots: search applied, then paged by Skip/Take
+ // - Children slice: ManagerID=parentId, search applied;
+ public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
+ {
+ EnsureDataLoaded();
-
-
-
- @{
- var e = (EmployeeData)context;
- var p = (e.Priority ?? "Low").Trim().ToLowerInvariant();
- var cls = p switch
- {
- "critical" => "chip chip-critical",
- "high" => "chip chip-high",
- "medium" => "chip chip-medium",
- _ => "chip chip-low"
- };
- var label = string.IsNullOrWhiteSpace(e.Priority) ? "Low" : e.Priority!;
- }
- @label
-
-
+ // Detect explicit children request from params first, then ManagerID filter in where (only to detect parentId)
+ string? parentId = TryGetParentIdFromParams(dataManager?.Params)
+ ?? TryGetParentIdFromWhere(dataManager?.Where);
-
-
-
- @{
- var e = (EmployeeData)context;
- var pct = Math.Clamp(e.Progress ?? 0, 0, 100);
- var barClass = pct >= 80 ? "progress-bar prog-high"
- : pct >= 50 ? "progress-bar prog-mid"
- : "progress-bar prog-low";
- }
-
-
-
+ // CHILDREN SLICE: return only direct children of requested parent (no paging)
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ IEnumerable children = _data
+ .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase));
-
-
-
-
-
-
-
+ // Apply search to the current level only
+ children = ApplySearch(children, dataManager?.Search ?? new List());
-@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
+ var list = children.ToList();
+ return new EmployeesDataResponse
+ {
+ Count = list.Count,
+ Result = list,
+ Items = list
+ };
+ }
- public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
+ // ROOTS: search then paging
+ IEnumerable roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID));
- private readonly List CategoryOptions = new()
- {
- new() { ID = "HR", Name = "HR" },
- new() { ID = "PM", Name = "Project Management" }
- };
+ // Apply search to roots
+ roots = ApplySearch(roots, dataManager?.Search ?? new List());
- public bool expandState { get; set; }
- private string SelectedCategory { get; set; } = "HR";
- private int treeindex { get; set; } = 3;
- private bool showHRColumns = true;
- private bool showPMColumns = false;
- private bool showEmail = true;
- public string headerName { get; set; } = "Name";
- private SfTreeGrid? tree;
+ int total = roots.Count();
- // Revert to ONLY $dataManager; server reads TreeGrid flags from dataManager.Params
- private const string HrQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- }
- }
- }";
+ // Paging
+ if (dataManager?.Skip is int sk && dataManager.Take is int tk)
+ {
+ roots = roots.Skip(sk).Take(tk);
+ }
- private const string PmQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
+ var page = roots.ToList();
+ return new EmployeesDataResponse
+ {
+ Count = total,
+ Result = page,
+ Items = page
+ };
}
- }
- }";
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = HrQuery,
- // ResolverName should match the GraphQL field name (camelCase)
-
- ResolverName = "employeesData"
- };
+ private static List _data = EnsureDataInternal();
- private async Task ModeChange(ChangeEventArgs args)
- {
- SelectedCategory = args?.Value ?? "HR";
- if (SelectedCategory == "PM")
+ private static List EnsureDataInternal() => EmployeeData.GetAllRecords();
+
+ private static void EnsureDataLoaded()
{
- adaptorOptions.Query = PmQuery;
- showHRColumns = false; showPMColumns = true; treeindex = 1;
- showEmail = false;
- headerName = "Assigned To";
+ if (_data == null || _data.Count == 0) _data = EnsureDataInternal();
}
- else
+
+ private static string? TryGetParentIdFromParams(object? prms)
{
- adaptorOptions.Query = HrQuery;
- showHRColumns = true; showPMColumns = false; treeindex = 3;
- showEmail = true;
- headerName = "Name";
- if (tree is not null) await tree.ClearFilteringAsync();
+ if (!TryReadFromParams(prms, "parentId", out var v) || v is null) return null;
+ return ToEmpId(v);
}
- await tree.CallStateHasChangedAsync();
- }
-
- public class EmployeeData
- {
- [JsonPropertyName("employeeID")] public string EmployeeID { get; set; } = "";
- [JsonPropertyName("managerID")] public string? ManagerID { get; set; }
- [JsonPropertyName("hasChild")] public bool HasChild { get; set; }
- [JsonPropertyName("name")] public string? Name { get; set; }
- [JsonPropertyName("lastName")] public string? LastName { get; set; }
- [JsonPropertyName("title")] public string? Title { get; set; }
- [JsonPropertyName("location")] public string? Location { get; set; }
- [JsonPropertyName("dateJoined")] public DateTime? DateJoined { get; set; }
- [JsonPropertyName("salaryPerMonth")] public decimal? SalaryPerMonth { get; set; }
- [JsonPropertyName("email")] public string? Email { get; set; }
- [JsonPropertyName("projectId")] public string? ProjectId { get; set; }
- [JsonPropertyName("projectDetails")] public string? ProjectDetails { get; set; }
- [JsonPropertyName("projectStatus")] public string? ProjectStatus { get; set; }
- [JsonPropertyName("priority")] public string? Priority { get; set; }
- [JsonPropertyName("progress")] public int? Progress { get; set; }
- [JsonPropertyName("projectStartDate")] public DateTime? ProjectStartDate { get; set; }
- [JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
- }
-}
+ private static bool TryReadFromParams(object? prms, string key, out object? value)
+ {
+ value = null;
+ if (prms == null) return false;
-{% endhighlight %}
+ if (prms is IDictionary dictObj)
+ return dictObj.TryGetValue(key, out value);
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
+ if (prms is IReadOnlyDictionary roDict)
+ return roDict.TryGetValue(key, out value);
-using GraphQLServer.Models;
+ if (prms is IDictionary dictJson)
+ {
+ if (dictJson.TryGetValue(key, out var je)) { value = je; return true; }
+ return false;
+ }
+
+ if (prms is IEnumerable> kvs)
+ {
+ foreach (var kv in kvs)
+ if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
+ { value = kv.Value; return true; }
+ }
+
+ if (prms is JsonElement jeObj && jeObj.ValueKind == JsonValueKind.Object)
+ {
+ if (jeObj.TryGetProperty(key, out var je))
+ { value = je; return true; }
+ }
+
+ return false;
+ }
+
+ private static string? TryGetParentIdFromWhere(List? where)
+ {
+ if (where == null || where.Count == 0) return null;
+
+ foreach (var wf in where)
+ {
+ if (!string.IsNullOrWhiteSpace(wf.Field) &&
+ wf.Field.Equals("ManagerID", StringComparison.OrdinalIgnoreCase))
+ {
+ var op = (wf.Operator ?? "equal").Trim().ToLowerInvariant();
+ if (op is "equal" or "eq")
+ {
+ if (wf.Value == null) return null;
+ return ToEmpId(wf.Value);
+ }
+ }
+
+ if (wf.Predicates != null && wf.Predicates.Count > 0)
+ {
+ var nested = TryGetParentIdFromWhere(wf.Predicates);
+ if (nested != null || wf.Value == null) return nested;
+ }
+ }
+ return null;
+ }
+
+ // Search only
+ private static IEnumerable ApplySearch(IEnumerable data, List searches)
+ {
+ if (searches == null || searches.Count == 0) return data;
+
+ IEnumerable current = data;
+
+ foreach (var s in searches)
+ {
+ if (s == null) continue;
+ var key = s.Key ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(key)) continue;
+
+ var cmp = s.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
+
+ // If no fields provided, search a reasonable set
+ var fields = (s.Fields != null && s.Fields.Count > 0)
+ ? s.Fields
+ : new List {
+ "employeeID","managerID","name","firstName","lastName",
+ "title","location","email","projectId","projectDetails",
+ "projectStatus","priority","progress"
+ };
+
+ current = current.Where(e =>
+ {
+ foreach (var f in fields)
+ {
+ var val = GetFieldString(e, f);
+ if (!string.IsNullOrEmpty(val) && val.IndexOf(key, cmp) >= 0)
+ return true;
+ }
+ return false;
+ });
+ }
+
+ return current;
+ }
+
+ // Map field to string for search
+ private static string? GetFieldString(EmployeeData e, string field)
+ {
+ if (string.IsNullOrWhiteSpace(field)) return null;
+
+ switch (field)
+ {
+ case "employeeID":
+ case "EmployeeID": return e.EmployeeID;
+ case "managerID":
+ case "ManagerID": return e.ManagerID;
+ case "hasChild":
+ case "HasChild": return e.HasChild ? "true" : "false";
+ case "name":
+ case "Name": return e.Name;
+ case "firstName":
+ case "FirstName": return e.FirstName;
+ case "lastName":
+ case "LastName": return e.LastName;
+ case "title":
+ case "Title": return e.Title;
+ case "location":
+ case "Location": return e.Location;
+ case "dateJoined":
+ case "DateJoined": return e.DateJoined?.ToString("o");
+ case "salaryPerMonth":
+ case "SalaryPerMonth": return e.SalaryPerMonth?.ToString();
+ case "email":
+ case "Email": return e.Email;
+ case "projectId":
+ case "ProjectId": return e.ProjectId;
+ case "projectDetails":
+ case "ProjectDetails": return e.ProjectDetails;
+ case "projectStatus":
+ case "ProjectStatus": return e.ProjectStatus;
+ case "priority":
+ case "Priority": return e.Priority;
+ case "progress":
+ case "Progress": return e.Progress?.ToString();
+ case "projectStartDate":
+ case "ProjectStartDate": return e.ProjectStartDate?.ToString("o");
+ case "projectEndDate":
+ case "ProjectEndDate": return e.ProjectEndDate?.ToString("o");
+ default: return null;
+ }
+ }
+
+ private static string? ToEmpId(object? v)
+ {
+ if (v == null) return null;
+ if (v is string s)
+ {
+ if (int.TryParse(s, out var n)) return $"EMP{n:000}";
+ return s;
+ }
+ if (v is int i) return $"EMP{i:000}";
+ if (v is long l && l >= int.MinValue && l <= int.MaxValue) return $"EMP{(int)l:000}";
+ if (v is JsonElement je)
+ {
+ return je.ValueKind switch
+ {
+ JsonValueKind.Number => je.TryGetInt32(out var j) ? $"EMP{j:000}" : null,
+ JsonValueKind.String => int.TryParse(je.GetString(), out var k) ? $"EMP{k:000}" : je.GetString(),
+ JsonValueKind.Null => null,
+ _ => null
+ };
+ }
+ return v.ToString();
+ }
+ }
+
+ // Response type
+ public class EmployeesDataResponse
+ {
+ [GraphQLName("count")]
+ public int Count { get; set; }
+
+ [GraphQLName("result")]
+ public List Result { get; set; } = new();
+
+ [GraphQLName("items")]
+ public List Items { get; set; } = new();
+ }
+
+}
+```
+The backend resolver receives this and processes the search filter in the `EmployeesData` method. Searching feature is now active.
+
+---
+### Step 7: Implement Sorting feature
+
+Sorting enables organizing records by selecting column headers, arranging the data in ascending or descending order.
+
+**Instructions:**
+
+1. Ensure the `` component has [AllowSorting="true"](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.TreeGrid.SfTreeGrid-1.html#Syncfusion_Blazor_TreeGrid_SfTreeGrid_1_AllowSorting).
+
+```razor
+
+
+
+
+
+
+```
+
+2. Update the `EmployeesData` method in the `GraphQLQuery` class to handle sorting:
+
+```csharp
+using System.Text.Json;
+using TreeGrid_GraphQLAdaptor.Models;
-// Defines the GraphQL resolver for handling TreeGrid data requests.
public class GraphQLQuery
{
- // - Roots: search applied, then paged by Skip/Take
- // - Children slice: ManagerID=parentId, search applied;
+ // - Roots: paged by Skip/Take and sorted if dataManager.Sorted provided
+ // - Children slice: direct children for a parentId; sorted if dataManager.Sorted provided;
public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
{
EnsureDataLoaded();
- // Detect explicit children request from params first, then ManagerID filter in where (only to detect parentId)
string? parentId = TryGetParentIdFromParams(dataManager?.Params)
- ?? TryGetParentIdFromWhere(dataManager?.Where);
+ ?? TryGetParentIdFromWhere(dataManager?.Where);
- // CHILDREN SLICE: return only direct children of requested parent (no paging)
+ // CHILDREN SLICE (no paging)
if (!string.IsNullOrWhiteSpace(parentId))
{
- IEnumerable children = _data
- .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase));
-
- // Apply search to the current level only
- children = ApplySearch(children, dataManager?.Search ?? new List());
+ var children = _data
+ .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
+ .ToList();
- var list = children.ToList();
+ children = SortListStable(children, dataManager?.Sorted);
return new EmployeesDataResponse
{
- Count = list.Count,
- Result = list,
- Items = list
+ Count = children.Count,
+ Result = children,
+ Items = children
};
}
- // ROOTS: search then paging
- IEnumerable roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID));
-
- // Apply search to roots
- roots = ApplySearch(roots, dataManager?.Search ?? new List());
+ // ROOTS: paging + optional sorting
+ var roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID)).ToList();
+ int total = roots.Count;
- int total = roots.Count();
+ roots = SortListStable(roots, dataManager?.Sorted);
- // Paging
+ IEnumerable page = roots;
if (dataManager?.Skip is int sk && dataManager.Take is int tk)
{
- roots = roots.Skip(sk).Take(tk);
+ page = page.Skip(sk).Take(tk);
}
- var page = roots.ToList();
+ var list = page.ToList();
return new EmployeesDataResponse
{
Count = total,
- Result = page,
- Items = page
+ Result = list,
+ Items = list
};
}
@@ -1508,92 +2204,6 @@ public class GraphQLQuery
return null;
}
- // Search only
- private static IEnumerable ApplySearch(IEnumerable data, List searches)
- {
- if (searches == null || searches.Count == 0) return data;
-
- IEnumerable current = data;
-
- foreach (var s in searches)
- {
- if (s == null) continue;
- var key = s.Key ?? string.Empty;
- if (string.IsNullOrWhiteSpace(key)) continue;
-
- var cmp = s.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
-
- // If no fields provided, search a reasonable set
- var fields = (s.Fields != null && s.Fields.Count > 0)
- ? s.Fields
- : new List {
- "employeeID","managerID","name","firstName","lastName",
- "title","location","email","projectId","projectDetails",
- "projectStatus","priority","progress"
- };
-
- current = current.Where(e =>
- {
- foreach (var f in fields)
- {
- var val = GetFieldString(e, f);
- if (!string.IsNullOrEmpty(val) && val.IndexOf(key, cmp) >= 0)
- return true;
- }
- return false;
- });
- }
-
- return current;
- }
-
- // Map field to string for search
- private static string? GetFieldString(EmployeeData e, string field)
- {
- if (string.IsNullOrWhiteSpace(field)) return null;
-
- switch (field)
- {
- case "employeeID":
- case "EmployeeID": return e.EmployeeID;
- case "managerID":
- case "ManagerID": return e.ManagerID;
- case "hasChild":
- case "HasChild": return e.HasChild ? "true" : "false";
- case "name":
- case "Name": return e.Name;
- case "firstName":
- case "FirstName": return e.FirstName;
- case "lastName":
- case "LastName": return e.LastName;
- case "title":
- case "Title": return e.Title;
- case "location":
- case "Location": return e.Location;
- case "dateJoined":
- case "DateJoined": return e.DateJoined?.ToString("o");
- case "salaryPerMonth":
- case "SalaryPerMonth": return e.SalaryPerMonth?.ToString();
- case "email":
- case "Email": return e.Email;
- case "projectId":
- case "ProjectId": return e.ProjectId;
- case "projectDetails":
- case "ProjectDetails": return e.ProjectDetails;
- case "projectStatus":
- case "ProjectStatus": return e.ProjectStatus;
- case "priority":
- case "Priority": return e.Priority;
- case "progress":
- case "Progress": return e.Progress?.ToString();
- case "projectStartDate":
- case "ProjectStartDate": return e.ProjectStartDate?.ToString("o");
- case "projectEndDate":
- case "ProjectEndDate": return e.ProjectEndDate?.ToString("o");
- default: return null;
- }
- }
-
private static string? ToEmpId(object? v)
{
if (v == null) return null;
@@ -1616,6 +2226,49 @@ public class GraphQLQuery
}
return v.ToString();
}
+
+ private static List SortListStable(List list, List? sorts)
+ {
+ if (sorts == null || sorts.Count == 0)
+ return list.OrderBy(x => x.EmployeeID, StringComparer.OrdinalIgnoreCase).ToList();
+
+ IOrderedEnumerable? ordered = null;
+
+ for (int i = 0; i < sorts.Count; i++)
+ {
+ var s = sorts[i];
+ bool desc = string.Equals(s.Direction, "desc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(s.Direction, "descending", StringComparison.OrdinalIgnoreCase);
+
+ Func key = s.Name switch
+ {
+ "employeeID" => e => e.EmployeeID,
+ "managerID" => e => e.ManagerID,
+ "name" => e => e.Name,
+ "firstName" => e => e.FirstName,
+ "lastName" => e => e.LastName,
+ "title" => e => e.Title,
+ "location" => e => e.Location,
+ "dateJoined" => e => e.DateJoined,
+ "salaryPerMonth" => e => e.SalaryPerMonth,
+ "email" => e => e.Email,
+ "projectId" => e => e.ProjectId,
+ "projectDetails" => e => e.ProjectDetails,
+ "projectStatus" => e => e.ProjectStatus,
+ "priority" => e => e.Priority,
+ "progress" => e => e.Progress,
+ "projectStartDate" => e => e.ProjectStartDate,
+ "projectEndDate" => e => e.ProjectEndDate,
+ _ => e => e.EmployeeID
+ };
+
+ ordered = i == 0
+ ? (desc ? list.OrderByDescending(key) : list.OrderBy(key))
+ : (desc ? ordered!.ThenByDescending(key) : ordered!.ThenBy(key));
+ }
+
+ return ordered!.ThenBy(x => x.EmployeeID, StringComparer.OrdinalIgnoreCase).ToList();
+ }
}
// Response type
@@ -1630,259 +2283,87 @@ public class EmployeesDataResponse
[GraphQLName("items")]
public List Items { get; set; } = new();
}
+```
-{% endhighlight %}
-{% endtabs %}
-
-## Handling filtering operation
+**How Sort Variables are Passed:**
-To handle filtering operations in the Syncfusion® Blazor TreeGrid using the `GraphQLAdaptor`, the `dataManager.Where` parameters can be utilized and the filter logic applied on the server side. This approach enables refinement of TreeGrid data by specifying one or more filter conditions based on column values.
+When a column header is selected for sorting, the TreeGrid automatically sends:
+```json
+{
+ "dataManager": {
+ "Sorted": [
+ {
+ "Name": "EmployeeID",
+ "Direction": "Descending"
+ }
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
+ }
+}
+```
-When a filter is applied in the TreeGrid, 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.
+The backend resolver receives this and processes the sort specification in the `EmployeesData` method. Multiple sorting conditions can be applied sequentially by holding the **Ctrl** key and selecting additional column headers. Sorting feature is now active.
-On the server, these parameters are parsed and used to filter the data source accordingly before returning the results to the TreeGrid.
+---
-
+ ### 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.TreeGrid.SfTreeGrid-1.html#Syncfusion_Blazor_TreeGrid_SfTreeGrid_1_AllowFiltering).
+
+ ``````razor
+
+
+
+
+
+
+ ``````
+
+ 2. Update the ``EmployeesData`` method in the ``GraphQLQuery`` class to handle filtering:
+
+ ``````csharp
+ // Defines the GraphQL resolver for handling TreeGrid requests.
+using System.Text.Json;
+using TreeGrid_GraphQLAdaptor.Models;
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
+public class GraphQLQuery
+{
+ // - Roots: filters applied, then paged by Skip/Take
+ // - Children slice: ManagerID=parentId, then filters applied; no paging
+ public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
+ {
+ EnsureDataLoaded();
-@page "/"
-@rendermode InteractiveServer
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.TreeGrid
-@using Syncfusion.Blazor.DropDowns
-@using Syncfusion.Blazor.Calendars
-@using System.Text.Json.Serialization
+ // Detect explicit children request from params first, then ManagerID filter in where
+ string? parentId = TryGetParentIdFromParams(dataManager?.Params)
+ ?? TryGetParentIdFromWhere(dataManager?.Where);
-
-
- Employees
- Category:
+ // CHILDREN SLICE: return only direct children of requested parent (no paging)
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ var children = _data
+ .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
+ .ToList();
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var p = (e.Priority ?? "Low").Trim().ToLowerInvariant();
- var cls = p switch
- {
- "critical" => "chip chip-critical",
- "high" => "chip chip-high",
- "medium" => "chip chip-medium",
- _ => "chip chip-low"
- };
- var label = string.IsNullOrWhiteSpace(e.Priority) ? "Low" : e.Priority!;
- }
- @label
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var pct = Math.Clamp(e.Progress ?? 0, 0, 100);
- var barClass = pct >= 80 ? "progress-bar prog-high"
- : pct >= 50 ? "progress-bar prog-mid"
- : "progress-bar prog-low";
- }
-
-
-
-
-
-
-
-
-
-
-
-
-@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
-
- public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
-
- private readonly List CategoryOptions = new()
- {
- new() { ID = "HR", Name = "HR" },
- new() { ID = "PM", Name = "Project Management" }
- };
-
- public bool expandState { get; set; }
- private string SelectedCategory { get; set; } = "HR";
- private int treeindex { get; set; } = 3;
- private bool showHRColumns = true;
- private bool showPMColumns = false;
- private bool showEmail = true;
- public string headerName { get; set; } = "Name";
- private SfTreeGrid? tree;
-
- // Revert to ONLY $dataManager; server reads TreeGrid flags from dataManager.Params
- private const string HrQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- }
- }
- }";
-
- private const string PmQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }
- }";
-
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = HrQuery,
- // ResolverName should match the GraphQL field name (camelCase)
-
- ResolverName = "employeesData"
- };
-
- private async Task ModeChange(ChangeEventArgs args)
- {
- SelectedCategory = args?.Value ?? "HR";
- if (SelectedCategory == "PM")
- {
- adaptorOptions.Query = PmQuery;
- showHRColumns = false; showPMColumns = true; treeindex = 1;
- showEmail = false;
- headerName = "Assigned To";
- }
- else
- {
- adaptorOptions.Query = HrQuery;
- showHRColumns = true; showPMColumns = false; treeindex = 3;
- showEmail = true;
- headerName = "Name";
- if (tree is not null) await tree.ClearFilteringAsync();
- }
- await tree.CallStateHasChangedAsync();
- }
-
- public class EmployeeData
- {
- [JsonPropertyName("employeeID")] public string EmployeeID { get; set; } = "";
- [JsonPropertyName("managerID")] public string? ManagerID { get; set; }
- [JsonPropertyName("hasChild")] public bool HasChild { get; set; }
- [JsonPropertyName("name")] public string? Name { get; set; }
- [JsonPropertyName("lastName")] public string? LastName { get; set; }
- [JsonPropertyName("title")] public string? Title { get; set; }
- [JsonPropertyName("location")] public string? Location { get; set; }
- [JsonPropertyName("dateJoined")] public DateTime? DateJoined { get; set; }
- [JsonPropertyName("salaryPerMonth")] public decimal? SalaryPerMonth { get; set; }
- [JsonPropertyName("email")] public string? Email { get; set; }
- [JsonPropertyName("projectId")] public string? ProjectId { get; set; }
- [JsonPropertyName("projectDetails")] public string? ProjectDetails { get; set; }
- [JsonPropertyName("projectStatus")] public string? ProjectStatus { get; set; }
- [JsonPropertyName("priority")] public string? Priority { get; set; }
- [JsonPropertyName("progress")] public int? Progress { get; set; }
- [JsonPropertyName("projectStartDate")] public DateTime? ProjectStartDate { get; set; }
- [JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
- }
-}
-
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
-
-using GraphQLServer.Models;
-
-// Defines the GraphQL resolver for handling TreeGrid requests.
-public class GraphQLQuery
-{
- // - Roots: filters applied, then paged by Skip/Take
- // - Children slice: ManagerID=parentId, then filters applied; no paging
- public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
- {
- EnsureDataLoaded();
-
- // Detect explicit children request from params first, then ManagerID filter in where
- string? parentId = TryGetParentIdFromParams(dataManager?.Params)
- ?? TryGetParentIdFromWhere(dataManager?.Where);
-
- // CHILDREN SLICE: return only direct children of requested parent (no paging)
- if (!string.IsNullOrWhiteSpace(parentId))
- {
- var children = _data
- .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- // Apply non-parent filters to the current level
- children = ApplyWhereExcludingParent(children, dataManager?.Where ?? new List()).ToList();
+ // Apply non-parent filters to the current level
+ children = ApplyWhereExcludingParent(children, dataManager?.Where ?? new List()).ToList();
return new EmployeesDataResponse
{
@@ -2140,1512 +2621,159 @@ public class EmployeesDataResponse
[GraphQLName("items")]
public List Items { get; set; } = new();
}
-
-{% endhighlight %}
-{% endtabs %}
-
-## Handling sorting operation
-
-To handle sorting operations in the Syncfusion® Blazor TreeGrid using the `GraphQLAdaptor`, the sorting logic can be implemented on the server side by utilizing the `dataManager.Sorted` parameter. This enables the TreeGrid to send sorting instructions to the server, specifying the fields and sort directions to apply.
-
-When a sort action is triggered in the TreeGrid, 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 TreeGrid.
-
-
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-@page "/"
-@rendermode InteractiveServer
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.TreeGrid
-@using Syncfusion.Blazor.DropDowns
-@using Syncfusion.Blazor.Calendars
-@using System.Text.Json.Serialization
-
-
-
- Employees
- Category:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var p = (e.Priority ?? "Low").Trim().ToLowerInvariant();
- var cls = p switch
- {
- "critical" => "chip chip-critical",
- "high" => "chip chip-high",
- "medium" => "chip chip-medium",
- _ => "chip chip-low"
- };
- var label = string.IsNullOrWhiteSpace(e.Priority) ? "Low" : e.Priority!;
- }
- @label
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var pct = Math.Clamp(e.Progress ?? 0, 0, 100);
- var barClass = pct >= 80 ? "progress-bar prog-high"
- : pct >= 50 ? "progress-bar prog-mid"
- : "progress-bar prog-low";
- }
-
-
-
-
-
-
-
-
-
-
-
-
-@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
-
- public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
-
- private readonly List CategoryOptions = new()
- {
- new() { ID = "HR", Name = "HR" },
- new() { ID = "PM", Name = "Project Management" }
- };
-
- public bool expandState { get; set; }
- private string SelectedCategory { get; set; } = "HR";
- private int treeindex { get; set; } = 3;
- private bool showHRColumns = true;
- private bool showPMColumns = false;
- private bool showEmail = true;
- public string headerName { get; set; } = "Name";
- private SfTreeGrid? tree;
-
- // Revert to ONLY $dataManager; server reads TreeGrid flags from dataManager.Params
- private const string HrQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- }
- }
- }";
-
- private const string PmQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }
- }";
-
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = HrQuery,
- // ResolverName should match the GraphQL field name (camelCase)
-
- ResolverName = "employeesData"
- };
-
- private async Task ModeChange(ChangeEventArgs args)
- {
- SelectedCategory = args?.Value ?? "HR";
- if (SelectedCategory == "PM")
- {
- adaptorOptions.Query = PmQuery;
- showHRColumns = false; showPMColumns = true; treeindex = 1;
- showEmail = false;
- headerName = "Assigned To";
- }
- else
- {
- adaptorOptions.Query = HrQuery;
- showHRColumns = true; showPMColumns = false; treeindex = 3;
- showEmail = true;
- headerName = "Name";
- if (tree is not null) await tree.ClearFilteringAsync();
- }
- await tree.CallStateHasChangedAsync();
- }
-
- public class EmployeeData
- {
- [JsonPropertyName("employeeID")] public string EmployeeID { get; set; } = "";
- [JsonPropertyName("managerID")] public string? ManagerID { get; set; }
- [JsonPropertyName("hasChild")] public bool HasChild { get; set; }
- [JsonPropertyName("name")] public string? Name { get; set; }
- [JsonPropertyName("lastName")] public string? LastName { get; set; }
- [JsonPropertyName("title")] public string? Title { get; set; }
- [JsonPropertyName("location")] public string? Location { get; set; }
- [JsonPropertyName("dateJoined")] public DateTime? DateJoined { get; set; }
- [JsonPropertyName("salaryPerMonth")] public decimal? SalaryPerMonth { get; set; }
- [JsonPropertyName("email")] public string? Email { get; set; }
- [JsonPropertyName("projectId")] public string? ProjectId { get; set; }
- [JsonPropertyName("projectDetails")] public string? ProjectDetails { get; set; }
- [JsonPropertyName("projectStatus")] public string? ProjectStatus { get; set; }
- [JsonPropertyName("priority")] public string? Priority { get; set; }
- [JsonPropertyName("progress")] public int? Progress { get; set; }
- [JsonPropertyName("projectStartDate")] public DateTime? ProjectStartDate { get; set; }
- [JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
- }
-}
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
-
-using GraphQLServer.Models;
-
-// Defines the GraphQL resolver for handling TreeGrid requests.
-public class GraphQLQuery
-{
- // - Roots: paged by Skip/Take and sorted if dataManager.Sorted provided
- // - Children slice: direct children for a parentId; sorted if dataManager.Sorted provided;
- public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
- {
- EnsureDataLoaded();
-
- string? parentId = TryGetParentIdFromParams(dataManager?.Params)
- ?? TryGetParentIdFromWhere(dataManager?.Where);
-
- // CHILDREN SLICE (no paging)
- if (!string.IsNullOrWhiteSpace(parentId))
- {
- var children = _data
- .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- children = SortListStable(children, dataManager?.Sorted);
- return new EmployeesDataResponse
- {
- Count = children.Count,
- Result = children,
- Items = children
- };
- }
-
- // ROOTS: paging + optional sorting
- var roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID)).ToList();
- int total = roots.Count;
-
- roots = SortListStable(roots, dataManager?.Sorted);
-
- IEnumerable page = roots;
- if (dataManager?.Skip is int sk && dataManager.Take is int tk)
- {
- page = page.Skip(sk).Take(tk);
- }
-
- var list = page.ToList();
- return new EmployeesDataResponse
- {
- Count = total,
- Result = list,
- Items = list
- };
- }
-
- private static List _data = EnsureDataInternal();
-
- private static List EnsureDataInternal() => EmployeeData.GetAllRecords();
-
- private static void EnsureDataLoaded()
- {
- if (_data == null || _data.Count == 0) _data = EnsureDataInternal();
- }
-
- private static string? TryGetParentIdFromParams(object? prms)
- {
- if (!TryReadFromParams(prms, "parentId", out var v) || v is null) return null;
- return ToEmpId(v);
- }
-
- private static bool TryReadFromParams(object? prms, string key, out object? value)
- {
- value = null;
- if (prms == null) return false;
-
- if (prms is IDictionary dictObj)
- return dictObj.TryGetValue(key, out value);
-
- if (prms is IReadOnlyDictionary roDict)
- return roDict.TryGetValue(key, out value);
-
- if (prms is IDictionary dictJson)
- {
- if (dictJson.TryGetValue(key, out var je)) { value = je; return true; }
- return false;
- }
-
- if (prms is IEnumerable> kvs)
- {
- foreach (var kv in kvs)
- if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
- { value = kv.Value; return true; }
- }
-
- if (prms is JsonElement jeObj && jeObj.ValueKind == JsonValueKind.Object)
- {
- if (jeObj.TryGetProperty(key, out var je))
- { value = je; return true; }
- }
-
- return false;
- }
-
- private static string? TryGetParentIdFromWhere(List? where)
- {
- if (where == null || where.Count == 0) return null;
-
- foreach (var wf in where)
- {
- if (!string.IsNullOrWhiteSpace(wf.Field) &&
- wf.Field.Equals("ManagerID", StringComparison.OrdinalIgnoreCase))
- {
- var op = (wf.Operator ?? "equal").Trim().ToLowerInvariant();
- if (op is "equal" or "eq")
- {
- if (wf.Value == null) return null;
- return ToEmpId(wf.Value);
- }
- }
-
- if (wf.Predicates != null && wf.Predicates.Count > 0)
- {
- var nested = TryGetParentIdFromWhere(wf.Predicates);
- if (nested != null || wf.Value == null) return nested;
- }
- }
- return null;
- }
-
- private static string? ToEmpId(object? v)
- {
- if (v == null) return null;
- if (v is string s)
- {
- if (int.TryParse(s, out var n)) return $"EMP{n:000}";
- return s;
- }
- if (v is int i) return $"EMP{i:000}";
- if (v is long l && l >= int.MinValue && l <= int.MaxValue) return $"EMP{(int)l:000}";
- if (v is JsonElement je)
- {
- return je.ValueKind switch
- {
- JsonValueKind.Number => je.TryGetInt32(out var j) ? $"EMP{j:000}" : null,
- JsonValueKind.String => int.TryParse(je.GetString(), out var k) ? $"EMP{k:000}" : je.GetString(),
- JsonValueKind.Null => null,
- _ => null
- };
- }
- return v.ToString();
- }
-
- private static List SortListStable(List list, List? sorts)
- {
- if (sorts == null || sorts.Count == 0)
- return list.OrderBy(x => x.EmployeeID, StringComparer.OrdinalIgnoreCase).ToList();
-
- IOrderedEnumerable? ordered = null;
-
- for (int i = 0; i < sorts.Count; i++)
- {
- var s = sorts[i];
- bool desc = string.Equals(s.Direction, "desc", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(s.Direction, "descending", StringComparison.OrdinalIgnoreCase);
-
- Func key = s.Name switch
- {
- "employeeID" => e => e.EmployeeID,
- "managerID" => e => e.ManagerID,
- "name" => e => e.Name,
- "firstName" => e => e.FirstName,
- "lastName" => e => e.LastName,
- "title" => e => e.Title,
- "location" => e => e.Location,
- "dateJoined" => e => e.DateJoined,
- "salaryPerMonth" => e => e.SalaryPerMonth,
- "email" => e => e.Email,
- "projectId" => e => e.ProjectId,
- "projectDetails" => e => e.ProjectDetails,
- "projectStatus" => e => e.ProjectStatus,
- "priority" => e => e.Priority,
- "progress" => e => e.Progress,
- "projectStartDate" => e => e.ProjectStartDate,
- "projectEndDate" => e => e.ProjectEndDate,
- _ => e => e.EmployeeID
- };
-
- ordered = i == 0
- ? (desc ? list.OrderByDescending(key) : list.OrderBy(key))
- : (desc ? ordered!.ThenByDescending(key) : ordered!.ThenBy(key));
- }
-
- return ordered!.ThenBy(x => x.EmployeeID, StringComparer.OrdinalIgnoreCase).ToList();
- }
-}
-
-// Response type
-public class EmployeesDataResponse
-{
- [GraphQLName("count")]
- public int Count { get; set; }
-
- [GraphQLName("result")]
- public List Result { get; set; } = new();
-
- [GraphQLName("items")]
- public List Items { get; set; } = new();
-}
-
-{% endhighlight %}
-{% endtabs %}
-
-## Handling paging operation
-
-To handle paging operations in the Syncfusion® Blazor TreeGrid using the `GraphQLAdaptor`, the `dataManager.Skip` and `dataManager.Take` parameters can be utilized. These parameters enable retrieval of data in pages, helping to manage large datasets efficiently by loading only a subset of records at a time.
-
-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.
-
-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 TreeGrid.
-
-
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-
-@page "/"
-@rendermode InteractiveServer
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.TreeGrid
-@using Syncfusion.Blazor.DropDowns
-@using Syncfusion.Blazor.Calendars
-@using System.Text.Json.Serialization
-
-
-
- Employees
- Category:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var p = (e.Priority ?? "Low").Trim().ToLowerInvariant();
- var cls = p switch
- {
- "critical" => "chip chip-critical",
- "high" => "chip chip-high",
- "medium" => "chip chip-medium",
- _ => "chip chip-low"
- };
- var label = string.IsNullOrWhiteSpace(e.Priority) ? "Low" : e.Priority!;
- }
- @label
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var pct = Math.Clamp(e.Progress ?? 0, 0, 100);
- var barClass = pct >= 80 ? "progress-bar prog-high"
- : pct >= 50 ? "progress-bar prog-mid"
- : "progress-bar prog-low";
- }
-
-
-
-
-
-
-
-
-
-
-
-
-@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
-
- public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
-
- private readonly List CategoryOptions = new()
- {
- new() { ID = "HR", Name = "HR" },
- new() { ID = "PM", Name = "Project Management" }
- };
-
- public bool expandState { get; set; }
- private string SelectedCategory { get; set; } = "HR";
- private int treeindex { get; set; } = 3;
- private bool showHRColumns = true;
- private bool showPMColumns = false;
- private bool showEmail = true;
- public string headerName { get; set; } = "Name";
- private SfTreeGrid? tree;
-
- // Revert to ONLY $dataManager; server reads TreeGrid flags from dataManager.Params
- private const string HrQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- }
- }
- }";
-
- private const string PmQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }
- }";
-
- private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
- {
- Query = HrQuery,
- // ResolverName should match the GraphQL field name (camelCase)
-
- ResolverName = "employeesData"
- };
-
- private async Task ModeChange(ChangeEventArgs args)
- {
- SelectedCategory = args?.Value ?? "HR";
- if (SelectedCategory == "PM")
- {
- adaptorOptions.Query = PmQuery;
- showHRColumns = false; showPMColumns = true; treeindex = 1;
- showEmail = false;
- headerName = "Assigned To";
- }
- else
- {
- adaptorOptions.Query = HrQuery;
- showHRColumns = true; showPMColumns = false; treeindex = 3;
- showEmail = true;
- headerName = "Name";
- if (tree is not null) await tree.ClearFilteringAsync();
- }
- await tree.CallStateHasChangedAsync();
- }
-
- public class EmployeeData
- {
- [JsonPropertyName("employeeID")] public string EmployeeID { get; set; } = "";
- [JsonPropertyName("managerID")] public string? ManagerID { get; set; }
- [JsonPropertyName("hasChild")] public bool HasChild { get; set; }
- [JsonPropertyName("name")] public string? Name { get; set; }
- [JsonPropertyName("lastName")] public string? LastName { get; set; }
- [JsonPropertyName("title")] public string? Title { get; set; }
- [JsonPropertyName("location")] public string? Location { get; set; }
- [JsonPropertyName("dateJoined")] public DateTime? DateJoined { get; set; }
- [JsonPropertyName("salaryPerMonth")] public decimal? SalaryPerMonth { get; set; }
- [JsonPropertyName("email")] public string? Email { get; set; }
- [JsonPropertyName("projectId")] public string? ProjectId { get; set; }
- [JsonPropertyName("projectDetails")] public string? ProjectDetails { get; set; }
- [JsonPropertyName("projectStatus")] public string? ProjectStatus { get; set; }
- [JsonPropertyName("priority")] public string? Priority { get; set; }
- [JsonPropertyName("progress")] public int? Progress { get; set; }
- [JsonPropertyName("projectStartDate")] public DateTime? ProjectStartDate { get; set; }
- [JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
- }
-}
-
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLQuery.cs" %}
-
-using GraphQLServer.Models;
-
-// Defines the GraphQL resolver for handling TreeGrid requests.
-public class GraphQLQuery
-{
- // SINGLE ENTRYPOINT: handles roots, children, expand/collapse, expandall, loadchildondemand, filtering, search, sort, paging
- public EmployeesDataResponse EmployeesData(DataManagerRequestInput dataManager)
- {
- EnsureDataLoaded();
-
- // Parent detection (params first, then ManagerID== in where)
- string? parentId = TryGetParentIdFromParams(dataManager?.Params)
- ?? TryGetParentIdFromWhere(dataManager?.Where);
-
- // CHILDREN SLICE: return only direct children of requested parent
- if (!string.IsNullOrWhiteSpace(parentId))
- {
- var children = _data
- .Where(d => string.Equals(d.ManagerID, parentId, StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- return new EmployeesDataResponse
- {
- Count = children.Count,
- Result = children,
- Items = children
- };
- }
-
- // ROOTS: proper root-only paging
- var roots = _data.Where(d => string.IsNullOrWhiteSpace(d.ManagerID)).ToList();
- int total = roots.Count;
-
- IEnumerable page = roots;
- if (dataManager?.Skip is int sk && dataManager.Take is int tk)
- {
- page = page.Skip(sk).Take(tk);
- }
-
- var list = page.ToList();
- return new EmployeesDataResponse
- {
- Count = total,
- Result = list,
- Items = list
- };
- }
-
- private static List _data = EnsureDataInternal();
-
- private static List EnsureDataInternal() => EmployeeData.GetAllRecords();
-
- private static void EnsureDataLoaded()
- {
- if (_data == null || _data.Count == 0) _data = EnsureDataInternal();
- }
-
- private static string? TryGetParentIdFromParams(object? prms)
- {
- if (!TryReadFromParams(prms, "parentId", out var v) || v is null) return null;
- return ToEmpId(v);
- }
-
- private static bool TryReadFromParams(object? prms, string key, out object? value)
- {
- value = null;
- if (prms == null) return false;
-
- // IDictionary
- if (prms is IDictionary dictObj)
- return dictObj.TryGetValue(key, out value);
-
- // IReadOnlyDictionary
- if (prms is IReadOnlyDictionary roDict)
- return roDict.TryGetValue(key, out value);
-
- // IDictionary
- if (prms is IDictionary dictJson)
- {
- if (dictJson.TryGetValue(key, out var je)) { value = je; return true; }
- return false;
- }
-
- // IEnumerable>
- if (prms is IEnumerable> kvs)
- {
- foreach (var kv in kvs)
- if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
- { value = kv.Value; return true; }
- }
-
- // JsonElement object
- if (prms is JsonElement jeObj && jeObj.ValueKind == JsonValueKind.Object)
- {
- if (jeObj.TryGetProperty(key, out var je))
- { value = je; return true; }
- }
-
- return false;
- }
-
- private static string? TryGetParentIdFromWhere(List? where)
- {
- if (where == null || where.Count == 0) return null;
-
- foreach (var wf in where)
- {
- if (!string.IsNullOrWhiteSpace(wf.Field) &&
- wf.Field.Equals("ManagerID", StringComparison.OrdinalIgnoreCase))
- {
- var op = (wf.Operator ?? "equal").Trim().ToLowerInvariant();
- if (op is "equal" or "eq")
- {
- if (wf.Value == null) return null;
- return ToEmpId(wf.Value);
- }
- }
-
- if (wf.Predicates != null && wf.Predicates.Count > 0)
- {
- var nested = TryGetParentIdFromWhere(wf.Predicates);
- if (nested != null || wf.Value == null) return nested;
- }
- }
- return null;
- }
-
- private static string? ToEmpId(object? v)
- {
- if (v == null) return null;
- if (v is string s)
- {
- if (int.TryParse(s, out var n)) return $"EMP{n:000}";
- return s;
- }
- if (v is int i) return $"EMP{i:000}";
- if (v is long l && l >= int.MinValue && l <= int.MaxValue) return $"EMP{(int)l:000}";
- if (v is JsonElement je)
- {
- return je.ValueKind switch
- {
- JsonValueKind.Number => je.TryGetInt32(out var j) ? $"EMP{j:000}" : null,
- JsonValueKind.String => int.TryParse(je.GetString(), out var k) ? $"EMP{k:000}" : je.GetString(),
- JsonValueKind.Null => null,
- _ => null
- };
- }
- return v.ToString();
- }
-}
-
-// Response type
-public class EmployeesDataResponse
-{
- [GraphQLName("count")]
- public int Count { get; set; }
-
- [GraphQLName("result")]
- public List Result { get; set; } = new();
-
- [GraphQLName("items")]
- public List Items { get; set; } = new();
-}
-
-{% endhighlight %}
-{% endtabs %}
-
-## Handling CRUD operation using mutation
-
-The Syncfusion® Blazor TreeGrid integrates seamlessly with GraphQL APIs using the `GraphQLAdaptor`, enabling support for CRUD (Create, Read, Update, and Delete) and Batch operations. This adaptor maps TreeGrid actions to GraphQL queries and mutations for real-time data interaction.
-
-This section demonstrates how to configure the TreeGrid with actual code to bind data and perform CRUD actions using the `GraphQLAdaptor`.
-
-**Set Up Mutation Queries**
-
-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:
-
-* **Insert Mutation:** A GraphQL mutation that allows adding new records.
-
-* **Update Mutation:** A GraphQL mutation for updating existing records.
-
-* **Delete Mutation:** A GraphQL mutation that removes records.
-
-
-**Configuration in GraphQL server application**
-
-The following code is the configuration in GraphQL server application to set GraphQL query and mutation type and to enable CORS.
-
-```cshtml
-
-var builder = WebApplication.CreateBuilder(args);
-
-//GraphQL resolver is defined in GraphQLQuery class and mutation methods are defined in GraphQLMutation class
-builder.Services.AddGraphQLServer().AddQueryType().AddMutationType();
-
-//CORS is enabled to access the GraphQL server from the client application
-builder.Services.AddCors(options =>
-{
- options.AddPolicy("AllowSpecificOrigin", builder =>
- {
- builder.WithOrigins("https://xxxxxx")
- .AllowAnyHeader()
- .AllowAnyMethod()
- .AllowCredentials().Build();
- });
-});
-
-```
-
-The following steps outline how to set up these operations in the TreeGrid.
-
-**1. Insert Operation:**
-
-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 TreeGrid. The adaptor sends the necessary parameters to the GraphQL server to perform the insertion.
-
-**Mutation query configuration**
-
-The Insert mutation should be configured as shown below:
-
-```cs
-Mutation = new GraphQLMutation
-{
- Insert = @"
- mutation create($record: EmployeeDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
- createEmployee(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }",
-},
-```
-
-**Parameters Sent to the Server**
-
-The following variables are passed as a parameter to the mutation method written for **Insert** operation in server side.
-
-| 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. |
-
-**Server-Side Mutation Implementation**
-
-The following example demonstrates how to implement the insert logic on the GraphQL server using C# with HotChocolate:
-
-```cs
-
-using GraphQLServer.Models;
-
-namespace GraphQLServer.GraphQL
-{
- public class GraphQLMutation
- {
- public EmployeeData CreateEmployee(
- EmployeeData record,
- int index,
- string action,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
- {
- var employees = EmployeeData.GetAllRecords();
-
- // Accept provided values; caller should supply EmployeeID (e.g., EMP001) and optional ManagerID.
- var entity = new EmployeeData
- {
- EmployeeID = record.EmployeeID,
- FirstName = record.FirstName,
- LastName = record.LastName,
- Title = record.Title,
- ManagerID = record.ManagerID,
- HasChild = record.HasChild,
- Name = record.Name,
- Location = record.Location,
- DateJoined = record.DateJoined,
- SalaryPerMonth = record.SalaryPerMonth,
- Email = record.Email,
- ProjectDetails = record.ProjectDetails,
- ProjectStatus = record.ProjectStatus,
- Priority = record.Priority,
- Progress = record.Progress,
- ProjectStartDate = record.ProjectStartDate,
- ProjectEndDate = record.ProjectEndDate,
- ProjectId = record.ProjectId // e.g., PRJ001
- };
-
- if (!string.IsNullOrWhiteSpace(entity.ManagerID))
- {
- var manager = employees.FirstOrDefault(e => string.Equals(e.EmployeeID, entity.ManagerID, System.StringComparison.OrdinalIgnoreCase));
- if (manager != null) manager.HasChild = true;
- }
-
- if (index >= 0 && index <= employees.Count)
- employees.Insert(index, entity);
- else
- employees.Add(entity);
-
- return entity;
- }
- }
-}
-
-```
-
-**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).
-
-This mutation query is triggered when an existing row in the Syncfusion® Blazor TreeGrid is modified. The adaptor sends the updated data and relevant parameters to the GraphQL server for processing.
-
-**Mutation query configuration**
-
-The Update mutation should be configured as shown below:
-
-```cs
-Mutation = new GraphQLMutation
-{
- Update = @"
- mutation update($record: EmployeeDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
- updateEmployee(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }",
-},
-```
-
-**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**
-
-The following example demonstrates how to implement the update logic on the GraphQL server using C# with HotChocolate:
-
-```cs
-using GraphQLServer.Models;
-
-namespace GraphQLServer.GraphQL
-{
- public class GraphQLMutation
- {
- public EmployeeData? UpdateEmployee(
- EmployeeData record,
- string action,
- string primaryColumnName,
- string primaryColumnValue,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
- {
- var employees = EmployeeData.GetAllRecords();
-
- var keyName = primaryColumnName?.ToLowerInvariant();
- var existing = keyName switch
- {
- "employeeid" => employees.FirstOrDefault(x => string.Equals(x.EmployeeID, primaryColumnValue, System.StringComparison.OrdinalIgnoreCase)),
- _ => employees.FirstOrDefault(x => string.Equals(x.EmployeeID, primaryColumnValue, System.StringComparison.OrdinalIgnoreCase))
- };
- if (existing == null) return null;
-
- if (record.FirstName != null) existing.FirstName = record.FirstName;
- if (record.LastName != null) existing.LastName = record.LastName;
- if (record.Title != null) existing.Title = record.Title;
- if (record.Name != null) existing.Name = record.Name;
- if (record.Location != null) existing.Location = record.Location;
- if (record.DateJoined.HasValue) existing.DateJoined = record.DateJoined;
- if (record.SalaryPerMonth.HasValue) existing.SalaryPerMonth = record.SalaryPerMonth;
- if (record.Email != null) existing.Email = record.Email;
- if (record.ProjectDetails != null) existing.ProjectDetails = record.ProjectDetails;
- if (record.ProjectStatus != null) existing.ProjectStatus = record.ProjectStatus;
- if (record.Priority != null) existing.Priority = record.Priority;
- if (record.Progress.HasValue) existing.Progress = record.Progress;
- if (record.ProjectStartDate.HasValue) existing.ProjectStartDate = record.ProjectStartDate;
- if (record.ProjectEndDate.HasValue) existing.ProjectEndDate = record.ProjectEndDate;
- if (record.ProjectId != null) existing.ProjectId = record.ProjectId;
-
- if (!string.IsNullOrWhiteSpace(record.ManagerID) &&
- !string.Equals(record.ManagerID, existing.ManagerID, System.StringComparison.OrdinalIgnoreCase))
- {
- var oldManagerId = existing.ManagerID;
- existing.ManagerID = record.ManagerID;
-
- if (!string.IsNullOrWhiteSpace(existing.ManagerID))
- {
- var newManager = employees.FirstOrDefault(e => string.Equals(e.EmployeeID, existing.ManagerID, System.StringComparison.OrdinalIgnoreCase));
- if (newManager != null) newManager.HasChild = true;
- }
-
- if (!string.IsNullOrWhiteSpace(oldManagerId))
- {
- var oldManager = employees.FirstOrDefault(e => string.Equals(e.EmployeeID, oldManagerId, System.StringComparison.OrdinalIgnoreCase));
- if (oldManager != null)
- {
- oldManager.HasChild = employees.Any(e => string.Equals(e.ManagerID, oldManager.EmployeeID, System.StringComparison.OrdinalIgnoreCase));
- }
- }
- }
-
- if (record.HasChild) existing.HasChild = record.HasChild;
-
- return existing;
- }
- }
-}
-
-```
-
-**3. Delete Operation:**
-
-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).
-
-This mutation query is executed when a row is removed from the Syncfusion® Blazor TreeGrid. The adaptor passes the required parameters to the GraphQL server to process the deletion.
-
-**Mutation query configuration**
-
-The Delete mutation should be configured as shown below:
-
-```cs
-Mutation = new GraphQLMutation
-{
- Delete = @"
- mutation delete($primaryColumnValue: String!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
- deleteEmployee(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }",
-},
-```
-
-**Parameters Sent to the Server**
-
-The following variables are passed as a parameter to the mutation method written for **Delete** operation in server side.
-
-| 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. |
-
-**Server-Side Mutation Implementation**
-
-The following example demonstrates how to implement the delete logic on the GraphQL server using C# with HotChocolate:
-
-```cs
-using GraphQLServer.Models;
-
-namespace GraphQLServer.GraphQL
+ ``````
+
+ **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" |
+ | greater than | Greater than numeric value | Amount > 1000 |
+ | less than | Less than numeric value | TaxPct < 0.15 |
+ | greater than equal | Greater than or equal | Amount >= 500 |
+ | less than equal | Less than or equal | TaxPct <= 0.10 |
+
+ **How Filter Variables are Passed:**
+
+ When filter conditions are applied, the TreeGrid automatically sends:
+ ``````json
{
- public class GraphQLMutation
- {
- public EmployeeData? DeleteEmployee(
- string primaryColumnValue,
- string action,
- string primaryColumnName,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
- {
- var employees = EmployeeData.GetAllRecords();
-
- var keyName = primaryColumnName?.ToLowerInvariant();
- var toDelete = keyName switch
- {
- "employeeid" => employees.FirstOrDefault(x => string.Equals(x.EmployeeID, primaryColumnValue, System.StringComparison.OrdinalIgnoreCase)),
- _ => employees.FirstOrDefault(x => string.Equals(x.EmployeeID, primaryColumnValue, System.StringComparison.OrdinalIgnoreCase))
- };
- if (toDelete == null) return null;
-
- var idsToRemove = new HashSet(System.StringComparer.OrdinalIgnoreCase);
- CollectWithDescendants(employees, toDelete.EmployeeID, idsToRemove);
-
- employees.RemoveAll(e => idsToRemove.Contains(e.EmployeeID));
-
- if (!string.IsNullOrWhiteSpace(toDelete.ManagerID))
+ "dataManager": {
+ "Where": [
{
- var manager = employees.FirstOrDefault(e => string.Equals(e.EmployeeID, toDelete.ManagerID, System.StringComparison.OrdinalIgnoreCase));
- if (manager != null)
- {
- manager.HasChild = employees.Any(e => string.Equals(e.ManagerID, manager.EmployeeID, System.StringComparison.OrdinalIgnoreCase));
- }
+ "Condition": "and",
+ "Predicates": [
+ {
+ "Field": "title",
+ "Operator": "equal",
+ "Value": "Director",
+ "Predicates": []
+ }
+ ]
}
-
- return toDelete;
- }
+ ],
+ "Skip": 0,
+ "Take": 10,
+ "RequiresCounts": true
}
}
+ ``````
-```
-
-The following code shows how to bind the TreeGrid with a GraphQL service and enable CRUD operations.
-
-{% tabs %}
-{% highlight razor tabtitle="Home.razor" %}
-
-@page "/"
-@rendermode InteractiveServer
-@using Syncfusion.Blazor
-@using Syncfusion.Blazor.Data
-@using Syncfusion.Blazor.Grids
-@using Syncfusion.Blazor.TreeGrid
-@using Syncfusion.Blazor.DropDowns
-@using Syncfusion.Blazor.Calendars
-@using System.Text.Json.Serialization
-
-
-
- Employees
- Category:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var p = (e.Priority ?? "Low").Trim().ToLowerInvariant();
- var cls = p switch
- {
- "critical" => "chip chip-critical",
- "high" => "chip chip-high",
- "medium" => "chip chip-medium",
- _ => "chip chip-low"
- };
- var label = string.IsNullOrWhiteSpace(e.Priority) ? "Low" : e.Priority!;
- }
- @label
-
-
-
-
-
-
- @{
- var e = (EmployeeData)context;
- var pct = Math.Clamp(e.Progress ?? 0, 0, 100);
- var barClass = pct >= 80 ? "progress-bar prog-high"
- : pct >= 50 ? "progress-bar prog-mid"
- : "progress-bar prog-low";
- }
-
-
-
-
-
-
-
-
-
-
-
-
-@code {
- // - Replace https://localhost:xxxx/graphql with the actual server port.
+ **Filter Logic**
- public class DropDownData { public string ID { get; set; } = ""; public string Name { get; set; } = ""; }
+- Top-level "Where" is an array of filter groups; groups are combined with AND by the resolver.
+- This specific group:
+ - Condition: "and" — combine its predicates with AND.
+ - Predicates: one predicate that filters the "title" field.
+ - Field: "title" → maps to EmployeeData.Title (GraphQL / camelCase).
+ - Operator: "equal" → exact match comparison.
+ - Value: "Director" → filter value.
+ - Predicates: [] → no nested filters under this predicate.
+- Skip: 0 and Take: 10 — return the first page (offset 0, up to 10 items).
+- RequiresCounts: true — request total matching count for paging UI.
- private readonly List CategoryOptions = new()
- {
- new() { ID = "HR", Name = "HR" },
- new() { ID = "PM", Name = "Project Management" }
- };
+ The backend resolver receives this and processes the filter conditions in the `EmployeesData` method using recursive evaluation to handle any depth of nesting. Filtering feature is now active.
- public bool expandState { get; set; }
- private string SelectedCategory { get; set; } = "HR";
- private int treeindex { get; set; } = 3;
- private bool showHRColumns = true;
- private bool showPMColumns = false;
- private bool showEmail = true;
- public string headerName { get; set; } = "Name";
- private SfTreeGrid? tree;
+ ---
- // Revert to ONLY $dataManager; server reads TreeGrid flags from dataManager.Params
- private const string HrQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- }
- }
- }";
+### Perform CRUD Operations
+
+ CRUD operations (Create, Read, Update, Delete) provide complete data‑management capabilities within the TreeGrid. The TreeGrid offers built‑in dialogs and action buttons to perform these operations, while backend resolvers execute the corresponding data modifications.
- private const string PmQuery = @"
- query employeesData($dataManager: DataManagerRequestInput!) {
- employeesData(dataManager: $dataManager) {
- count
- result {
- employeeID
- managerID
- hasChild
- name
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }
- }";
+ Add the TreeGrid `TreeGridEditSettings` and `Toolbar` configuration to enable create, read, update, and delete (CRUD) operations.
+
+ ``````razor
+
+
+
+
+
+
+
+ ``````
+
+Add the toolbar items list in the `@code` block:
+
+```csharp
+@code {
+ private List ToolbarItems = new List { "Add", "Edit", "Delete", "Update", "Cancel", "Search"};
+
+ // CustomAdaptor class code...
+}
+```
+
+**Insert**
+
+ The Insert operation enables adding new expense records to the system. When the Add button in the toolbar is selected, the TreeGrid 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 = HrQuery,
+
// ResolverName should match the GraphQL field name (camelCase)
- Mutation = new Syncfusion.Blazor.Data.GraphQLMutation
- {
- Insert = @"
- mutation create($record: EmployeeDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
- createEmployee(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }",
- Update = @"
- mutation update($record: EmployeeDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
- updateEmployee(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
- }",
- Delete = @"
- mutation delete($primaryColumnValue: String!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
- deleteEmployee(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
- employeeID
- managerID
- hasChild
- name
- lastName
- title
- location
- dateJoined
- salaryPerMonth
- email
- projectId
- projectDetails
- projectStatus
- priority
- progress
- projectStartDate
- projectEndDate
- }
+ ResolverName = "employeesData",
+
+ Mutation = new GraphQLMutation
+ {
+ Insert = @"mutation create($record: EmployeeDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
+ createEmployee(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
}"
- },
- ResolverName = "employeesData"
- };
-
- private async Task ModeChange(ChangeEventArgs args)
- {
- SelectedCategory = args?.Value ?? "HR";
- if (SelectedCategory == "PM")
- {
- adaptorOptions.Query = PmQuery;
- showHRColumns = false; showPMColumns = true; treeindex = 1;
- showEmail = false;
- headerName = "Assigned To";
- }
- else
- {
- adaptorOptions.Query = HrQuery;
- showHRColumns = true; showPMColumns = false; treeindex = 3;
- showEmail = true;
- headerName = "Name";
- if (tree is not null) await tree.ClearFilteringAsync();
}
- await tree.CallStateHasChangedAsync();
- }
-
- public class EmployeeData
- {
- [JsonPropertyName("employeeID")] public string EmployeeID { get; set; } = "";
- [JsonPropertyName("managerID")] public string? ManagerID { get; set; }
- [JsonPropertyName("hasChild")] public bool HasChild { get; set; }
- [JsonPropertyName("name")] public string? Name { get; set; }
- [JsonPropertyName("lastName")] public string? LastName { get; set; }
- [JsonPropertyName("title")] public string? Title { get; set; }
- [JsonPropertyName("location")] public string? Location { get; set; }
- [JsonPropertyName("dateJoined")] public DateTime? DateJoined { get; set; }
- [JsonPropertyName("salaryPerMonth")] public decimal? SalaryPerMonth { get; set; }
- [JsonPropertyName("email")] public string? Email { get; set; }
- [JsonPropertyName("projectId")] public string? ProjectId { get; set; }
- [JsonPropertyName("projectDetails")] public string? ProjectDetails { get; set; }
- [JsonPropertyName("projectStatus")] public string? ProjectStatus { get; set; }
- [JsonPropertyName("priority")] public string? Priority { get; set; }
- [JsonPropertyName("progress")] public int? Progress { get; set; }
- [JsonPropertyName("projectStartDate")] public DateTime? ProjectStartDate { get; set; }
- [JsonPropertyName("projectEndDate")] public DateTime? ProjectEndDate { get; set; }
- }
-}
-
-{% endhighlight %}
-
-{% highlight c# tabtitle="GraphQLMutation.cs" %}
+ };
+ }
+ ``````
-using GraphQLServer.Models;
-using System.Collections.Generic;
-using System.Linq;
-using HotChocolate;
+ 2. Implement the ``CreateEmployee`` method in the ``GraphQLMutation`` class:
-namespace GraphQLServer.GraphQL
+ ``````csharp
+ namespace TreeGrid_GraphQLAdaptor.Models
{
- public class GraphQLMutation
+ public class GraphQLMutation
{
public EmployeeData CreateEmployee(
- EmployeeData record,
- int index,
- string action,
- [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
+ EmployeeData record,
+ int index,
+ string action,
+ [GraphQLType(typeof(AnyType))] IDictionary additionalParameters)
{
var employees = EmployeeData.GetAllRecords();
@@ -3685,8 +2813,170 @@ namespace GraphQLServer.GraphQL
return entity;
}
+ }
+}
+ ``````
- public EmployeeData? UpdateEmployee(
+**Insert Operation Logic Breakdown:**
+
+| Step | Purpose | Implementation |
+|------|---------|----------------|
+| **1. Receive Input** | Backend receives the new record object from the client | `CreateEmployee` receives `record: EmployeeData` — the client must supply `employeeID` (server does not auto‑generate IDs in this sample) |
+| **2. Validate Manager** | Ensure manager state is consistent | If `ManagerID` is provided and a matching manager exists, set that manager's `HasChild = true` |
+| **3. Insert Record** | Place the new record in the in‑memory list | Insert at `index` when 0 ≤ index ≤ count; otherwise append (`employees.Insert(index, entity)` or `employees.Add(entity)`) |
+| **4. Return Created** | Return the created entity back to the client | `CreateEmployee` returns the created `EmployeeData` object (with the same fields supplied) |
+
+ **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: EmployeeDataInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
+ createEmployee(record: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
+}
+ ```
+
+ **Variables Sent with the Request:**
+
+ ```json
+ {
+ "record": {
+ "employeeID": "EMP2000",
+ "managerID": null,
+ "firstName": "Alice",
+ "lastName": "Johnson",
+ "name": "Alice Johnson",
+ "title": "Engineer",
+ "location": "Seattle",
+ "dateJoined": "2024-06-01T00:00:00Z",
+ "salaryPerMonth": 8500,
+ "email": "alice.johnson151@company.com",
+ "projectDetails": "Mobile App",
+ "projectStatus": "Open",
+ "priority": "High",
+ "progress": 5,
+ "projectStartDate": "2024-06-01T00:00:00Z",
+ "projectEndDate": "2025-06-01T00:00:00Z",
+ "projectId": "PRJ151",
+ "hasChild": false
+ },
+ "index": 0,
+ "action": "add",
+ "additionalParameters": {}
+}
+ ```
+
+ **Parameter Explanation:**
+
+ | Parameter | Type | Purpose | Example |
+ |-----------|------|---------|---------|
+ | `record` | `EmployeeData` | New employee object; must include `employeeID` when using this sample implementation.| Expense data filled in the add form |
+ | `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 TreeGrid | Empty object `{}` or additional metadata |
+
+ **Backend Response:**
+
+ The mutation returns the created record directly:
+
+ ```json
+ {
+ "data": {
+ "createEmployee": {
+ "employeeID": "EMP2000",
+ "managerID": null,
+ "hasChild": false,
+ "name": "Alice Johnson",
+ "lastName": "Johnson",
+ "title": "Engineer",
+ "location": "Seattle",
+ "dateJoined": "2024-06-01T00:00:00.000Z",
+ "salaryPerMonth": 8500,
+ "email": "alice.johnson151@company.com",
+ "projectId": "PRJ151",
+ "projectDetails": "Mobile App",
+ "projectStatus": "Open",
+ "priority": "High",
+ "progress": 5,
+ "projectStartDate": "2024-06-01T00:00:00.000Z",
+ "projectEndDate": "2025-06-01T00:00:00.000Z"
+ }
+ }
+}
+ ```
+
+**Update**
+The Update operation enables modifying existing expense records. When the Edit action is selected from the toolbar and a row is chosen, the TreeGrid 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.
+
+**Instructions:**
+
+1. Update the `GraphQLAdaptorOptions` in the `@code` block to include the Update mutation:
+
+```csharp
+@code {
+ private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+ {
+ Query = HrQuery,
+
+ // ResolverName should match the GraphQL field name (camelCase)
+ ResolverName = "employeesData",
+
+ Mutation = new GraphQLMutation
+ {
+ Update = @"mutation update($record: EmployeeDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
+ updateEmployee(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
+ }"
+ }
+ };
+}
+```
+
+2. Implement the `UpdateEmployee` method in the `GraphQLMutation` class:
+
+```csharp
+namespace TreeGrid_GraphQLAdaptor.Models
+{
+ public class GraphQLMutation
+ {
+ public EmployeeData? UpdateEmployee(
EmployeeData record,
string action,
string primaryColumnName,
@@ -3745,7 +3035,164 @@ namespace GraphQLServer.GraphQL
return existing;
}
+ }
+}
+```
+
+**Update Operation Logic Breakdown:**
+
+| Step | Purpose | Implementation |
+|------|---------|----------------|
+| **1. Locate Record** | Find the existing employee by primary key | `UpdateEmployee` looks up by `primaryColumnName` (case‑insensitive); default lookup is by `employeeID` |
+| **2. Validate Existence** | Ensure record exists before modifying | `if (existing == null) return null;` |
+| **3. Apply Field Changes** | Copy only provided fields from incoming `record` | Each property is set only when the incoming value is non-null (or HasValue for nullable value types), e.g. `if (record.FirstName != null) existing.FirstName = record.FirstName;` |
+| **4. Handle Manager Change** | Maintain parent/child relationship consistency | If `ManagerID` changes: assign `existing.ManagerID = record.ManagerID`; set `newManager.HasChild = true` when present; recompute `oldManager.HasChild` by checking remaining children |
+| **5. Preserve Unchanged Data** | Avoid overwriting unspecified fields | Unspecified/null fields are left intact (ID is preserved) |
+| **6. Update HasChild Flag** | Allow explicit HasChild updates from client | `if (record.HasChild) existing.HasChild = record.HasChild;` |
+| **7. Return Updated** | Return the modified EmployeeData instance | The resolver returns the mutated `existing` object (or null if not found) |
+
+**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: EmployeeDataInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: String!, $additionalParameters: Any) {
+ updateEmployee(record: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
+}
+```
+
+**Variables Sent with the Request:**
+
+```json
+{
+ "record": {
+ "employeeID": "EMP021",
+ "firstName": "Alice",
+ "lastName": "Johnson",
+ "name": "Alice Johnson",
+ "hasChild": true
+ },
+ "action": "save",
+ "primaryColumnName": "EmployeeID",
+ "primaryColumnValue": "EMP021",
+ "additionalParameters": {}
+}
+```
+
+**Parameter Explanation:**
+
+| Parameter | Type | Purpose | Example |
+|-----------|------|---------|---------|
+| `record` | `EmployeeData` | Object with fields to update; only non-null / HasValue fields are applied | See JSON `record` above |
+| `action` | `string` | Action descriptor from the tree grid (commonly `"save"` for updates) | `"save"` |
+| `primaryColumnName` | `string` | Name of the primary key column used by the tree grid/backend (case‑insensitive) | `"EmployeeID"` |
+| `primaryColumnValue` | `string` | Primary key value identifying the record to update | `"EMP011"` |
+| `additionalParameters` | `Any` | Optional free form metadata passed through by the adaptor | `{}` or custom context |
+
+**Backend Response:**
+
+The mutation returns the updated record with all changes applied:
+
+```json
+{
+ "data": {
+ "updateEmployee": {
+ "employeeID": "EMP021",
+ "managerID": null,
+ "hasChild": true,
+ "name": "Alice Johnson",
+ "lastName": "Johnson",
+ "title": "Manager",
+ "location": "New York",
+ "dateJoined": "2018-03-07T00:00:00.000Z",
+ "salaryPerMonth": 17400,
+ "email": "abigail.edwards021@company.com",
+ "projectId": "PRJ021",
+ "projectDetails": "Backup Service",
+ "projectStatus": "Open",
+ "priority": "Medium",
+ "progress": 74,
+ "projectStartDate": "2024-03-03T00:00:00.000Z",
+ "projectEndDate": "2025-09-03T00:00:00.000Z"
+ }
+ }
+}
+```
+
+---
+
+**Delete**
+
+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.
+
+**Instructions:**
+
+1. Update the `GraphQLAdaptorOptions` in the `@code` block to include the Delete mutation:
+
+```csharp
+@code {
+ private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
+ {
+ Query = HrQuery,
+
+ // ResolverName should match the GraphQL field name (camelCase)
+ ResolverName = "employeesData",
+
+ Mutation = new GraphQLMutation
+ {
+ Delete = @"mutation delete($primaryColumnValue: String!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
+ deleteEmployee(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
+ }"
+ }
+ };
+}
+```
+2. Implement the `DeleteExpense` method in the `GraphQLMutation` class:
+
+```csharp
+namespace TreeGrid_GraphQLAdaptor.Models
+{
+ public class GraphQLMutation
+ {
public EmployeeData? DeleteEmployee(
string primaryColumnValue,
string action,
@@ -3778,7 +3225,6 @@ namespace GraphQLServer.GraphQL
return toDelete;
}
-
private static void CollectWithDescendants(List all, string rootId, HashSet bag)
{
if (bag.Contains(rootId)) return;
@@ -3790,9 +3236,149 @@ namespace GraphQLServer.GraphQL
}
}
}
+```
+
+**Delete Operation Logic Breakdown:**
+
+| Step | Purpose | Implementation |
+|------|---------|----------------|
+| **1. Receive Key** | Backend receives the primary key identifying the record to delete | `DeleteEmployee` parameter `primaryColumnValue` contains the EmployeeID (string) |
+| **2. Find Record & Collect Descendants** | Locate the record and all its descendants so child rows are removed too | Locate `toDelete` by matching primaryColumnName (case‑insensitive) to `EmployeeID`; call `CollectWithDescendants(employees, toDelete.EmployeeID, idsToRemove)` to gather subtree IDs |
+| **3. Remove Records** | Remove the record and all collected descendants from the in‑memory list | `employees.RemoveAll(e => idsToRemove.Contains(e.EmployeeID))` |
+| **4. Update Parent HasChild** | Recompute the parent's HasChild flag after deletion | If `toDelete.ManagerID` is not empty, find manager and set `manager.HasChild = employees.Any(e => e.ManagerID == manager.EmployeeID)` |
+| **5. Return Deleted** | Return the deleted EmployeeData object for client consumption | `DeleteEmployee` returns the removed `toDelete` object (or `null` if not found) |
+
+**GraphQL Mutation Request:**
+
+```graphql
+mutation delete($primaryColumnValue: String!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
+ deleteEmployee(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
+ employeeID
+ managerID
+ hasChild
+ name
+ lastName
+ title
+ location
+ dateJoined
+ salaryPerMonth
+ email
+ projectId
+ projectDetails
+ projectStatus
+ priority
+ progress
+ projectStartDate
+ projectEndDate
+ }
+}
+```
+
+**Variables Sent with the Request:**
+
+```json
+{
+ "primaryColumnValue": "EMP021",
+ "action": "delete",
+ "primaryColumnName": "EmployeeID",
+ "additionalParameters": {}
+}
+```
+
+**Parameter Explanation:**
+
+| Parameter | Type | Purpose | Example |
+|-----------|------|---------|---------|
+| `primaryColumnValue` | `string` | Primary key value identifying which employee to delete | `"EMP021"` |
+| `action` | `string` | Action descriptor from the tree grid (commonly `"delete"`) | `"delete"` |
+| `primaryColumnName` | `string` | Name of the primary key column used by tree grid/backend (case‑insensitive) | `"EmployeeID"` |
+| `additionalParameters` | `Any` | Optional free form metadata passed through by the adaptor | `{}` or custom context |
+
+**Backend Response:**
+
+The mutation returns a boolean success/failure indicator:
+
+```json
+{
+ "data": {
+ "deleteEmployee": {
+ "employeeID": "EMP021",
+ "managerID": null,
+ "hasChild": true,
+ "name": "Alice Johnson",
+ "lastName": "Johnson",
+ "title": "Manager",
+ "location": "New York",
+ "dateJoined": "2018-03-07T00:00:00.000Z",
+ "salaryPerMonth": 17400,
+ "email": "abigail.edwards021@company.com",
+ "projectId": "PRJ021",
+ "projectDetails": "Backup Service",
+ "projectStatus": "Open",
+ "priority": "Medium",
+ "progress": 74,
+ "projectStartDate": "2024-03-03T00:00:00.000Z",
+ "projectEndDate": "2025-09-03T00:00:00.000Z"
+ }
+ }
+}
+```
+
+If the record doesn't exist:
+
+```json
+{
+ "data": {
+ "deleteEmployee": null
+ }
+}
+```
+
+## Running the Application
+
+**Step 1: Build the Application**
+
+1. Open the terminal or Package Manager Console.
+2. Navigate to the project directory.
+3. Run the following command:
+
+```powershell
+dotnet build
+```
+
+**Step 2: Run the Application**
+
+Execute the following command:
+
+```powershell
+dotnet run
+```
+
+**Step 3: Access the Application**
+
+1. Open a web browser.
+2. Navigate to `https://localhost:7213` (or the port shown in the terminal).
+3. The Expense Tracker System is now running and ready to use.
+
+---
+
+## Complete Sample Repository
+
+A complete, working sample implementation is available in the [GitHub repository](#).
+
+---
+## Summary
-{% endhighlight %}
-{% endtabs %}
+This guide demonstrates how to:
-
+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 tree 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 TreeGrid and configure the GraphQL adaptor. [🔗](#step-3-configure-graphql-adaptor-and-data-binding)
+9. Perform CRUD operations from the tree grid using GraphQL mutations. [🔗](#perform-crud-operations)
+The application now provides a complete solution for managing employees with a modern Syncfusion Blazor TreeGrid integrated with a Hot Chocolate GraphQL backend.