diff --git a/Frends.MicrosoftSQL.ExecuteQuery/CHANGELOG.md b/Frends.MicrosoftSQL.ExecuteQuery/CHANGELOG.md index 9aefddd..609650c 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/CHANGELOG.md +++ b/Frends.MicrosoftSQL.ExecuteQuery/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.2.0] - 2024-11-26 +### Added +- Added method to form JToken from the SqlDataReader so that SqlGeography and SqlGeometry typed objects can be handled. +- Fixed how Scalar handles the data so that SqlGeography and SqlGeometry typed objects can be handled. +- Added Microsoft.SqlServer.Types version 160.1000.6 as dependency. + ## [2.1.0] - 2024-09-10 ### Fixed - Fixed how null values are handled by setting them as DBNull.Value. diff --git a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/AutoUnitTests.cs b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/AutoUnitTests.cs index ce62158..4f91213 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/AutoUnitTests.cs +++ b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/AutoUnitTests.cs @@ -132,4 +132,63 @@ public async Task TestExecuteQuery_Auto() CleanUp(); } } + + [TestMethod] + public async Task TestWithGeographyData() + { + var table = "geographytest"; + + var options = new Options() + { + SqlTransactionIsolationLevel = SqlTransactionIsolationLevel.None, + CommandTimeoutSeconds = 2, + ThrowErrorOnFailure = true + }; + + var input = new Input + { + ConnectionString = _connString, + Query = $"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{table}') BEGIN CREATE TABLE {table} ( Id int IDENTITY(1, 1), GeogCol1 geography, GeogCol2 AS GeogCol1.STAsText()); END", + ExecuteType = ExecuteTypes.Auto, + Parameters = null + }; + + try + { + var create = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(create.Success, "Create table"); + + input.Query = $"INSERT INTO {table} (GeogCol1) VALUES (geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656 )', 4326));"; + + var insert1 = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(insert1.Success, "First insert"); + + input.Query = $"INSERT INTO {table} (GeogCol1) VALUES(geography::STGeomFromText('POLYGON((-122.358 47.653 , -122.348 47.649, -122.348 47.658, -122.358 47.658, -122.358 47.653))', 4326));"; + + var insert2 = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(insert2.Success, "Second insert"); + + input.Query = $"SELECT * From {table}"; + + var select = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(select.Success, "Select"); + Assert.AreEqual(typeof(JArray), select.Data.GetType()); + Assert.AreEqual(2, select.Data.Count); + + // Verify first row (LINESTRING) + Assert.IsNotNull(select.Data[0]["GeogCol1"]); + Assert.IsTrue(select.Data[0]["GeogCol2"].ToString().StartsWith("LINESTRING")); + + // Verify second row (POLYGON) + Assert.IsNotNull(select.Data[1]["GeogCol1"]); + Assert.IsTrue(select.Data[1]["GeogCol2"].ToString().StartsWith("POLYGON")); + } + finally + { + input.Query = $"DROP TABLE {table}"; + + var drop = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(drop.Success, "Drop"); + } + } } \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/Lib/ExecuteQueryTestBase.cs b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/Lib/ExecuteQueryTestBase.cs index d11b8bc..bf83618 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/Lib/ExecuteQueryTestBase.cs +++ b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/Lib/ExecuteQueryTestBase.cs @@ -14,7 +14,7 @@ public void Init() using var connection = new SqlConnection(_connString); connection.Open(); var createTable = connection.CreateCommand(); - createTable.CommandText = $@"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{_tableName}') BEGIN CREATE TABLE {_tableName} ( Id int, LastName varchar(255), FirstName varchar(255) ); END"; + createTable.CommandText = $@"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{_tableName}') BEGIN CREATE TABLE {_tableName} ( Id int, LastName varchar(255), FirstName varchar(255)); END"; createTable.ExecuteNonQuery(); connection.Close(); connection.Dispose(); diff --git a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/ScalarUnitTests.cs b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/ScalarUnitTests.cs index 20f3be4..aa57444 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/ScalarUnitTests.cs +++ b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.Tests/ScalarUnitTests.cs @@ -1,6 +1,7 @@ using Frends.MicrosoftSQL.ExecuteQuery.Definitions; using Frends.MicrosoftSQL.ExecuteQuery.Tests.Lib; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; namespace Frends.MicrosoftSQL.ExecuteQuery.Tests; @@ -130,4 +131,58 @@ public async Task TestExecuteQuery_Scalar() CleanUp(); } } + + [TestMethod] + public async Task TestWithGeographyData_Scalar() + { + var table = "geographytest"; + + var options = new Options() + { + SqlTransactionIsolationLevel = SqlTransactionIsolationLevel.None, + CommandTimeoutSeconds = 2, + ThrowErrorOnFailure = true + }; + + var input = new Input + { + ConnectionString = _connString, + Query = $"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{table}') BEGIN CREATE TABLE {table} ( Id int IDENTITY(1, 1), GeogCol1 geography, GeogCol2 AS GeogCol1.STAsText()); END", + ExecuteType = ExecuteTypes.Auto, + Parameters = null + }; + + try + { + var create = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(create.Success, "Create table"); + + input.Query = $"INSERT INTO {table} (GeogCol1) VALUES (geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656 )', 4326));"; + + var insert1 = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(insert1.Success, "First insert"); + + input.Query = $"INSERT INTO {table} (GeogCol1) VALUES(geography::STGeomFromText('POLYGON((-122.358 47.653 , -122.348 47.649, -122.348 47.658, -122.358 47.658, -122.358 47.653))', 4326));"; + + var insert2 = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(insert2.Success, "Second insert"); + + input.Query = $"SELECT GeogCol1 From {table}"; + input.ExecuteType = ExecuteTypes.Scalar; + + var select = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(select.Success, "Select"); + Assert.IsNotNull(select.Data["Value"], "Selected data should not be null"); + Assert.IsInstanceOfType(select.Data["Value"], typeof(JValue), "Geography data should be converted to string"); + Assert.IsTrue(((string)select.Data["Value"]).StartsWith("LINESTRING"), "First row should be a LINESTRING"); + } + finally + { + input.Query = $"DROP TABLE {table}"; + input.ExecuteType = ExecuteTypes.Auto; + + var drop = await MicrosoftSQL.ExecuteQuery(input, options, default); + Assert.IsTrue(drop.Success, "Drop"); + } + } } \ No newline at end of file diff --git a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/ExecuteQuery.cs b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/ExecuteQuery.cs index 50a9e13..b3dc7be 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/ExecuteQuery.cs +++ b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/ExecuteQuery.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using IsolationLevel = System.Data.IsolationLevel; using Microsoft.Data.SqlClient; +using Microsoft.SqlServer.Types; namespace Frends.MicrosoftSQL.ExecuteQuery; @@ -92,7 +93,6 @@ private static async Task ExecuteHandler(Input input, Options options, S Result result; object dataObject; SqlDataReader dataReader = null; - using var table = new DataTable(); try { @@ -102,8 +102,7 @@ private static async Task ExecuteHandler(Input input, Options options, S if (input.Query.ToLower().StartsWith("select")) { dataReader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); - table.Load(dataReader); - result = new Result(true, dataReader.RecordsAffected, null, JToken.FromObject(table)); + result = new Result(true, dataReader.RecordsAffected, null, await LoadData(dataReader, cancellationToken)); await dataReader.CloseAsync(); break; } @@ -116,12 +115,16 @@ private static async Task ExecuteHandler(Input input, Options options, S break; case ExecuteTypes.Scalar: dataObject = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); + + // JToken.FromObject() method can't handle SqlGeography typed objects so we convert it into string. + if (dataObject != null && (dataObject.GetType() == typeof(SqlGeography) || dataObject.GetType() == typeof(SqlGeometry))) + dataObject = dataObject.ToString(); + result = new Result(true, 1, null, JToken.FromObject(new { Value = dataObject })); break; case ExecuteTypes.ExecuteReader: dataReader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); - table.Load(dataReader); - result = new Result(true, dataReader.RecordsAffected, null, JToken.FromObject(table)); + result = new Result(true, dataReader.RecordsAffected, null, await LoadData(dataReader, cancellationToken)); await dataReader.CloseAsync(); break; default: @@ -165,6 +168,43 @@ private static async Task ExecuteHandler(Input input, Options options, S return new Result(false, 0, $"ExecuteHandler exception: (If required) transaction rollback completed without exception. {ex}.", null); } } + finally + { + if (dataReader != null && !dataReader.IsClosed) + await dataReader.CloseAsync(); + } + } + + private static async Task LoadData(SqlDataReader reader, CancellationToken cancellationToken) + { + var table = new JArray(); + while (reader.HasRows) + { + while (await reader.ReadAsync(cancellationToken)) + { + var row = new JObject(); + for (var i = 0; i < reader.FieldCount; i++) + { + object fieldValue = reader.GetValue(i); + object value; + if (fieldValue == DBNull.Value) + value = null; + else if (fieldValue is SqlGeography geography) + value = geography.ToString(); + else if (fieldValue is SqlGeometry geometry) + value = geometry.ToString(); + else + value = fieldValue; + + row.Add(new JProperty(reader.GetName(i), value)); + } + + table.Add(row); + } + await reader.NextResultAsync(cancellationToken).ConfigureAwait(false); + } + + return table; } private static IsolationLevel GetIsolationLevel(Options options) diff --git a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.csproj b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.csproj index 49b7dd9..38389f9 100644 --- a/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.csproj +++ b/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery/Frends.MicrosoftSQL.ExecuteQuery.csproj @@ -2,7 +2,7 @@ net6.0 - 2.1.0 + 2.2.0 Frends Frends Frends @@ -24,5 +24,6 @@ + \ No newline at end of file