Files
DbFirst/DbFirst.API/Dashboards/SqlDashboardStorage.cs
OlgunR 4fa3bcae3d Make dashboard change notifications async and robust
Refactored DashboardChangeNotifier and IDashboardChangeNotifier to use async notification with improved error handling and logging. Updated SqlDashboardStorage to call the new async notification method after dashboard changes, ensuring non-blocking and reliable client updates.
2026-04-21 13:42:31 +02:00

139 lines
5.7 KiB
C#

using System.Data;
using System.Text;
using System.Xml.Linq;
using DevExpress.DashboardWeb;
using Microsoft.Data.SqlClient;
namespace DbFirst.API.Dashboards;
public sealed class SqlDashboardStorage : IEditableDashboardStorage
{
private readonly string _connectionString;
private readonly string _tableName;
private readonly Func<string?>? _userProvider;
private readonly IDashboardChangeNotifier? _notifier;
public SqlDashboardStorage(string connectionString, string tableName = "TBDD_SMF_CONFIG", Func<string?>? userProvider = null, IDashboardChangeNotifier? notifier = null)
{
_connectionString = connectionString;
_tableName = tableName;
_userProvider = userProvider;
_notifier = notifier;
}
public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo()
{
var dashboards = new List<DashboardInfo>();
using var connection = new SqlConnection(_connectionString);
using var command = new SqlCommand($"SELECT DashboardId, DashboardName FROM dbo.[{_tableName}] WHERE ACTIVE = 1 ORDER BY DashboardName", connection);
connection.Open();
using var reader = command.ExecuteReader();
while (reader.Read())
{
var id = reader.GetString(0);
var name = reader.GetString(1);
dashboards.Add(new DashboardInfo { ID = id, Name = name });
}
return dashboards;
}
public XDocument LoadDashboard(string dashboardId)
{
using var connection = new SqlConnection(_connectionString);
using var command = new SqlCommand($"SELECT DashboardData FROM dbo.[{_tableName}] WHERE DashboardId = @Id AND ACTIVE = 1", connection);
command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });
connection.Open();
var data = command.ExecuteScalar() as byte[];
if (data == null)
{
throw new ArgumentException($"Dashboard '{dashboardId}' not found.");
}
var xml = Encoding.UTF8.GetString(data);
var doc = XDocument.Parse(xml);
NormalizeCatalogDateDimensions(doc);
return doc;
}
private static void NormalizeCatalogDateDimensions(XDocument doc)
{
var dateMembers = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"AddedWhen",
"ChangedWhen"
};
foreach (var dimension in doc.Descendants("Dimension"))
{
var member = dimension.Attribute("DataMember")?.Value;
if (member == null || !dateMembers.Contains(member))
{
continue;
}
var interval = dimension.Attribute("DateTimeGroupInterval")?.Value;
if (string.IsNullOrWhiteSpace(interval) || string.Equals(interval, "Year", StringComparison.OrdinalIgnoreCase))
{
dimension.SetAttributeValue("DateTimeGroupInterval", "DayMonthYear");
}
}
}
public string AddDashboard(XDocument dashboard, string dashboardName)
{
var id = string.IsNullOrWhiteSpace(dashboardName)
? Guid.NewGuid().ToString("N")
: dashboardName;
var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting));
var userName = _userProvider?.Invoke();
using var connection = new SqlConnection(_connectionString);
using var command = new SqlCommand($"INSERT INTO dbo.[{_tableName}] (ACTIVE, DashboardId, DashboardName, DashboardData, ADDED_WHO, ADDED_WHEN) VALUES (1, @Id, @Name, @Data, COALESCE(@User, SUSER_SNAME()), SYSUTCDATETIME())", connection);
command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = id });
command.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar, 256) { Value = string.IsNullOrWhiteSpace(dashboardName) ? id : dashboardName });
command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload });
command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value });
connection.Open();
command.ExecuteNonQuery();
_ = _notifier?.NotifyChangedAsync();
return id;
}
public void SaveDashboard(string dashboardId, XDocument dashboard)
{
var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting));
var userName = _userProvider?.Invoke();
using var connection = new SqlConnection(_connectionString);
using var command = new SqlCommand($"UPDATE dbo.[{_tableName}] SET DashboardData = @Data, CHANGED_WHO = COALESCE(@User, SUSER_SNAME()), CHANGED_WHEN = SYSUTCDATETIME() WHERE DashboardId = @Id", connection);
command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });
command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload });
command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value });
connection.Open();
var rows = command.ExecuteNonQuery();
if (rows == 0)
{
throw new ArgumentException($"Dashboard '{dashboardId}' not found.");
}
_ = _notifier?.NotifyChangedAsync();
}
public void DeleteDashboard(string dashboardId)
{
using var connection = new SqlConnection(_connectionString);
using var command = new SqlCommand($"DELETE FROM dbo.[{_tableName}] WHERE DashboardId = @Id", connection);
command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });
connection.Open();
command.ExecuteNonQuery();
_ = _notifier?.NotifyChangedAsync();
}
}