From 49ee5472f1f024b0a8b73bdedf4edafb92b592f1 Mon Sep 17 00:00:00 2001 From: Mateusz Noga-Wojtania Date: Wed, 14 Jan 2026 10:41:08 +0100 Subject: [PATCH 1/4] fix unloading --- .../CHANGELOG.md | 4 +++ .../Frends.MicrosoftSQL.ExecuteQueryToFile.cs | 28 ++++++++++++++++--- ...nds.MicrosoftSQL.ExecuteQueryToFile.csproj | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md b/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md index 528ac6f..a74161e 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [2.2.0] - 2026-01-14 +### Fixed +- Fix unloading assembly + ## [2.1.0] - 2024-12-16 ### Added - Added Microsoft.SqlServer.Types dependency so that SqlGeography and SqlGeometry typed objects can be handled. diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs index 1e5d353..0033dd4 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs @@ -1,19 +1,32 @@ -namespace Frends.MicrosoftSQL.ExecuteQueryToFile; - -using System; +using System; using System.ComponentModel; using System.Data; +using System.Reflection; +using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; using Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; using Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; using Microsoft.Data.SqlClient; +namespace Frends.MicrosoftSQL.ExecuteQueryToFile; + /// /// Main class of the Task. /// public static class MicrosoftSQL { + static MicrosoftSQL() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + var currentContext = AssemblyLoadContext.GetLoadContext(currentAssembly); + + if (currentContext != null) + { + currentContext.Unloading += OnPluginUnloadingRequested; + } + } + /// /// Frends Task for executing Microsoft SQL queries into a file. /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.MicrosoftSQL.ExecuteQueryToFile). @@ -25,6 +38,7 @@ public static class MicrosoftSQL public static async Task ExecuteQueryToFile([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) { Result result = new(); + using (var sqlConnection = new SqlConnection(input.ConnectionString)) { await sqlConnection.OpenAsync(cancellationToken); @@ -59,10 +73,16 @@ public static async Task ExecuteQueryToFile([PropertyTab] Input input, [ case ReturnFormat.CSV: var csvWriter = new CsvFileWriter(command, input, options.CsvOptions); result = await csvWriter.SaveQueryToCSV(cancellationToken); + break; } } return result; } -} + + private static void OnPluginUnloadingRequested(AssemblyLoadContext obj) + { + obj.Unloading -= OnPluginUnloadingRequested; + } +} \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj index ec6c0bf..0bd3cc0 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.csproj @@ -3,7 +3,7 @@ net6.0 Latest - 2.1.0 + 2.2.0 Frends Frends Frends From 453c4222848602838b5777a424112da1677c1d38 Mon Sep 17 00:00:00 2001 From: Mateusz Noga-Wojtania Date: Wed, 14 Jan 2026 10:47:21 +0100 Subject: [PATCH 2/4] fix linter --- .../Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs | 1 - .../Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs index cdc2840..a08ae90 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs @@ -13,5 +13,4 @@ public enum FileEncoding Unicode, Other, } - #pragma warning restore CS1591 // Self-explanatory \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs index f37af8c..a27c447 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs @@ -9,5 +9,4 @@ public enum ReturnFormat { CSV, } - #pragma warning restore CS1591 // Self-explanatory \ No newline at end of file From e4842c217c9e841f810ad10088785dc9c906515b Mon Sep 17 00:00:00 2001 From: Mateusz Noga-Wojtania Date: Wed, 14 Jan 2026 10:59:19 +0100 Subject: [PATCH 3/4] linter --- .../Enums/FileEncoding.cs | 31 ++++++++++--------- .../Enums/ReturnFormat.cs | 23 +++++++------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs index a08ae90..f0fa0a8 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/FileEncoding.cs @@ -1,16 +1,17 @@ -namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; - -#pragma warning disable CS1591 // Self-explanatory - -/// -/// File encoding used to encode the file. -/// -public enum FileEncoding -{ - UTF8, - ANSI, - ASCII, - Unicode, - Other, -} +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// File encoding used to encode the file. +/// +public enum FileEncoding +{ + UTF8, + ANSI, + ASCII, + Unicode, + Other, +} + #pragma warning restore CS1591 // Self-explanatory \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs index a27c447..4c47233 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Enums/ReturnFormat.cs @@ -1,12 +1,13 @@ -namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; - -#pragma warning disable CS1591 // Self-explanatory - -/// -/// Enumeration for output format. -/// -public enum ReturnFormat -{ - CSV, -} +namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Enums; + +#pragma warning disable CS1591 // Self-explanatory + +/// +/// Enumeration for output format. +/// +public enum ReturnFormat +{ + CSV, +} + #pragma warning restore CS1591 // Self-explanatory \ No newline at end of file From dc0f18327b7edc3ac71f2612ca09895e87c80ae1 Mon Sep 17 00:00:00 2001 From: Mateusz Noga-Wojtania Date: Thu, 22 Jan 2026 12:06:01 +0100 Subject: [PATCH 4/4] actual fix --- .../Definitions/CsvFileWriter.cs | 51 +++++++----- .../Frends.MicrosoftSQL.ExecuteQueryToFile.cs | 80 ++++++++----------- 2 files changed, 61 insertions(+), 70 deletions(-) diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs index 7015525..b8e9c85 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Definitions/CsvFileWriter.cs @@ -15,7 +15,7 @@ namespace Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; -internal class CsvFileWriter +internal class CsvFileWriter : IAsyncDisposable { internal CsvFileWriter(SqlCommand sqlCommand, Input input, CsvOptions options) { @@ -30,31 +30,27 @@ internal CsvFileWriter(SqlCommand sqlCommand, Input input, CsvOptions options) private CsvOptions Options { get; set; } - public async Task SaveQueryToCSV(CancellationToken cancellationToken) + public async ValueTask DisposeAsync() { - var output = 0; - var encoding = GetEncoding(Options.FileEncoding, Options.EnableBom, Options.EncodingInString); - - using (var writer = new StreamWriter(Input.OutputFilePath, false, encoding)) - using (var csvFile = CreateCsvWriter(Options.GetFieldDelimiterAsString(), writer)) - { - writer.NewLine = Options.GetLineBreakAsString(); - - var reader = await SqlCommand.ExecuteReaderAsync(cancellationToken); - output = DataReaderToCsv(reader, csvFile, Options, cancellationToken); + if (SqlCommand != null) await SqlCommand.DisposeAsync(); + } - csvFile.Flush(); - } + public async Task SaveQueryToCsv(CancellationToken cancellationToken) + { + var encoding = GetEncoding(Options.FileEncoding, Options.EnableBom, Options.EncodingInString); + await using var writer = new StreamWriter(Input.OutputFilePath, false, encoding); + await using var csvFile = CreateCsvWriter(Options.GetFieldDelimiterAsString(), writer); + writer.NewLine = Options.GetLineBreakAsString(); + var reader = await SqlCommand.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + var output = DataReaderToCsv(reader, csvFile, Options, cancellationToken); + await csvFile.FlushAsync().ConfigureAwait(false); return new Result(output, Input.OutputFilePath, Path.GetFileName(Input.OutputFilePath)); } private static CsvWriter CreateCsvWriter(string delimiter, TextWriter writer) { - var csvOptions = new CsvConfiguration(CultureInfo.InvariantCulture) - { - Delimiter = delimiter, - }; + var csvOptions = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = delimiter, }; return new CsvWriter(writer, csvOptions); } @@ -63,10 +59,11 @@ private static string FormatDbHeader(string header, bool forceSpecialFormatting) { if (!forceSpecialFormatting) return header; - // First part of regex removes all non-alphanumeric ('_' also allowed) chars from the whole string. - // Second part removed any leading numbers or underscoress. + // The first part of regex removes all non-alphanumeric ('_' also allowed) chars from the whole string. + // The second part removed any leading numbers or underscores. var rgx = new Regex("[^a-zA-Z0-9_-]|^[0-9_]+"); header = rgx.Replace(header, string.Empty); + return header.ToLower(); } @@ -76,6 +73,7 @@ private static string FormatDbValue(object value, string dbTypeName, Type dotnet { if (dotnetType == typeof(string)) return "\"\""; if (dotnetType == typeof(DateTime) && options.AddQuotesToDates) return "\"\""; + return string.Empty; } @@ -87,8 +85,10 @@ private static string FormatDbValue(object value, string dbTypeName, Type dotnet str = str.Replace("\r\n", " "); str = str.Replace("\r", " "); str = str.Replace("\n", " "); + if (options.AddQuotesToStrings) return $"\"{str}\""; + return str; } @@ -101,7 +101,9 @@ private static string FormatDbValue(object value, string dbTypeName, Type dotnet "date" => dateTime.ToString(options.DateFormat, CultureInfo.InvariantCulture), _ => dateTime.ToString(options.DateTimeFormat, CultureInfo.InvariantCulture), }; + if (options.AddQuotesToDates) return $"\"{output}\""; + return output; } @@ -126,8 +128,9 @@ private static int DataReaderToCsv( CsvOptions options, CancellationToken cancellationToken) { - // Write header and remember column indexes to include. + // Write a header and remember column indexes to include it. var columnIndexesToInclude = new List(); + for (var i = 0; i < reader.FieldCount; i++) { var columnName = reader.GetName(i); @@ -153,6 +156,7 @@ private static int DataReaderToCsv( if (options.IncludeHeadersInOutput) csvWriter.NextRecord(); int count = 0; + while (reader.Read()) { foreach (var columnIndex in columnIndexesToInclude) @@ -172,7 +176,10 @@ private static int DataReaderToCsv( return count; } - private static Encoding GetEncoding(FileEncoding optionsFileEncoding, bool optionsEnableBom, string optionsEncodingInString) + private static Encoding GetEncoding( + FileEncoding optionsFileEncoding, + bool optionsEnableBom, + string optionsEncodingInString) { return optionsFileEncoding switch { diff --git a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs index 0033dd4..2c51983 100644 --- a/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs +++ b/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile/Frends.MicrosoftSQL.ExecuteQueryToFile.cs @@ -1,8 +1,6 @@ using System; using System.ComponentModel; using System.Data; -using System.Reflection; -using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; using Frends.MicrosoftSQL.ExecuteQueryToFile.Definitions; @@ -16,17 +14,6 @@ namespace Frends.MicrosoftSQL.ExecuteQueryToFile; /// public static class MicrosoftSQL { - static MicrosoftSQL() - { - var currentAssembly = Assembly.GetExecutingAssembly(); - var currentContext = AssemblyLoadContext.GetLoadContext(currentAssembly); - - if (currentContext != null) - { - currentContext.Unloading += OnPluginUnloadingRequested; - } - } - /// /// Frends Task for executing Microsoft SQL queries into a file. /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.MicrosoftSQL.ExecuteQueryToFile). @@ -35,54 +22,51 @@ static MicrosoftSQL() /// Options parameters. /// Cancellation token given by Frends. /// Object { int EntriesWritten, string Path, string FileName } - public static async Task ExecuteQueryToFile([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) + public static async Task ExecuteQueryToFile( + [PropertyTab] Input input, + [PropertyTab] Options options, + CancellationToken cancellationToken) { Result result = new(); - using (var sqlConnection = new SqlConnection(input.ConnectionString)) - { - await sqlConnection.OpenAsync(cancellationToken); + await using var sqlConnection = new SqlConnection(input.ConnectionString); + await sqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); - using var command = sqlConnection.CreateCommand(); - command.CommandTimeout = options.TimeoutSeconds; - command.CommandText = input.Query; - command.CommandType = CommandType.Text; + await using var command = sqlConnection.CreateCommand(); + command.CommandTimeout = options.TimeoutSeconds; + command.CommandText = input.Query; + command.CommandType = CommandType.Text; - if (input.QueryParameters != null) + if (input.QueryParameters != null) + { + foreach (var parameter in input.QueryParameters) { - foreach (var parameter in input.QueryParameters) - { - if (parameter.Value is null) - parameter.Value = DBNull.Value; + parameter.Value ??= DBNull.Value; - if (parameter.SqlDataType is SqlDataTypes.Auto) - { - command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value); - } - else - { - var sqlDbType = (SqlDbType)Enum.Parse(typeof(SqlDbType), parameter.SqlDataType.ToString()); - var commandParameter = command.Parameters.Add(parameter.Name, sqlDbType); - commandParameter.Value = parameter.Value; - } + if (parameter.SqlDataType is SqlDataTypes.Auto) + { + command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value); + } + else + { + var sqlDbType = (SqlDbType)Enum.Parse(typeof(SqlDbType), parameter.SqlDataType.ToString()); + var commandParameter = command.Parameters.Add(parameter.Name, sqlDbType); + commandParameter.Value = parameter.Value; } } + } - switch (options.ReturnFormat) - { - case ReturnFormat.CSV: - var csvWriter = new CsvFileWriter(command, input, options.CsvOptions); - result = await csvWriter.SaveQueryToCSV(cancellationToken); + switch (options.ReturnFormat) + { + case ReturnFormat.CSV: + { + await using var csvWriter = new CsvFileWriter(command, input, options.CsvOptions); + result = await csvWriter.SaveQueryToCsv(cancellationToken).ConfigureAwait(false); break; - } + } } return result; } - - private static void OnPluginUnloadingRequested(AssemblyLoadContext obj) - { - obj.Unloading -= OnPluginUnloadingRequested; - } -} \ No newline at end of file +}