9/9/2024 M. Washington (Admin)
Microsoft Fabric and the Blazor Data Sheet
Using the Blazor DataSheet allows you to easily view and update data in your Microsoft Fabric Warehouse.
Creating Forms is the biggest cost in an application
Creating forms is often one of the most time-consuming and costly parts of building an application, but utilizing a Blazor DataSheet can significantly reduce both. By allowing users to view and update data directly within a flexible, grid-like interface, Blazor DataSheet eliminates the need for complex form-building, streamlining development and maintenance efforts.
Additionally, it supports custom validation, ensuring that specific business rules are enforced without the overhead of designing and managing traditional form structures. This approach not only saves time but also lowers development costs while maintaining robust data integrity.
Using Parquet Files To Update Data
When connecting to a SQL data source TSQL commands can be uses to read, update, and delete data as covered in the following article: Using a Blazor Excel Datasheet To Update A Database. However, when using Fabric data updates can be very slow, especially when updating large amounts of data.
Therefore, to perform updates, the following method will be used:
- Delete rows that have been removed or updated
- TSQL will be used for this step because deleting data is a fast process
- Any rows that are removed from the DataSheet will be deleted
- Any rows that are updated in the DataSheet will be deleted (to be added in the subsequent step)
- Create parquet file from the data
- Parquet is a the native format of data in the Fabric warehouse. However, it is being used in this instance as a way to compress the data into a format that is easily imported into Fabric
- Any rows that are added in the DataSheet will be added
- Any rows that are updated in the DataSheet will be added
- Parquet is a the native format of data in the Fabric warehouse. However, it is being used in this instance as a way to compress the data into a format that is easily imported into Fabric
- Import Parquet file into Azure Storage
- The parquet file must be placed in a location that allows the TSQL statement: COPY INTO to import the data. Azure Storage provides such a location. a SAS token will be created to allow Fabric the authentication to access the parquet file
- Use COPY INTO to import the parquet file into Fabric
- This TSQL statement can import a parquet file that has a million rows in seconds.
Walk-Thru
When you download and run the project at: https://github.com/ADefWebserver/FabricDataExplorer navigate to the Datasheet tab.
Click the Gear icon next to Fabric Connection to open the Database Connection dialog.
Connect to any Fabric Data Warehouse that can be reached by the Blazor application and click the Set Database Connection button.
Note: See the following article: Connect to Microsoft Fabric Using TSQL and Microsoft Blazor for instructions on connecting to Fabric using TSQL.
Click the Gear icon next to Azure Storage Connection to open the Azure Storage Connection dialog.
Enter the connection information and click the Set Storage Connection button.
Note: See https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal for assistance on creating Azure Storage containers.
The data will be retrieved.
The data will be displayed in the Blazor DataSheet.
The tables in the Fabric Data Warehouse will display.
Click the table dropdown to select a table to display.
Make any changes to the data including adding and deleting rows.
Click the Save button to save the changes.
When the update is complete, the confirmation result will display.
You can verify the data has been updated in the Fabric website.
You can also verify the data has been imported using the Microsoft SQL Server Management Studio.
Note: See the following article: Connect to Microsoft Fabric Using TSQL and Microsoft Blazor for instructions on connecting to Fabric using Microsoft SQL Server Management Studio.
The Application Code
Most of the code is found in the DatasheetEditor.razor file.
The Blazor DataSheet
Blazor DataSheet serves as the interface for displaying and editing data within the application, streamlining user interactions.
<Datasheet @ref="_datasheet"Sheet="sheet"Virtualise="true"CanUserMergeRows="false"ShowColHeadings="true"ShowRowHeadings="true"CanUserInsertRows="true"CanUserRemoveRows="true"CanUserInsertCols="false"CanUserRemoveCols="false"CanUserSort="false" />
The following code is used to display the toolbar:
<div class="rz-p-sm-1" style="background-color:whitesmoke;border-block:thin;border-block-color:lightgray;text-align:left"><button @onclick="btnSave" disabled="@SavingDisabled" style="background:none;border:none;"><i class="material-icons" style="color:black;">save</i></button><span style="border-right: 2px solid #D3D3D3;"></span><button @onclick="() => sheet.Commands.Undo()" title="Undo" style="background:none;border:none;"><i class="material-icons" style="color:black;">undo</i></button><button @onclick="() => sheet.Commands.Redo()" title="Redo" style="background:none;border:none;"><i class="material-icons" style="color:black;">redo</i></button><span style="border-right: 2px solid #D3D3D3;"></span><button @onclick="InsertRowAfterSelection" title="Insert Row" style="background:none;border:none;"><i class="material-icons" style="color:black;">input</i></button><button @onclick="RemoveRow" title="Delete Row" style="background:none;border:none;"><i class="material-icons" style="color:black;">logout</i></button><span style="border-right: 2px solid #D3D3D3;"></span><span style="border-right: 2px solid #D3D3D3;"></span><span> Table: </span><select @onchange="OnSelectedTableChanged" style="width: 300px; border-block:hidden">@foreach (var tableName in tableNames){@if (tableName == selectedTable){<option value="@tableName" selected>@tableName</option>}else{<option value="@tableName">@tableName</option>}}</select></div>
When data is retrieved, this SQL statement is used to pull in the data with the first field set to: __Id. This auto number field allows the various DataTables to be compared to determine which rows have been inserted and deleted.
// Get the data from the selected table
sql = $"SELECT ROW_NUMBER() OVER (ORDER BY {strPrimaryKeys}) AS __Id, {strTableNames} FROM {selectedTable}";
It is also used to retrieve rows from the DataTable that have been updated. This is possible because this method is fired when any row is changed and records the value of the row’s __Id field:
private void CellChanged(object sender, CellDataChangedEventArgs args){// Only add the row if one cell is changed
if ((args.Positions.Count() > 0) && (args.Positions.Count() < 2))
{// Get the row number
int row = Convert.ToInt32(args.Positions.FirstOrDefault().row);
// Use that row number to get the hidden __Id value
int __Id = Convert.ToInt32(sheet.Cells[row, 0].Value);
// Add the row to the list of changed rows
ColChangedRows.Add(__Id);}}
Updating Data
When the Save button is pressed, the data in the Blazor DataSheet is turned into a DataTable. This DataTable is compared to a DataTable that was created when the Blazor DataSheet was first loaded.
This allows the code to determine which rows have been deleted, Witch rows have been added, and which rows have been updated.
When rows are deleted, ProcessDeletedRows() is triggered to remove them from the Fabric database. Similarly, new rows are handled by ProcessInsertedRows(), and changes to existing rows are processed by ProcessUpdatedRows().
Once these operations are complete, if any updates exist, the system imports the modified data into the Fabric environment using a parquet file.
// *****************************************
// Deletes
// *****************************************
ProcessDeletedRows();// *****************************************
// Inserts
// *****************************************
ProcessInsertedRows();// *****************************************
// Updates
// *****************************************
ProcessUpdatedRows();
The code then creates an instance of the ImportDataTableIntoFabric class, which is responsible for importing data into Fabric. It sets various properties of the objImportDataTableIntoFabric object, including the DataTable, table name, Fabric connection string, and Azure storage account details.
The code then calls the ImportDatAsynca() method of the objImportDataTableIntoFabric object, which performs the actual data import process. This method returns a message indicating the result of the import.
ImportDataTableIntoFabric objImportDataTableIntoFabric = new ImportDataTableIntoFabric();
objImportDataTableIntoFabric.InputDataTable = UpdateDataTable;objImportDataTableIntoFabric.TableName = selectedTable;objImportDataTableIntoFabric.DataWarehouseConnectionString = FabricConnectionString;objImportDataTableIntoFabric.StorageAccountName = StorageAccountName;objImportDataTableIntoFabric.StorageAccountKey = AccountKey;objImportDataTableIntoFabric.StorageContainer = ContainerName;var result = await objImportDataTableIntoFabric.ImportDataAsync();
The ImportDataTableIntoFabric Class
This code defines the ImportDataTableIntoFabric class that facilitates the process of importing the data into the Fabric Data Warehouse through Azure Blob Storage using Parquet format.
The main method ImportDataAsync parses the DataTable and extracts data, and transforms the result set into a Parquet table.
var databaseFields = ExtractFieldsFromDataTable(InputDataTable);var parquetSchema =new ParquetSchema(databaseFields.Select(f => new DataField<string>(f)).ToList());var parquetTable =new Parquet.Rows.Table(parquetSchema);
// Loop through the DataTable and add rows to the Parquet table
foreach (DataRow dataRow in InputDataTable.Rows){var row = new Row(
databaseFields.Select(f => CleanValue(dataRow[f]?.ToString())).ToArray());parquetTable.Add(row);}
It then writes the table into an in-memory stream and uploads it to an Azure Blob Storage container.
using var ms = new MemoryStream();await parquetTable.WriteAsync(ms);ms.Position = 0;var blobClient = new BlobContainerClient(
$"DefaultEndpointsProtocol=https;" +
$"AccountName={StorageAccountName};" +
$"AccountKey={StorageAccountKey};" +
$"EndpointSuffix=core.windows.net",
StorageContainer);string fileName = Path.GetFileName(TableName) + ".parquet";var blob = blobClient.GetBlobClient($"{fileName}");
await blob.UploadAsync(ms, true);
await Task.Delay(5000);
After uploading the data, a SAS (Shared Access Signature) token is generated, which allows access to the blob with limited permissions.
private string GenerateSasToken(){var sasBuilder = new AccountSasBuilder
{Protocol = SasProtocol.Https,Services = AccountSasServices.Blobs,ResourceTypes = AccountSasResourceTypes.Container | AccountSasResourceTypes.Object,ExpiresOn = DateTimeOffset.UtcNow.AddHours(1)};sasBuilder.SetPermissions(AccountSasPermissions.Read | AccountSasPermissions.Write);return sasBuilder.ToSasQueryParameters(
new StorageSharedKeyCredential(
StorageAccountName,StorageAccountKey)).ToString();}
This SAS token is used to construct a SQL script that constructs the SQL command for performing a bulk data load using the COPY INTO command.
private List<string> GenerateSqlScripts(string tableName, IList<string> fields, string sasToken){var fieldSchema =string.Join(", ", fields.Select(f => $"[{f}] VARCHAR(4000)"));var filePath =$"https://{StorageAccountName}.blob.core.windows.net/{StorageContainer}/{tableName}.parquet";
return new List<string>{$"COPY INTO {tableName} " +
$"FROM '{filePath}' " +
$"WITH (file_type = 'PARQUET', CREDENTIAL=(IDENTITY= 'Shared Access Signature', SECRET='{sasToken}'))"
};}
Finally the script is executed to import the data.
using (var connection = new SqlConnection(DataWarehouseConnectionString)){connection.Open();foreach (var sql in sqlScripts){using var command = new SqlCommand(sql, connection);await command.ExecuteNonQueryAsync();await Task.Delay(5000); // To ensure operations are sequenced properly
}}
Links
FabricDataExplorer (Github - Source code)
Import Data Fast into Fabric Using Parquet Files
Connect to Microsoft Fabric Using TSQL and Microsoft Blazor
https://github.com/anmcgrath/BlazorDatasheet
https://github.com/aloneguid/parquet-dotnet
https://learn.microsoft.com/en-us/fabric/data-warehouse/ingest-data