Skip to content

NH-3736 - Pass Table Variable as a Input parameter to Stored Procedure #376

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
87 changes: 87 additions & 0 deletions src/NHibernate.Test/SqlTest/Custom/MsSQL/MSSQLTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Data;
using NHibernate.Dialect;
using NUnit.Framework;

Expand All @@ -16,5 +17,91 @@ protected override bool AppliesTo(Dialect.Dialect dialect)
{
return dialect is MsSql2000Dialect;
}

[Test]
public void CanCallStoredProcedureWithTableParameterInferedAutomatically()
{
//NH-3736
var createTypeSql = "CREATE TYPE dbo.TableType AS TABLE " +
"( " +
" a INT, " +
" b INT " +
")";
var createProcedureSql = "CREATE PROCEDURE dbo.TableProcedure " +
"( " +
" @t TableType READONLY " +
") " +
"AS " +
"BEGIN " +
" SELECT * " +
" FROM @t " +
" RETURN @@ROWCOUNT " +
"END";

var deleteTypeSql = "DROP TYPE dbo.TableType";
var deleteProcedureSql = "DROP PROCEDURE dbo.TableProcedure";

using (var session = this.OpenSession())
{
session.CreateSQLQuery(createTypeSql).ExecuteUpdate();
session.CreateSQLQuery(createProcedureSql).ExecuteUpdate();

var table = new DataTable("dbo.TableType");
table.Columns.Add("a", typeof (int));
table.Columns.Add("b", typeof(int));
table.Rows.Add(1, 2);

var result = session.CreateSQLQuery("EXEC dbo.TableProcedure :t").SetParameter("t", table).List();

session.CreateSQLQuery(deleteProcedureSql).ExecuteUpdate();
session.CreateSQLQuery(deleteTypeSql).ExecuteUpdate();

Assert.IsNotNull(result);
Assert.IsNotEmpty(result);
}
}

[Test]
public void CanCallStoredProcedureWithTableParameterSetSpecifically()
{
//NH-3736
var createTypeSql = "CREATE TYPE dbo.TableType AS TABLE " +
"( " +
" a INT, " +
" b INT " +
")";
var createProcedureSql = "CREATE PROCEDURE dbo.TableProcedure " +
"( " +
" @t TableType READONLY " +
") " +
"AS " +
"BEGIN " +
" SELECT * " +
" FROM @t " +
" RETURN @@ROWCOUNT " +
"END";

var deleteTypeSql = "DROP TYPE dbo.TableType";
var deleteProcedureSql = "DROP PROCEDURE dbo.TableProcedure";

using (var session = this.OpenSession())
{
session.CreateSQLQuery(createTypeSql).ExecuteUpdate();
session.CreateSQLQuery(createProcedureSql).ExecuteUpdate();

var table = new DataTable("dbo.TableType");
table.Columns.Add("a", typeof(int));
table.Columns.Add("b", typeof(int));
table.Rows.Add(1, 2);

var result = session.CreateSQLQuery("EXEC dbo.TableProcedure :t").SetParameter("t", table, NHibernateUtil.Structured("dbo.TableType")).List();

session.CreateSQLQuery(deleteProcedureSql).ExecuteUpdate();
session.CreateSQLQuery(deleteTypeSql).ExecuteUpdate();

Assert.IsNotNull(result);
Assert.IsNotEmpty(result);
}
}
}
}
11 changes: 11 additions & 0 deletions src/NHibernate/Driver/Sql2008ClientDriver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using NHibernate.SqlTypes;
using NHibernate.Util;

namespace NHibernate.Driver
Expand All @@ -11,8 +13,10 @@ public class Sql2008ClientDriver : SqlClientDriver

#if NETFX
private static readonly Action<object, SqlDbType> SetSqlDbType = (p, t) => ((System.Data.SqlClient.SqlParameter) p).SqlDbType = t;
private static readonly Action<object, string> SetTypeName = (p, t) => ((System.Data.SqlClient.SqlParameter) p).TypeName = t;
#else
private static readonly Action<object, SqlDbType> SetSqlDbType = DelegateHelper.BuildPropertySetter<SqlDbType>(System.Type.GetType("System.Data.SqlClient.SqlParameter, System.Data.SqlClient", true), "SqlDbType");
private static readonly Action<object, string> SetTypeName = DelegateHelper.BuildPropertySetter<SqlDbType>(System.Type.GetType("System.Data.SqlClient.SqlParameter, System.Data.SqlClient", true), "TypeName");
#endif

protected override void InitializeParameter(DbParameter dbParam, string name, SqlTypes.SqlType sqlType)
Expand All @@ -28,6 +32,13 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq
SetSqlDbType(dbParam, SqlDbType.Date);
break;
}

if (sqlType is StructuredSqlType type)
{
//NH-3736
SetSqlDbType(dbParam, SqlDbType.Structured);
SetTypeName(dbParam, type.TypeName);
}
}

public override bool RequiresTimeSpanForTime => true;
Expand Down
3 changes: 1 addition & 2 deletions src/NHibernate/Driver/SqlClientDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,12 @@ public override bool SupportsMultipleOpenReaders
protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType)
{
base.InitializeParameter(dbParam, name, sqlType);

// Defaults size/precision/scale
switch (dbParam.DbType)
{
case DbType.AnsiString:
case DbType.AnsiStringFixedLength:
dbParam.Size = IsAnsiText(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForAnsiClob : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString;
dbParam.Size = IsAnsiText(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForAnsiClob : MsSql2000Dialect.MaxSizeForLengthLimitedAnsiString;
break;
case DbType.Binary:
dbParam.Size = IsBlob(dbParam, sqlType) ? MsSql2000Dialect.MaxSizeForBlob : MsSql2000Dialect.MaxSizeForLengthLimitedBinary;
Expand Down
41 changes: 35 additions & 6 deletions src/NHibernate/Impl/AbstractQueryImpl.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using NHibernate.Engine;
using NHibernate.Engine.Query;
using NHibernate.Hql;
Expand Down Expand Up @@ -312,7 +313,22 @@ public IQuery SetParameter<T>(int position, T val)
{
CheckPositionalParameter(position);

return SetParameter(position, val, parameterMetadata.GetOrdinalParameterExpectedType(position + 1) ?? GuessType(typeof(T)));
var type = parameterMetadata.GetOrdinalParameterExpectedType(position + 1);

if (type == null)
{
var table = val as DataTable;
if (table != null)
{
type = NHibernateUtil.Structured(table.TableName);
}
else
{
type = GuessType(typeof(T));
}
}

return SetParameter(position, val, type);
}

private void CheckPositionalParameter(int position)
Expand All @@ -329,7 +345,22 @@ private void CheckPositionalParameter(int position)

public IQuery SetParameter<T>(string name, T val)
{
return SetParameter(name, val, parameterMetadata.GetNamedParameterExpectedType(name) ?? GuessType(typeof (T)));
var type = parameterMetadata.GetNamedParameterExpectedType(name);

if (type == null)
{
var table = val as DataTable;
if (table != null)
{
type = NHibernateUtil.Structured(table.TableName);
}
else
{
type = GuessType(typeof (T));
}
}

return SetParameter(name, val, type);
}

public IQuery SetParameter(string name, object val)
Expand All @@ -345,8 +376,7 @@ public IQuery SetParameter(string name, object val)
IType type = parameterMetadata.GetNamedParameterExpectedType(name);
if (type == null)
{
throw new ArgumentNullException("val",
"A type specific Set(name, val) should be called because the Type can not be guessed from a null value.");
throw new ArgumentNullException("val", "A type specific Set(name, val) should be called because the Type can not be guessed from a null value.");
}

SetParameter(name, val, type);
Expand All @@ -363,8 +393,7 @@ public IQuery SetParameter(int position, object val)
{
if (val == null)
{
throw new ArgumentNullException("val",
"A type specific Set(position, val) should be called because the Type can not be guessed from a null value.");
throw new ArgumentNullException("val", "A type specific Set(position, val) should be called because the Type can not be guessed from a null value.");
}
else
{
Expand Down
22 changes: 21 additions & 1 deletion src/NHibernate/NHibernateUtil.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Data;
using NHibernate.Collection;
using NHibernate.Impl;
using NHibernate.Intercept;
Expand Down Expand Up @@ -41,7 +42,10 @@ public static IType GuessType(System.Type type)
var value = TypeFactory.GetDefaultTypeFor(type);
if (value != null)
return value;


if (type == typeof (DataTable))
return Structured();

if (type.IsEnum)
return (IType) Activator.CreateInstance(typeof (EnumType<>).MakeGenericType(type));

Expand All @@ -54,6 +58,22 @@ public static IType GuessType(System.Type type)
return Entity(type);
}

/// <summary>
/// Table-valued type with type name parameter
/// </summary>
public static StructuredType Structured(string typeName)
{
return new StructuredType(typeName);
}

/// <summary>
/// Table-valued type
/// </summary>
public static StructuredType Structured()
{
return Structured(string.Empty);
}

/// <summary>
/// NHibernate Ansi String type
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/NHibernate/SqlTypes/SqlTypeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public static class SqlTypeFactory
public static readonly SqlType UInt32 = new SqlType(DbType.UInt32);
public static readonly SqlType UInt64 = new SqlType(DbType.UInt64);

public static SqlType Structured(string typeName)
{
return new StructuredSqlType(typeName);
}

public static readonly SqlType[] NoTypes = Array.Empty<SqlType>();

private delegate T TypeWithLenOrScaleCreateDelegate<out T, in TDim>(TDim lengthOrScale); // Func<int, T>
Expand Down
39 changes: 39 additions & 0 deletions src/NHibernate/SqlTypes/StructuredSqlType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Data;

namespace NHibernate.SqlTypes
{
[Serializable]
public class StructuredSqlType : SqlType, IEquatable<StructuredSqlType>
{
/// <summary>
/// Initializes a new instance of the <see cref="StringSqlType"/> class.
/// </summary>
public StructuredSqlType(string typeName) : base(DbType.Object)
{
TypeName = typeName ?? string.Empty;
}

public string TypeName { get; }

public override bool Equals(object obj)
{
return Equals(obj as StructuredSqlType);
}

public override int GetHashCode()
{
unchecked
{
return (base.GetHashCode() * 397) ^ (TypeName != null ? TypeName.GetHashCode() : 0);
}
}

public bool Equals(StructuredSqlType other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && string.Equals(TypeName, other.TypeName);
}
}
}
Loading