Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MicrosoftSQL.ExecuteQuery - Fixed issue with null parameter values #57

Merged
merged 3 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Frends.MicrosoftSQL.ExecuteQuery/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [2.1.0] - 2024-09-10
### Fixed
- Fixed how null values are handled by setting them as DBNull.Value.
- Fixed how JValue parameters are handled by adding a check for those values and assigning ToString() method on the values.

## [2.0.0] - 2024-08-01
### Changed
- [Breaking] The task now uses Microsoft.Data.SqlClient instead of System.Data.SqlClient.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -130,4 +131,71 @@ public async Task TestExecuteQuery_NonQuery()
CleanUp();
}
}

[TestMethod]
public async Task TestExecuteQuery_DBNullValues()
{
var options = new Options()
{
SqlTransactionIsolationLevel = SqlTransactionIsolationLevel.Default,
CommandTimeoutSeconds = 30,
ThrowErrorOnFailure = true
};

var input = new Input()
{
ConnectionString = _connString,
Query = $@"INSERT INTO {_tableName} VALUES (1, @Last, @First)",
ExecuteType = ExecuteTypes.NonQuery,
Parameters = new QueryParameter[]
{
new() {
Name = "@Last",
Value = null,
SqlDataType = SqlDataTypes.Auto
},
new() {
Name = "@First",
Value = "Mikki",
SqlDataType = SqlDataTypes.Auto
}
}
};

var result = await MicrosoftSQL.ExecuteQuery(input, options, default);
Assert.IsTrue(result.Success);

input.Query = $"SELECT * FROM {_tableName}";
input.ExecuteType = ExecuteTypes.Auto;
var selectResult = await MicrosoftSQL.ExecuteQuery(input, options, default);
Assert.AreEqual(null, (string)selectResult.Data[0]["LastName"]);
Assert.AreEqual("Mikki", (string)selectResult.Data[0]["FirstName"]);

var jToken = new JObject(
new JProperty("Etu", "Mikki")
);

input.Parameters = new QueryParameter[]
{
new() {
Name = "@Last",
Value = jToken["Suku"],
SqlDataType = SqlDataTypes.Auto
},
new() {
Name = "@First",
Value = jToken["Etu"],
SqlDataType = SqlDataTypes.Auto
}
};

result = await MicrosoftSQL.ExecuteQuery(input, options, default);
Assert.IsTrue(result.Success);
RikuVirtanen marked this conversation as resolved.
Show resolved Hide resolved

input.Query = $"SELECT * FROM {_tableName}";
input.ExecuteType = ExecuteTypes.Auto;
selectResult = await MicrosoftSQL.ExecuteQuery(input, options, default);
Assert.AreEqual(null, (string)selectResult.Data[0]["LastName"]);
Assert.AreEqual("Mikki", (string)selectResult.Data[0]["FirstName"]);
}
}
Original file line number Diff line number Diff line change
@@ -1,116 +1,116 @@
namespace Frends.MicrosoftSQL.ExecuteQuery.Definitions;

/// <summary>
/// SQL transaction isolation levels.
/// </summary>
public enum SqlTransactionIsolationLevel
{
/// <summary>
/// A different isolation level than the one specified is being used, but the level cannot be determined.
/// </summary>
Unspecified = -1,

/// <summary>
/// No transaction.
/// </summary>
None,

/// <summary>
/// Default is configured by the SQL Server, usually ReadCommited.
/// </summary>
Default,

/// <summary>
/// Shared locks are held while the data is being read to avoid dirty reads, but the data can be changed before the end of the transaction, resulting in non-repeatable reads or phantom data.
/// </summary>
ReadCommitted = 4096,

/// <summary>
/// A dirty read is possible, meaning that no shared locks are issued and no exclusive locks are honored.
/// </summary>
namespace Frends.MicrosoftSQL.ExecuteQuery.Definitions;
/// <summary>
/// SQL transaction isolation levels.
/// </summary>
public enum SqlTransactionIsolationLevel
{
/// <summary>
/// A different isolation level than the one specified is being used, but the level cannot be determined.
/// </summary>
Unspecified = -1,
/// <summary>
/// No transaction.
/// </summary>
None,
/// <summary>
/// Default is configured by the SQL Server, usually ReadCommited.
/// </summary>
Default,
/// <summary>
/// Shared locks are held while the data is being read to avoid dirty reads, but the data can be changed before the end of the transaction, resulting in non-repeatable reads or phantom data.
/// </summary>
ReadCommitted = 4096,
/// <summary>
/// A dirty read is possible, meaning that no shared locks are issued and no exclusive locks are honored.
/// </summary>
ReadUncommitted = 256,

/// <summary>
/// Locks are placed on all data that is used in a query, preventing other users from updating the data.
/// Prevents non-repeatable reads but phantom rows are still possible.
/// </summary>
RepeatableRead = 65536,

/// <summary>
/// A range lock is placed on the System.Data.DataSet, preventing other users from updating or inserting rows into the dataset until the transaction is complete.
/// </summary>
Serializable = 1048576,

/// <summary>
/// Reduces blocking by storing a version of data that one application can read while another is modifying the same data.
/// Indicates that from one transaction you cannot see changes made in other transactions, even if you requery.
/// </summary>
Snapshot = 16777216,
}

/// <summary>
/// Execute types.
/// </summary>
public enum ExecuteTypes
{
/// <summary>
/// ExecuteReader for SELECT-query and NonQuery for UPDATE, INSERT, or DELETE statements.
/// </summary>
Auto,

/// <summary>
/// Executes a Transact-SQL statement against the connection and returns the number of rows affected.
/// </summary>
NonQuery,

/// <summary>
/// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored.
/// </summary>
Scalar,

/// <summary>
/// Executes the query, and returns an object that can iterate over the entire result set.
/// </summary>
ExecuteReader
}

/// <summary>
/// SQL Server-specific data type.
/// </summary>
public enum SqlDataTypes
{
#pragma warning disable CS1591 // self explanatory
Auto = -1,
BigInt = 0,
Binary = 1,
Bit = 2,
Char = 3,
DateTime = 4,
Decimal = 5,
Float = 6,
Image = 7,
Int = 8,
Money = 9,
NChar = 10,
NText = 11,
NVarChar = 12,
Real = 13,
UniqueIdentifier = 14,
SmallDateTime = 15,
SmallInt = 16,
SmallMoney = 17,
Text = 18,
Timestamp = 19,
TinyInt = 20,
VarBinary = 21,
VarChar = 22,
Variant = 23,
Xml = 25,
Udt = 29,
Structured = 30,
Date = 31,
Time = 32,
DateTime2 = 33,
DateTimeOffset = 34
#pragma warning restore CS1591 // self explanatory
/// <summary>
/// Locks are placed on all data that is used in a query, preventing other users from updating the data.
/// Prevents non-repeatable reads but phantom rows are still possible.
/// </summary>
RepeatableRead = 65536,
/// <summary>
/// A range lock is placed on the System.Data.DataSet, preventing other users from updating or inserting rows into the dataset until the transaction is complete.
/// </summary>
Serializable = 1048576,
/// <summary>
/// Reduces blocking by storing a version of data that one application can read while another is modifying the same data.
/// Indicates that from one transaction you cannot see changes made in other transactions, even if you requery.
/// </summary>
Snapshot = 16777216,
}
/// <summary>
/// Execute types.
/// </summary>
public enum ExecuteTypes
{
/// <summary>
/// ExecuteReader for SELECT-query and NonQuery for UPDATE, INSERT, or DELETE statements.
/// </summary>
Auto,
/// <summary>
/// Executes a Transact-SQL statement against the connection and returns the number of rows affected.
/// </summary>
NonQuery,
/// <summary>
/// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored.
/// </summary>
Scalar,
/// <summary>
/// Executes the query, and returns an object that can iterate over the entire result set.
/// </summary>
ExecuteReader
}
/// <summary>
/// SQL Server-specific data type.
/// </summary>
public enum SqlDataTypes
{
#pragma warning disable CS1591 // self explanatory
Auto = -1,
BigInt = 0,
Binary = 1,
Bit = 2,
Char = 3,
DateTime = 4,
Decimal = 5,
Float = 6,
Image = 7,
Int = 8,
Money = 9,
NChar = 10,
NText = 11,
NVarChar = 12,
Real = 13,
UniqueIdentifier = 14,
SmallDateTime = 15,
SmallInt = 16,
SmallMoney = 17,
Text = 18,
Timestamp = 19,
TinyInt = 20,
VarBinary = 21,
VarChar = 22,
Variant = 23,
Xml = 25,
Udt = 29,
Structured = 30,
Date = 31,
Time = 32,
DateTime2 = 33,
DateTimeOffset = 34
#pragma warning restore CS1591 // self explanatory
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,26 @@ public static async Task<Result> ExecuteQuery([PropertyTab] Input input, [Proper
{
foreach (var parameter in input.Parameters)
{
if (parameter.SqlDataType is SqlDataTypes.Auto)
command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value);
if (parameter.Value == null)
{
command.Parameters.AddWithValue(parameterName: parameter.Name, value: DBNull.Value);
}
else if (parameter.Value.GetType() == typeof(JValue))
{
if (((JToken)parameter.Value).Type == JTokenType.Null)
command.Parameters.AddWithValue(parameterName: parameter.Name, value: DBNull.Value);
else
command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value.ToString());
}
else if (parameter.SqlDataType is SqlDataTypes.Auto)
{
command.Parameters.AddWithValue(parameterName: parameter.Name, value: parameter.Value ?? DBNull.Value);
}
else
{
var sqlDbType = (SqlDbType)Enum.Parse(typeof(SqlDbType), parameter.SqlDataType.ToString());
var commandParameter = command.Parameters.Add(parameter.Name, sqlDbType);
commandParameter.Value = parameter.Value;
commandParameter.Value = parameter.Value ?? DBNull.Value;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>2.0.0</Version>
<Version>2.1.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand Down
4 changes: 2 additions & 2 deletions Frends.MicrosoftSQL.ExecuteQuery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ You can install the Task via Frends UI Task View.

## Building


Rebuild the project

`dotnet build`

Run tests

Create a simple SQL server to docker:
`docker-compose up`
`cd Frends.MicrosoftSQL.ExecuteQuery.Tests`
`docker-compose up -d`

`dotnet test`

Expand Down
Loading