529 lines
16 KiB
C#
529 lines
16 KiB
C#
using SQLVision.Core.Enums;
|
|
using SQLVision.Core.Interfaces;
|
|
using SQLVision.Core.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Windows.ApplicationModel.DataTransfer;
|
|
|
|
namespace SQLVision.UI.ViewModels;
|
|
|
|
public class MainViewModel : ObservableObject
|
|
{
|
|
private readonly IScriptManager _scriptManager;
|
|
private readonly ISqlExecutionService _executionService;
|
|
private readonly IExportService _exportService;
|
|
private readonly IControlFactory _controlFactory;
|
|
private readonly IVisualizerFactory _visualizerFactory;
|
|
private readonly ILogger<MainViewModel> _logger;
|
|
|
|
private readonly ObservableCollection<ScriptMetadata> _scripts = new();
|
|
private readonly ObservableCollection<ScriptCategory> _scriptCategories = new();
|
|
private readonly ObservableCollection<ExecutionHistoryItem> _history = new();
|
|
private readonly ObservableCollection<ResultTabViewModel> _resultTabs = new();
|
|
|
|
private ScriptMetadata _selectedScript;
|
|
private bool _isBusy;
|
|
private string _statusMessage;
|
|
private ResultTabViewModel _selectedResultTab;
|
|
private string _searchText;
|
|
|
|
public ObservableCollection<ScriptCategory> ScriptCategories => _scriptCategories;
|
|
public ObservableCollection<ExecutionHistoryItem> History => _history;
|
|
public ObservableCollection<ResultTabViewModel> ResultTabs => _resultTabs;
|
|
|
|
public ScriptMetadata SelectedScript
|
|
{
|
|
get => _selectedScript;
|
|
set
|
|
{
|
|
if (SetProperty(ref _selectedScript, value))
|
|
{
|
|
OnSelectedScriptChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsBusy
|
|
{
|
|
get => _isBusy;
|
|
set => SetProperty(ref _isBusy, value);
|
|
}
|
|
|
|
public string StatusMessage
|
|
{
|
|
get => _statusMessage;
|
|
set => SetProperty(ref _statusMessage, value);
|
|
}
|
|
|
|
public ResultTabViewModel SelectedResultTab
|
|
{
|
|
get => _selectedResultTab;
|
|
set => SetProperty(ref _selectedResultTab, value);
|
|
}
|
|
|
|
public string SearchText
|
|
{
|
|
get => _searchText;
|
|
set
|
|
{
|
|
if (SetProperty(ref _searchText, value))
|
|
{
|
|
FilterScripts();
|
|
}
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<ParameterViewModel> Parameters { get; } = new();
|
|
|
|
public IAsyncRelayCommand LoadScriptsCommand { get; }
|
|
public IAsyncRelayCommand ExecuteCommand { get; }
|
|
public IAsyncRelayCommand ExportCommand { get; }
|
|
public IRelayCommand CopySqlCommand { get; }
|
|
public IRelayCommand ClearResultsCommand { get; }
|
|
public IRelayCommand SaveParametersCommand { get; }
|
|
public IRelayCommand LoadParametersCommand { get; }
|
|
|
|
public MainViewModel(
|
|
IScriptManager scriptManager,
|
|
ISqlExecutionService executionService,
|
|
IExportService exportService,
|
|
IControlFactory controlFactory,
|
|
IVisualizerFactory visualizerFactory,
|
|
ILogger<MainViewModel> logger)
|
|
{
|
|
_scriptManager = scriptManager;
|
|
_executionService = executionService;
|
|
_exportService = exportService;
|
|
_controlFactory = controlFactory;
|
|
_visualizerFactory = visualizerFactory;
|
|
_logger = logger;
|
|
|
|
LoadScriptsCommand = new AsyncRelayCommand(LoadScriptsAsync);
|
|
ExecuteCommand = new AsyncRelayCommand(ExecuteScriptAsync, CanExecuteScript);
|
|
ExportCommand = new AsyncRelayCommand(ExportResultsAsync, CanExportResults);
|
|
CopySqlCommand = new RelayCommand(CopySqlToClipboard);
|
|
ClearResultsCommand = new RelayCommand(ClearResults);
|
|
SaveParametersCommand = new RelayCommand(SaveParameters);
|
|
LoadParametersCommand = new RelayCommand(LoadParameters);
|
|
|
|
// Подписка на события
|
|
_scriptManager.ScriptChanged += OnScriptChanged;
|
|
_scriptManager.ScriptsReloaded += OnScriptsReloaded;
|
|
}
|
|
|
|
private async Task LoadScriptsAsync()
|
|
{
|
|
try
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "Загрузка скриптов...";
|
|
|
|
var scripts = await _scriptManager.LoadScriptsAsync();
|
|
_scripts.Clear();
|
|
|
|
foreach (var script in scripts)
|
|
{
|
|
_scripts.Add(script);
|
|
}
|
|
|
|
CategorizeScripts();
|
|
StatusMessage = $"Загружено {_scripts.Count} скриптов";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error loading scripts");
|
|
StatusMessage = $"Ошибка загрузки: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
private void CategorizeScripts()
|
|
{
|
|
_scriptCategories.Clear();
|
|
|
|
var categories = _scripts
|
|
.GroupBy(s => s.Category ?? "Без категории")
|
|
.OrderBy(g => g.Key);
|
|
|
|
foreach (var category in categories)
|
|
{
|
|
var scriptCategory = new ScriptCategory
|
|
{
|
|
Name = category.Key,
|
|
Scripts = new ObservableCollection<ScriptMetadata>(category.OrderBy(s => s.FileName))
|
|
};
|
|
|
|
_scriptCategories.Add(scriptCategory);
|
|
}
|
|
}
|
|
|
|
private void FilterScripts()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(SearchText))
|
|
{
|
|
// Показать все скрипты
|
|
foreach (var category in _scriptCategories)
|
|
{
|
|
foreach (var script in category.Scripts)
|
|
{
|
|
script.IsVisible = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var searchLower = SearchText.ToLower();
|
|
|
|
foreach (var category in _scriptCategories)
|
|
{
|
|
foreach (var script in category.Scripts)
|
|
{
|
|
script.IsVisible =
|
|
script.FileName.ToLower().Contains(searchLower) ||
|
|
script.Description?.ToLower().Contains(searchLower) == true ||
|
|
script.Tags.Any(t => t.ToLower().Contains(searchLower));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSelectedScriptChanged()
|
|
{
|
|
Parameters.Clear();
|
|
|
|
if (_selectedScript == null) return;
|
|
|
|
// Создание ViewModel для каждого параметра
|
|
foreach (var param in _selectedScript.Parameters.OrderBy(p => p.Order))
|
|
{
|
|
var paramVm = new ParameterViewModel(param, _controlFactory);
|
|
paramVm.ValueChanged += OnParameterValueChanged;
|
|
Parameters.Add(paramVm);
|
|
}
|
|
|
|
// Восстановление сохраненных значений
|
|
LoadSavedParameters();
|
|
}
|
|
|
|
private void OnParameterValueChanged(object sender, EventArgs e)
|
|
{
|
|
ExecuteCommand.NotifyCanExecuteChanged();
|
|
|
|
// Обновление зависимых параметров
|
|
if (sender is ParameterViewModel changedParam)
|
|
{
|
|
UpdateDependentParameters(changedParam);
|
|
}
|
|
}
|
|
|
|
private void UpdateDependentParameters(ParameterViewModel changedParam)
|
|
{
|
|
foreach (var paramVm in Parameters)
|
|
{
|
|
if (paramVm.Parameter.DependsOn == changedParam.Parameter.Name)
|
|
{
|
|
paramVm.UpdateDependencies(GetCurrentParameterValues());
|
|
}
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, object> GetCurrentParameterValues()
|
|
{
|
|
return Parameters.ToDictionary(
|
|
p => p.Parameter.Name,
|
|
p => p.Value ?? p.Parameter.DefaultValue);
|
|
}
|
|
|
|
private bool CanExecuteScript()
|
|
{
|
|
if (_selectedScript == null) return false;
|
|
|
|
// Проверка обязательных параметров
|
|
foreach (var paramVm in Parameters)
|
|
{
|
|
if (paramVm.Parameter.IsRequired &&
|
|
(paramVm.Value == null || string.IsNullOrWhiteSpace(paramVm.Value.ToString())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private async Task ExecuteScriptAsync()
|
|
{
|
|
try
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "Выполнение скрипта...";
|
|
|
|
var parameters = GetCurrentParameterValues();
|
|
|
|
var result = await _executionService.ExecuteAsync(_selectedScript, parameters);
|
|
|
|
// Добавление в историю
|
|
var historyItem = new ExecutionHistoryItem
|
|
{
|
|
ScriptId = _selectedScript.Id,
|
|
ScriptName = _selectedScript.FileName,
|
|
ExecutionTime = DateTime.Now,
|
|
Duration = result.ExecutionTime,
|
|
Success = result.IsSuccess,
|
|
Parameters = new Dictionary<string, object>(parameters),
|
|
RowCount = result.RowCount,
|
|
ErrorMessage = result.ErrorMessage,
|
|
ExecutedSql = result.ExecutedSql
|
|
};
|
|
|
|
_history.Insert(0, historyItem);
|
|
|
|
// Очистка старых записей истории
|
|
while (_history.Count > 1000)
|
|
{
|
|
_history.RemoveAt(_history.Count - 1);
|
|
}
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
CreateResultTabs(result);
|
|
StatusMessage = $"Выполнено за {result.ExecutionTime.TotalSeconds:F2} сек. Получено строк: {result.RowCount}";
|
|
}
|
|
else
|
|
{
|
|
StatusMessage = $"Ошибка: {result.ErrorMessage}";
|
|
ShowErrorDialog(result.ErrorMessage);
|
|
}
|
|
|
|
// Сохранение параметров
|
|
SaveParameters();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error executing script");
|
|
StatusMessage = $"Ошибка: {ex.Message}";
|
|
ShowErrorDialog(ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
private void CreateResultTabs(ExecutionResult result)
|
|
{
|
|
// Удаляем старые вкладки
|
|
ResultTabs.Clear();
|
|
|
|
for (int i = 0; i < result.Data.Tables.Count; i++)
|
|
{
|
|
var table = result.Data.Tables[i];
|
|
var outputDef = i < _selectedScript.Outputs.Count
|
|
? _selectedScript.Outputs[i]
|
|
: CreateDefaultOutputDefinition(table, i);
|
|
|
|
var visualizer = _visualizerFactory.GetVisualizer(outputDef.Type);
|
|
var content = visualizer.Visualize(table, outputDef);
|
|
|
|
var tabVm = new ResultTabViewModel
|
|
{
|
|
Title = outputDef.Description,
|
|
Content = content,
|
|
DataTable = table,
|
|
OutputDefinition = outputDef,
|
|
CanExport = true,
|
|
CanCopy = true
|
|
};
|
|
|
|
ResultTabs.Add(tabVm);
|
|
}
|
|
|
|
if (ResultTabs.Any())
|
|
{
|
|
SelectedResultTab = ResultTabs[0];
|
|
}
|
|
}
|
|
|
|
private OutputDefinition CreateDefaultOutputDefinition(DataTable table, int index)
|
|
{
|
|
return new OutputDefinition
|
|
{
|
|
Type = OutputType.Table,
|
|
Description = $"Результат {index + 1}",
|
|
DataTableName = table.TableName
|
|
};
|
|
}
|
|
|
|
private bool CanExportResults()
|
|
{
|
|
return SelectedResultTab != null &&
|
|
SelectedResultTab.DataTable != null &&
|
|
SelectedResultTab.DataTable.Rows.Count > 0;
|
|
}
|
|
|
|
private async Task ExportResultsAsync()
|
|
{
|
|
if (SelectedResultTab?.DataTable == null) return;
|
|
|
|
try
|
|
{
|
|
var savePicker = new FileSavePicker();
|
|
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
|
|
savePicker.FileTypeChoices.Add("Excel файл", new List<string> { ".xlsx" });
|
|
savePicker.FileTypeChoices.Add("CSV файл", new List<string> { ".csv" });
|
|
savePicker.FileTypeChoices.Add("JSON файл", new List<string> { ".json" });
|
|
savePicker.SuggestedFileName = $"{_selectedScript.FileName}_{DateTime.Now:yyyyMMdd_HHmmss}";
|
|
|
|
var file = await savePicker.PickSaveFileAsync();
|
|
if (file != null)
|
|
{
|
|
var options = new ExportOptions
|
|
{
|
|
Format = Path.GetExtension(file.Path).TrimStart('.').ToUpper(),
|
|
IncludeHeaders = true,
|
|
AutoFilter = true
|
|
};
|
|
|
|
await _exportService.ExportAsync(SelectedResultTab.DataTable, file.Path, options);
|
|
|
|
StatusMessage = $"Экспортировано в {file.Path}";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error exporting results");
|
|
StatusMessage = $"Ошибка экспорта: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void CopySqlToClipboard()
|
|
{
|
|
if (_selectedScript == null) return;
|
|
|
|
var parameters = GetCurrentParameterValues();
|
|
var sql = FormatSqlWithParameters(_selectedScript.ProcessedSql, parameters);
|
|
|
|
var package = new DataPackage();
|
|
package.SetText(sql);
|
|
Clipboard.SetContent(package);
|
|
|
|
StatusMessage = "SQL скопирован в буфер обмена";
|
|
}
|
|
|
|
private string FormatSqlWithParameters(string sql, Dictionary<string, object> parameters)
|
|
{
|
|
var result = new StringBuilder(sql);
|
|
|
|
foreach (var param in parameters)
|
|
{
|
|
var paramName = $"@{param.Key}";
|
|
var paramValue = FormatParameterForDisplay(param.Value);
|
|
|
|
result = result.Replace(paramName, paramValue);
|
|
}
|
|
|
|
return result.ToString();
|
|
}
|
|
|
|
private string FormatParameterForDisplay(object value)
|
|
{
|
|
if (value == null) return "NULL";
|
|
|
|
return value switch
|
|
{
|
|
string str => $"N'{str.Replace("'", "''")}'",
|
|
DateTime dt => $"'{dt:yyyy-MM-dd HH:mm:ss}'",
|
|
bool b => b ? "1" : "0",
|
|
_ => value.ToString()
|
|
};
|
|
}
|
|
|
|
private void ClearResults()
|
|
{
|
|
ResultTabs.Clear();
|
|
StatusMessage = "Результаты очищены";
|
|
}
|
|
|
|
private void SaveParameters()
|
|
{
|
|
if (_selectedScript == null) return;
|
|
|
|
var parameters = GetCurrentParameterValues();
|
|
var settings = ApplicationData.Current.LocalSettings;
|
|
|
|
var dict = new Dictionary<string, object>();
|
|
foreach (var param in parameters)
|
|
{
|
|
dict[param.Key] = param.Value;
|
|
}
|
|
|
|
var json = JsonSerializer.Serialize(dict);
|
|
settings.Values[$"ScriptParams_{_selectedScript.Id}"] = json;
|
|
|
|
StatusMessage = "Параметры сохранены";
|
|
}
|
|
|
|
private void LoadParameters()
|
|
{
|
|
if (_selectedScript == null) return;
|
|
|
|
var settings = ApplicationData.Current.LocalSettings;
|
|
if (settings.Values.TryGetValue($"ScriptParams_{_selectedScript.Id}", out var jsonObj))
|
|
{
|
|
try
|
|
{
|
|
var savedParams = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonObj.ToString());
|
|
|
|
foreach (var paramVm in Parameters)
|
|
{
|
|
if (savedParams.TryGetValue(paramVm.Parameter.Name, out var value))
|
|
{
|
|
paramVm.Value = value;
|
|
}
|
|
}
|
|
|
|
StatusMessage = "Параметры загружены";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Error loading saved parameters");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ShowErrorDialog(string message)
|
|
{
|
|
// Реализация диалога ошибки
|
|
// Можно использовать ContentDialog или другое UI решение
|
|
}
|
|
|
|
private void OnScriptChanged(object sender, ScriptChangedEventArgs e)
|
|
{
|
|
// Обновление UI при изменении скрипта
|
|
DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
|
|
{
|
|
CategorizeScripts();
|
|
|
|
if (e.ChangeType == ScriptChangeType.Deleted &&
|
|
_selectedScript?.FullPath == e.FilePath)
|
|
{
|
|
SelectedScript = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void OnScriptsReloaded(object sender, ScriptsReloadedEventArgs e)
|
|
{
|
|
DispatcherQueue.GetForCurrentThread().TryEnqueue(CategorizeScripts);
|
|
}
|
|
}
|