From e7ad0874de8b3680f0453789b10f6e78d436dc40 Mon Sep 17 00:00:00 2001 From: Sergii Volchkov Date: Thu, 15 Jun 2017 12:06:25 +0200 Subject: [PATCH] Enable SqlDataRecord TVPs on corefx. (#801) --- Dapper.Tests/ParameterTests.cs | 135 +++++++++++++++++------- Dapper/Dapper.csproj | 2 +- Dapper/SqlDataRecordHandler.cs | 2 - Dapper/SqlDataRecordListTVPParameter.cs | 16 +-- Dapper/SqlMapper.cs | 10 +- 5 files changed, 104 insertions(+), 61 deletions(-) diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index a1e43f9fa..506e5b243 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -37,6 +37,24 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id } } + private static List CreateSqlDataRecordList(IEnumerable numbers) + { + var number_list = new List(); + + // Create an SqlMetaData object that describes our table type. + Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) }; + + foreach (int n in numbers) + { + // Create a new record, using the metadata array above. + var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition); + rec.SetInt32(0, n); // Set the value. + number_list.Add(rec); // Add it to the list. + } + + return number_list; + } + private class IntDynamicParam : SqlMapper.IDynamicParameters { private readonly IEnumerable numbers; @@ -50,18 +68,7 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // Create an SqlMetaData object that describes our table type. - Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) }; - - foreach (int n in numbers) - { - // Create a new record, using the metadata array above. - var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition); - rec.SetInt32(0, n); // Set the value. - number_list.Add(rec); // Add it to the list. - } + var number_list = CreateSqlDataRecordList(numbers); // Add the table parameter. var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); @@ -84,18 +91,7 @@ public void AddParameter(IDbCommand command, string name) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // Create an SqlMetaData object that describes our table type. - Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) }; - - foreach (int n in numbers) - { - // Create a new record, using the metadata array above. - var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition); - rec.SetInt32(0, n); // Set the value. - number_list.Add(rec); // Add it to the list. - } + var number_list = CreateSqlDataRecordList(numbers); // Add the table parameter. var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured); @@ -217,7 +213,6 @@ public void TestMassiveStrings() .IsEqualTo(str); } -#if !COREFX [Fact] public void TestTVPWithAnonymousObject() { @@ -288,18 +283,7 @@ public DynamicParameterWithIntTVP(IEnumerable numbers) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // Create an SqlMetaData object that describes our table type. - Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) }; - - foreach (int n in numbers) - { - // Create a new record, using the metadata array above. - var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition); - rec.SetInt32(0, n); // Set the value. - number_list.Add(rec); // Add it to the list. - } + var number_list = CreateSqlDataRecordList(numbers); // Add the table parameter. var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); @@ -343,6 +327,83 @@ public void TestTVPWithAdditionalParams() } } + [Fact] + public void TestSqlDataRecordListParametersWithAsTableValuedParameter() + { + try + { + connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); + connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); + + var records = CreateSqlDataRecordList(new int[] { 1, 2, 3 }); + + var nums = connection.Query("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + nums = connection.Query("select * from @integers", new { integers = records.AsTableValuedParameter("int_list_type") }).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + try + { + connection.Query("select * from @integers", new { integers = records.AsTableValuedParameter() }).First(); + throw new InvalidOperationException(); + } + catch (Exception ex) + { + ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); + } + } + finally + { + try + { + connection.Execute("DROP PROC get_ints"); + } + finally + { + connection.Execute("DROP TYPE int_list_type"); + } + } + } + + [Fact] + public void TestSqlDataRecordListParametersWithTypeHandlers() + { + try + { + connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); + connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); + + // Variable type has to be IEnumerable for TypeHandler to kick in. + IEnumerable records = CreateSqlDataRecordList(new int[] { 1, 2, 3 }); + + var nums = connection.Query("get_ints", new { integers = records }, commandType: CommandType.StoredProcedure).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + try + { + connection.Query("select * from @integers", new { integers = records }).First(); + throw new InvalidOperationException(); + } + catch (Exception ex) + { + ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); + } + } + finally + { + try + { + connection.Execute("DROP PROC get_ints"); + } + finally + { + connection.Execute("DROP TYPE int_list_type"); + } + } + } + +#if !COREFX [Fact] public void DataTableParameters() { diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 59856b4d8..bb7979dab 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -17,7 +17,7 @@ - + diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index 9ca63bf40..301aa36ab 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data; -#if !COREFX namespace Dapper { internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler @@ -18,4 +17,3 @@ public void SetValue(IDbDataParameter parameter, object value) } } } -#endif \ No newline at end of file diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index 9a0706aed..8e8384705 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; using System.Data; -using System.Reflection; -#if !COREFX + namespace Dapper { /// @@ -23,18 +22,6 @@ public SqlDataRecordListTVPParameter(IEnumerable setTypeName; - - static SqlDataRecordListTVPParameter() - { - var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty(nameof(System.Data.SqlClient.SqlParameter.TypeName), BindingFlags.Instance | BindingFlags.Public); - if (prop != null && prop.PropertyType == typeof(string) && prop.CanWrite) - { - setTypeName = (Action) - Delegate.CreateDelegate(typeof(Action), prop.GetSetMethod()); - } - } - void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); @@ -54,4 +41,3 @@ internal static void Set(IDbDataParameter parameter, IEnumerable(); #if !COREFX AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); +#endif try { AddSqlDataRecordsTypeHandler(clone); } catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } -#endif AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); } -#if !COREFX [MethodImpl(MethodImplOptions.NoInlining)] private static void AddSqlDataRecordsTypeHandler(bool clone) { AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); } -#endif /// /// Configure the specified type to be mapped to a given db-type. @@ -3662,15 +3660,15 @@ public static void SetTypeName(this DataTable table, string typeName) /// The that has a type name associated with it. public static string GetTypeName(this DataTable table) => table?.ExtendedProperties[DataTableTypeNameKey] as string; +#endif /// - /// Used to pass a IEnumerable<SqlDataRecord> as a . + /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// - /// Thhe list of records to convert to TVPs. + /// The list of records to convert to TVPs. /// The sql parameter type name. public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) => new SqlDataRecordListTVPParameter(list, typeName); -#endif // one per thread [ThreadStatic]