Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This file explains how Visual Studio created the project.

The following steps were used to generate this project:
- Create new ASP\.NET Core Web API project.
- Update project file to add a reference to the frontend project and set SPA properties.
- Update `launchSettings.json` to register the SPA proxy as a startup assembly.
- Add project to the startup projects list.
- Write this file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Grid_MySQL.Server.Data;
using Grid_MySQL.Server.Models;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.Base;

namespace Grid_MySQL.Server.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GridController(AppDataConnection db) : ControllerBase
{
private readonly AppDataConnection _db = db;

// POST: /api/grid/url
// All data actions (search, filter, multi-column sort, paging) are delegated to sp_GetTransactions via pre-built MySQL clause strings.
[HttpPost("url")]
public async Task<IActionResult> UrlDatasource([FromBody] DataManagerRequest dm)
{
// Translate Syncfusion DataManagerRequest into MySQL clause strings using SqlClauseBuilder.

var searchClause = SqlClauseBuilder.BuildSearchClause(dm.Search);
var whereClause = SqlClauseBuilder.BuildWhereClause(dm.Where);
var sortClause = SqlClauseBuilder.BuildSortClause(dm.Sorted);

// Call the stored procedure.
var result = await _db.SpGetTransactionsAsync(
searchClause: searchClause,
whereClause: whereClause,
sortClause: sortClause,
skip: dm.Skip,
take: dm.Take
);

// Return the response
if (dm.RequiresCounts)
return Ok(new { result = result.Rows, count = result.TotalCount });

return Ok(result.Rows);
}

// POST: /api/grid/insert
// Delegates to sp_InsertTransaction and returns the new row with its generated Id.
[HttpPost("insert")]
public async Task<IActionResult> Insert([FromBody] CRUDModel<Transaction> model)
{
if (model?.Value == null)
return BadRequest();

var value = model.Value;

// Ensure CreatedAt is set before insert.
if (value.CreatedAt == default)
value.CreatedAt = DateTime.UtcNow;

// Call stored procedure; it sets the OUT param and returns the new Id.
var newId = await _db.SpInsertTransactionAsync(value);
value.Id = newId;

return Ok(value);
}

// POST: /api/grid/update
// Delegates to sp_UpdateTransaction. Returns 404 when the row is not found.
[HttpPost("update")]
public async Task<IActionResult> Update([FromBody] CRUDModel<Transaction> model)
{
if (model?.Value == null)
return BadRequest();

var value = model.Value;

// Call stored procedure; it returns the number of affected rows.
var affected = await _db.SpUpdateTransactionAsync(value);
if (affected == 0)
return NotFound("Record not found");

return Ok(value);
}

// POST: /api/grid/remove
// Delegates to sp_DeleteTransaction. Returns 404 when the row is not found.
[HttpPost("remove")]
public async Task<IActionResult> Remove([FromBody] CRUDModel<Transaction> model)
{
var key = Convert.ToInt32(model.Key);

// Call stored procedure; it returns the number of affected rows.
var affected = await _db.SpDeleteTransactionAsync(key);
if (affected == 0)
return NotFound("Record not found");

return Ok(new { key });
}

// POST: /api/grid/batch
// Handles batch add / update / delete in a single DB transaction,
// each operation routed through the relevant stored procedure.
[HttpPost("batch")]
public async Task<IActionResult> BatchUpdate([FromBody] CRUDModel<Transaction> payload)
{
using var tr = await _db.BeginTransactionAsync();

// INSERT many – sp_InsertTransaction is called once per added row.
if (payload.Added != null && payload.Added.Count > 0)
{
foreach (var r in payload.Added)
{
if (r.CreatedAt == default)
r.CreatedAt = DateTime.UtcNow;

var newId = await _db.SpInsertTransactionAsync(r);
r.Id = newId; // echo generated key back to the client
}
}

// UPDATE many – sp_UpdateTransaction is called once per changed row.
if (payload.Changed != null && payload.Changed.Count > 0)
{
foreach (var r in payload.Changed)
await _db.SpUpdateTransactionAsync(r);
}

// DELETE many – sp_DeleteTransaction is called once per deleted row.
if (payload.Deleted != null && payload.Deleted.Count > 0)
{
foreach (var r in payload.Deleted)
if (r.Id.HasValue)
await _db.SpDeleteTransactionAsync(r.Id.Value);
}

await tr.CommitAsync();
return Ok(payload);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Grid_MySQL.Server.Models;
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.MySql;

namespace Grid_MySQL.Server.Data
{
// Holds the paged rows and the total matching count returned by sp_GetTransactions
public sealed class PagedResult
{
public List<Transaction> Rows { get; init; } = [];
public int TotalCount { get; init; }
}

public sealed class AppDataConnection : DataConnection
{
public AppDataConnection(IConfiguration config)
: base(
new DataOptions().UseMySql(
config.GetConnectionString("MySqlConn")!,
MySqlVersion.MySql80,
MySqlProvider.MySqlConnector
)
)
{
InlineParameters = true;
}

// Direct table access
public ITable<Transaction> Transactions => this.GetTable<Transaction>();

// ------------------------------------------------------------------
// Stored-procedure helpers
// ------------------------------------------------------------------

// Calls sp_GetTransactions with all data-action parameters so that sorting, filtering,
// searching, and paging are executed entirely inside MySQL.

public async Task<PagedResult> SpGetTransactionsAsync(
string? searchClause,
string? whereClause,
string? sortClause,
int skip,
int take)
{
// OUT parameter – MySQL writes the total count into it.
var outCount = new DataParameter("p_TotalCount", null, LinqToDB.DataType.Int32)
{
Direction = System.Data.ParameterDirection.Output
};

// sp_GetTransactions returns one result set (the paged rows) and
// sets the OUT parameter p_TotalCount.
var rows = await this.QueryProcAsync<Transaction>(
"sp_GetTransactions",
new DataParameter("p_SearchClause", searchClause ?? string.Empty, LinqToDB.DataType.NVarChar),
new DataParameter("p_WhereClause", whereClause ?? string.Empty, LinqToDB.DataType.NVarChar),
new DataParameter("p_SortClause", sortClause ?? string.Empty, LinqToDB.DataType.NVarChar),
new DataParameter("p_Skip", skip, LinqToDB.DataType.Int32),
new DataParameter("p_Take", take, LinqToDB.DataType.Int32),
outCount
);

return new PagedResult
{
Rows = rows.ToList(),
TotalCount = Convert.ToInt32(outCount.Value)
};
}


// Calls sp_InsertTransaction, captures the OUT parameter p_NewId, and returns the newly generated auto-increment Id.
public async Task<int> SpInsertTransactionAsync(Transaction t)
{
// The OUT parameter must be declared as a DataParameter with
// Direction = Output so LinqToDB reads back the value MySQL sets.
var outId = new DataParameter("p_NewId", null, LinqToDB.DataType.Int32)
{
Direction = System.Data.ParameterDirection.Output
};

await this.ExecuteProcAsync(
"sp_InsertTransaction",
new DataParameter("p_TransactionId", t.TransactionId, LinqToDB.DataType.NVarChar),
new DataParameter("p_CustomerId", t.CustomerId, LinqToDB.DataType.Int32),
new DataParameter("p_OrderId", t.OrderId, LinqToDB.DataType.Int32),
new DataParameter("p_InvoiceNumber", t.InvoiceNumber, LinqToDB.DataType.NVarChar),
new DataParameter("p_Description", t.Description, LinqToDB.DataType.NVarChar),
new DataParameter("p_Amount", t.Amount, LinqToDB.DataType.Decimal),
new DataParameter("p_CurrencyCode", t.CurrencyCode, LinqToDB.DataType.NVarChar),
new DataParameter("p_TransactionType", t.TransactionType, LinqToDB.DataType.NVarChar),
new DataParameter("p_PaymentGateway", t.PaymentGateway, LinqToDB.DataType.NVarChar),
new DataParameter("p_CreatedAt", t.CreatedAt, LinqToDB.DataType.DateTime),
new DataParameter("p_CompletedAt", t.CompletedAt, LinqToDB.DataType.DateTime),
new DataParameter("p_Status", t.Status, LinqToDB.DataType.NVarChar),
outId
);

return Convert.ToInt32(outId.Value);
}


// Calls sp_UpdateTransaction for the given transaction row. Returns the number of rows affected (0 = not found, 1 = updated).
public async Task<int> SpUpdateTransactionAsync(Transaction t)
{
// sp_UpdateTransaction returns a single-row result set containing
// AffectedRows. Use QueryProcAsync to read that result set.
var rows = await this.QueryProcAsync<AffectedRowsResult>(
"sp_UpdateTransaction",
new DataParameter("p_Id", t.Id, LinqToDB.DataType.Int32),
new DataParameter("p_TransactionId", t.TransactionId, LinqToDB.DataType.NVarChar),
new DataParameter("p_CustomerId", t.CustomerId, LinqToDB.DataType.Int32),
new DataParameter("p_OrderId", t.OrderId, LinqToDB.DataType.Int32),
new DataParameter("p_InvoiceNumber", t.InvoiceNumber, LinqToDB.DataType.NVarChar),
new DataParameter("p_Description", t.Description, LinqToDB.DataType.NVarChar),
new DataParameter("p_Amount", t.Amount, LinqToDB.DataType.Decimal),
new DataParameter("p_CurrencyCode", t.CurrencyCode, LinqToDB.DataType.NVarChar),
new DataParameter("p_TransactionType", t.TransactionType, LinqToDB.DataType.NVarChar),
new DataParameter("p_PaymentGateway", t.PaymentGateway, LinqToDB.DataType.NVarChar),
new DataParameter("p_CreatedAt", t.CreatedAt, LinqToDB.DataType.DateTime),
new DataParameter("p_CompletedAt", t.CompletedAt, LinqToDB.DataType.DateTime),
new DataParameter("p_Status", t.Status, LinqToDB.DataType.NVarChar)
);

return rows.FirstOrDefault()?.AffectedRows ?? 0;
}


// Calls sp_DeleteTransaction for the given primary key. Returns the number of rows deleted (0 = not found, 1 = deleted).
public async Task<int> SpDeleteTransactionAsync(int id)
{
var rows = await this.QueryProcAsync<AffectedRowsResult>(
"sp_DeleteTransaction",
new DataParameter("p_Id", id, LinqToDB.DataType.Int32)
);

return rows.FirstOrDefault()?.AffectedRows ?? 0;
}
}

// Small DTO used to map the AffectedRows result-set column that
// sp_UpdateTransaction and sp_DeleteTransaction return.
internal sealed class AffectedRowsResult
{
public int AffectedRows { get; set; }
}
}
Loading