QueryWindow: Allow users to export query result to csv file
All checks were successful
Test / Run tests (push) Successful in 1m47s

This commit is contained in:
2025-12-16 16:50:15 +01:00
parent 9af498287d
commit 5cec5b3556
4 changed files with 98 additions and 27 deletions

View File

@@ -15,7 +15,9 @@ namespace Elwig.Helpers.Export {
protected readonly char Separator; protected readonly char Separator;
protected string? Header; protected string? Header;
public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { } public Csv(string filename, char separator = ';') :
this(filename, separator, Utils.UTF8) {
}
public Csv(string filename, char separator, Encoding encoding) { public Csv(string filename, char separator, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding); _writer = new StreamWriter(filename, false, encoding);
@@ -58,4 +60,22 @@ namespace Elwig.Helpers.Export {
public abstract string FormatRow(T row); public abstract string FormatRow(T row);
} }
public class CsvSimple : Csv<IEnumerable<object?>> {
public CsvSimple(string filename, char separator, Encoding encoding) :
base(filename, separator, encoding) {
}
public CsvSimple(string filename, char separator = ';') :
base(filename, separator) {
}
public override string FormatRow(IEnumerable<object?> row) {
return string.Join(Separator, row.Select(i => {
var str = $"{i}";
return str.Contains(Separator) || str.Contains('\n') ? $"\"{str.Replace("\"", "\"\"")}\"" : str;
}));
}
}
} }

View File

@@ -36,6 +36,7 @@ namespace Elwig.Helpers {
public static partial class Utils { public static partial class Utils {
public static readonly Encoding UTF8 = new UTF8Encoding(false, true); public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
public static readonly Encoding UTF8BOM = new UTF8Encoding(true, true);
public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static int CurrentYear => DateTime.Now.Year; public static int CurrentYear => DateTime.Now.Year;

View File

@@ -5,27 +5,35 @@
Title="Datenbankabfragen - Elwig" Height="450" Width="800" MinWidth="400" MinHeight="300"> Title="Datenbankabfragen - Elwig" Height="450" Width="800" MinWidth="400" MinHeight="300">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="5"/> <RowDefinition Height="5"/>
<RowDefinition Height="3*" MinHeight="100"/> <RowDefinition Height="1*" MinHeight="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="1*" MinHeight="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="6*" MinHeight="100"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBox x:Name="QueryInput" Text="SELECT * FROM v_delivery" <TextBox x:Name="QueryInput" Text="SELECT * FROM v_member" Grid.Row="1" Grid.RowSpan="3"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,120,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,120,5"
FontFamily="Cascadia Code Light" FontSize="13"> FontFamily="Cascadia Code Light" FontSize="13">
<TextBox.InputBindings> <TextBox.InputBindings>
<KeyBinding Key="Return" Modifiers="Control" Command="{Binding EnterCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" /> <KeyBinding Key="Return" Modifiers="Control" Command="{Binding EnterCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" />
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" />
</TextBox.InputBindings> </TextBox.InputBindings>
</TextBox> </TextBox>
<Button x:Name="QueryButton" Content="Abfragen" <Button x:Name="QueryButton" Content="Abfragen" Grid.Row="1"
HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,10,10,5" HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,5,10,0"
Click="QueryButton_Click" Width="100" Click="QueryButton_Click" Width="100"
FontSize="14"/> FontSize="14"/>
<Button x:Name="SaveButton" Content="Speichern" Grid.Row="3"
HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,0,10,5"
Click="SaveButton_Click" Width="100"
FontSize="14"/>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <GridSplitter Grid.Row="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<DataGrid x:Name="DataList" Grid.Row="2" <DataGrid x:Name="DataList" Grid.Row="5"
AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Extended" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Extended"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,10,10"/> HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,10,10"/>

View File

@@ -1,7 +1,10 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -13,7 +16,12 @@ namespace Elwig.Windows {
private ICommand? _enterCommand; private ICommand? _enterCommand;
public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => { public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => {
await ExecuteQuery(); await DisplayQuery();
});
private ICommand? _saveCommand;
public ICommand SaveCommand => _saveCommand ??= new ActionCommand(async () => {
await SaveQuery();
}); });
@@ -22,33 +30,45 @@ namespace Elwig.Windows {
} }
private async void QueryButton_Click(object sender, RoutedEventArgs evt) { private async void QueryButton_Click(object sender, RoutedEventArgs evt) {
await ExecuteQuery(); await DisplayQuery();
} }
private async Task ExecuteQuery() { private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
await SaveQuery();
}
private async Task DisplayQuery() {
try { try {
await ExecuteQuery(QueryInput.Text); Mouse.OverrideCursor = Cursors.Wait;
await DisplayQuery(QueryInput.Text);
Mouse.OverrideCursor = null;
} catch (Exception e) { } catch (Exception e) {
Mouse.OverrideCursor = null;
MessageBox.Show(e.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(e.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
private async Task ExecuteQuery(string sqlQuery) { private async Task SaveQuery() {
await SaveQuery(QueryInput.Text);
}
private static async Task<(IList<DbColumn>, IEnumerable<object[]>)> ExecuteQuery(string sqlQuery) {
var rows = new List<object[]>(); var rows = new List<object[]>();
IList<DbColumn> header; using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand();
using (var cnx = await AppDbContext.ConnectAsync()) { cmd.CommandText = sqlQuery;
using var cmd = cnx.CreateCommand(); using var reader = await cmd.ExecuteReaderAsync();
cmd.CommandText = sqlQuery; var header = await reader.GetColumnSchemaAsync();
using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) {
header = await reader.GetColumnSchemaAsync(); var values = new object[reader.FieldCount];
while (await reader.ReadAsync()) { reader.GetValues(values);
var values = new object[reader.FieldCount]; rows.Add(values);
reader.GetValues(values);
rows.Add(values);
}
} }
return (header, rows);
}
private async Task DisplayQuery(string sqlQuery) {
var (header, rows) = await ExecuteQuery(sqlQuery);
var styleRight = new Style(); var styleRight = new Style();
styleRight.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right)); styleRight.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right));
@@ -63,5 +83,27 @@ namespace Elwig.Windows {
} }
DataList.ItemsSource = rows; DataList.ItemsSource = rows;
} }
private static async Task SaveQuery(string sqlQuery) {
var d = new SaveFileDialog() {
FileName = $"Abfrage.csv",
DefaultExt = "csv",
Filter = "CSV-Datei (*.csv)|*.csv",
Title = $"Datenbank Abfrage speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var (header, rows) = await ExecuteQuery(sqlQuery);
using var csv = new CsvSimple(d.FileName, ';', Utils.UTF8BOM);
await csv.ExportAsync(rows.Prepend([.. header.Select(h => h.ColumnName)]));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
} }
} }