Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 6dde19a

Browse files
authored
Adding DataTable support in SqlParameter (#19905) (#19961)
Adding DataTable support in SqlParameter
1 parent 3ab05c1 commit 6dde19a

File tree

6 files changed

+361
-4
lines changed

6 files changed

+361
-4
lines changed

src/System.Data.SqlClient/src/Microsoft/SqlServer/Server/MetadataUtilsSmi.cs

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ internal class MetaDataUtilsSmi
6868
SqlDbType.NVarChar, // System.Data.SqlTypes.SqlChars
6969
SqlDbType.VarBinary, // System.Data.SqlTypes.SqlBytes
7070
SqlDbType.Xml, // System.Data.SqlTypes.SqlXml
71+
SqlDbType.Structured, // System.Data.DataTable
7172
SqlDbType.Structured, // System.Collections.IEnumerable, used for TVPs it must return IDataRecord
7273
SqlDbType.Structured, // System.Collections.Generic.IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>
7374
SqlDbType.Time, // System.TimeSpan
@@ -121,12 +122,12 @@ private static Dictionary<Type, ExtendedClrTypeCode> CreateTypeToExtendedTypeCod
121122
{ typeof(SqlChars), ExtendedClrTypeCode.SqlChars },
122123
{ typeof(SqlBytes), ExtendedClrTypeCode.SqlBytes },
123124
{ typeof(SqlXml), ExtendedClrTypeCode.SqlXml },
125+
{ typeof(DataTable), ExtendedClrTypeCode.DataTable },
124126
{ typeof(DbDataReader), ExtendedClrTypeCode.DbDataReader },
125127
{ typeof(IEnumerable<SqlDataRecord>), ExtendedClrTypeCode.IEnumerableOfSqlDataRecord },
126128
{ typeof(TimeSpan), ExtendedClrTypeCode.TimeSpan },
127129
{ typeof(DateTimeOffset), ExtendedClrTypeCode.DateTimeOffset },
128130
};
129-
Debug.Assert(dictionary.Count == Count);
130131
return dictionary;
131132
}
132133

@@ -336,7 +337,11 @@ object value
336337
case SqlDbType.Structured:
337338
if (isMultiValued)
338339
{
339-
if (value is IEnumerable<SqlDataRecord>)
340+
if (value is DataTable)
341+
{
342+
extendedCode = ExtendedClrTypeCode.DataTable;
343+
}
344+
else if (value is IEnumerable<SqlDataRecord>)
340345
{
341346
extendedCode = ExtendedClrTypeCode.IEnumerableOfSqlDataRecord;
342347
}
@@ -603,5 +608,188 @@ internal static SmiExtendedMetaData SmiMetaDataFromType(string colName, Type col
603608
null,
604609
null);
605610
}
611+
612+
// Extract metadata for a single DataColumn
613+
internal static SmiExtendedMetaData SmiMetaDataFromDataColumn(DataColumn column, DataTable parent)
614+
{
615+
SqlDbType dbType = InferSqlDbTypeFromType_Katmai(column.DataType);
616+
if (InvalidSqlDbType == dbType)
617+
{
618+
throw SQL.UnsupportedColumnTypeForSqlProvider(column.ColumnName, column.DataType.Name);
619+
}
620+
621+
long maxLength = AdjustMaxLength(dbType, column.MaxLength);
622+
if (InvalidMaxLength == maxLength)
623+
{
624+
throw SQL.InvalidColumnMaxLength(column.ColumnName, maxLength);
625+
}
626+
627+
byte precision;
628+
byte scale;
629+
if (column.DataType == typeof(SqlDecimal))
630+
{
631+
// Must scan all values in column to determine best-fit precision & scale
632+
Debug.Assert(null != parent);
633+
scale = 0;
634+
byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
635+
foreach (DataRow row in parent.Rows)
636+
{
637+
object obj = row[column];
638+
if (!(obj is DBNull))
639+
{
640+
SqlDecimal value = (SqlDecimal)obj;
641+
if (!value.IsNull)
642+
{
643+
byte tempNonFractPrec = checked((byte)(value.Precision - value.Scale));
644+
if (tempNonFractPrec > nonFractionalPrecision)
645+
{
646+
nonFractionalPrecision = tempNonFractPrec;
647+
}
648+
649+
if (value.Scale > scale)
650+
{
651+
scale = value.Scale;
652+
}
653+
}
654+
}
655+
}
656+
657+
precision = checked((byte)(nonFractionalPrecision + scale));
658+
659+
if (SqlDecimal.MaxPrecision < precision)
660+
{
661+
throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
662+
}
663+
else if (0 == precision)
664+
{
665+
precision = 1;
666+
}
667+
}
668+
else if (dbType == SqlDbType.DateTime2 || dbType == SqlDbType.DateTimeOffset || dbType == SqlDbType.Time)
669+
{
670+
// Time types care about scale, too. But have to infer maximums for these.
671+
precision = 0;
672+
scale = SmiMetaData.DefaultTime.Scale;
673+
}
674+
else if (dbType == SqlDbType.Decimal)
675+
{
676+
// Must scan all values in column to determine best-fit precision & scale
677+
Debug.Assert(null != parent);
678+
scale = 0;
679+
byte nonFractionalPrecision = 0; // finds largest non-Fractional portion of precision
680+
foreach (DataRow row in parent.Rows)
681+
{
682+
object obj = row[column];
683+
if (!(obj is DBNull))
684+
{
685+
SqlDecimal value = (SqlDecimal)(Decimal)obj;
686+
byte tempNonFractPrec = checked((byte)(value.Precision - value.Scale));
687+
if (tempNonFractPrec > nonFractionalPrecision)
688+
{
689+
nonFractionalPrecision = tempNonFractPrec;
690+
}
691+
692+
if (value.Scale > scale)
693+
{
694+
scale = value.Scale;
695+
}
696+
}
697+
}
698+
699+
precision = checked((byte)(nonFractionalPrecision + scale));
700+
701+
if (SqlDecimal.MaxPrecision < precision)
702+
{
703+
throw SQL.InvalidTableDerivedPrecisionForTvp(column.ColumnName, precision);
704+
}
705+
else if (0 == precision)
706+
{
707+
precision = 1;
708+
}
709+
}
710+
else
711+
{
712+
precision = 0;
713+
scale = 0;
714+
}
715+
716+
// In Net Core, since DataColumn.Locale is not accessible because it is internal and in a separate assembly,
717+
// we try to get the Locale from the parent
718+
CultureInfo columnLocale = ((null != parent) ? parent.Locale : CultureInfo.CurrentCulture);
719+
720+
return new SmiExtendedMetaData(
721+
dbType,
722+
maxLength,
723+
precision,
724+
scale,
725+
columnLocale.LCID,
726+
SmiMetaData.DefaultNVarChar.CompareOptions,
727+
false, // no support for multi-valued columns in a TVP yet
728+
null, // no support for structured columns yet
729+
null, // no support for structured columns yet
730+
column.ColumnName,
731+
null,
732+
null,
733+
null);
734+
}
735+
736+
internal static long AdjustMaxLength(SqlDbType dbType, long maxLength)
737+
{
738+
if (SmiMetaData.UnlimitedMaxLengthIndicator != maxLength)
739+
{
740+
if (maxLength < 0)
741+
{
742+
maxLength = InvalidMaxLength;
743+
}
744+
745+
switch (dbType)
746+
{
747+
case SqlDbType.Binary:
748+
if (maxLength > SmiMetaData.MaxBinaryLength)
749+
{
750+
maxLength = InvalidMaxLength;
751+
}
752+
break;
753+
case SqlDbType.Char:
754+
if (maxLength > SmiMetaData.MaxANSICharacters)
755+
{
756+
maxLength = InvalidMaxLength;
757+
}
758+
break;
759+
case SqlDbType.NChar:
760+
if (maxLength > SmiMetaData.MaxUnicodeCharacters)
761+
{
762+
maxLength = InvalidMaxLength;
763+
}
764+
break;
765+
case SqlDbType.NVarChar:
766+
// Promote to MAX type if it won't fit in a normal type
767+
if (SmiMetaData.MaxUnicodeCharacters < maxLength)
768+
{
769+
maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
770+
}
771+
break;
772+
case SqlDbType.VarBinary:
773+
// Promote to MAX type if it won't fit in a normal type
774+
if (SmiMetaData.MaxBinaryLength < maxLength)
775+
{
776+
maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
777+
}
778+
break;
779+
case SqlDbType.VarChar:
780+
// Promote to MAX type if it won't fit in a normal type
781+
if (SmiMetaData.MaxANSICharacters < maxLength)
782+
{
783+
maxLength = SmiMetaData.UnlimitedMaxLengthIndicator;
784+
}
785+
break;
786+
default:
787+
break;
788+
}
789+
}
790+
791+
return maxLength;
792+
}
793+
606794
}
607795
}

src/System.Data.SqlClient/src/Microsoft/SqlServer/Server/ValueUtilsSmi.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,9 @@ ParameterPeekAheadValue peekAhead
16111611

16121612
switch (typeCode)
16131613
{
1614+
case ExtendedClrTypeCode.DataTable:
1615+
SetDataTable_Unchecked(sink, setters, ordinal, metaData, (DataTable)value);
1616+
break;
16141617
case ExtendedClrTypeCode.DbDataReader:
16151618
SetDbDataReader_Unchecked(sink, setters, ordinal, metaData, (DbDataReader)value);
16161619
break;
@@ -1629,6 +1632,57 @@ ParameterPeekAheadValue peekAhead
16291632
}
16301633
}
16311634

1635+
private static void SetDataTable_Unchecked(
1636+
SmiEventSink_Default sink,
1637+
SmiTypedGetterSetter setters,
1638+
int ordinal,
1639+
SmiMetaData metaData,
1640+
DataTable value
1641+
)
1642+
{
1643+
// Get the target gettersetter
1644+
setters = setters.GetTypedGetterSetter(sink, ordinal);
1645+
sink.ProcessMessagesAndThrow();
1646+
1647+
// iterate over all records
1648+
// if first record was obtained earlier, use it prior to pulling more
1649+
ExtendedClrTypeCode[] cellTypes = new ExtendedClrTypeCode[metaData.FieldMetaData.Count];
1650+
for (int i = 0; i < metaData.FieldMetaData.Count; i++)
1651+
{
1652+
cellTypes[i] = ExtendedClrTypeCode.Invalid;
1653+
}
1654+
foreach (DataRow row in value.Rows)
1655+
{
1656+
setters.NewElement(sink);
1657+
sink.ProcessMessagesAndThrow();
1658+
1659+
// Set all columns in the record
1660+
for (int i = 0; i < metaData.FieldMetaData.Count; i++)
1661+
{
1662+
SmiMetaData fieldMetaData = metaData.FieldMetaData[i];
1663+
if (row.IsNull(i))
1664+
{
1665+
SetDBNull_Unchecked(sink, setters, i);
1666+
}
1667+
else
1668+
{
1669+
object cellValue = row[i];
1670+
1671+
// Only determine cell types for first row, to save expensive
1672+
if (ExtendedClrTypeCode.Invalid == cellTypes[i])
1673+
{
1674+
cellTypes[i] = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(
1675+
fieldMetaData.SqlDbType, fieldMetaData.IsMultiValued, cellValue);
1676+
}
1677+
SetCompatibleValueV200(sink, setters, i, fieldMetaData, cellValue, cellTypes[i], 0, NoLengthLimit, null);
1678+
}
1679+
}
1680+
}
1681+
1682+
setters.EndElements(sink);
1683+
sink.ProcessMessagesAndThrow();
1684+
}
1685+
16321686
// Copy multiple fields from reader to ITypedSettersV3
16331687
// Assumes caller enforces that reader and setter metadata are compatible
16341688
internal static void FillCompatibleITypedSettersFromReader(SmiEventSink_Default sink, ITypedSettersV3 setters, SmiMetaData[] metaData, SqlDataReader reader)

src/System.Data.SqlClient/src/Resources/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,4 +1045,10 @@
10451045
<data name="PlatformNotSupported_DataSqlClient" xml:space="preserve">
10461046
<value>System.Data.SqlClient is not supported on this platform.</value>
10471047
</data>
1048+
<data name="SqlParameter_InvalidTableDerivedPrecisionForTvp" xml:space="preserve">
1049+
<value>Precision '{0}' required to send all values in column '{1}' exceeds the maximum supported precision '{2}'. The values must all fit in a single precision.</value>
1050+
</data>
1051+
<data name="SqlProvider_InvalidDataColumnMaxLength" xml:space="preserve">
1052+
<value>The size of column '{0}' is not supported. The size is {1}.</value>
1053+
</data>
10481054
</root>

src/System.Data.SqlClient/src/System/Data/SqlClient/SqlParameter.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ internal static object CoerceValue(object value, MetaType destinationType, out b
946946
value = new DateTimeOffset((DateTime)value);
947947
}
948948
else if (TdsEnums.SQLTABLE == destinationType.TDSType && (
949+
value is DataTable ||
949950
value is DbDataReader ||
950951
value is System.Collections.Generic.IEnumerable<SqlDataRecord>))
951952
{
@@ -1078,7 +1079,48 @@ private void GetActualFieldsAndProperties(out List<MSS.SmiExtendedMetaData> fiel
10781079
peekAhead = null;
10791080

10801081
object value = GetCoercedValue();
1081-
if (value is SqlDataReader)
1082+
if (value is DataTable dt)
1083+
{
1084+
if (dt.Columns.Count <= 0)
1085+
{
1086+
throw SQL.NotEnoughColumnsInStructuredType();
1087+
}
1088+
fields = new List<MSS.SmiExtendedMetaData>(dt.Columns.Count);
1089+
bool[] keyCols = new bool[dt.Columns.Count];
1090+
bool hasKey = false;
1091+
1092+
// set up primary key as unique key list
1093+
// do this prior to general metadata loop to favor the primary key
1094+
if (null != dt.PrimaryKey && 0 < dt.PrimaryKey.Length)
1095+
{
1096+
foreach (DataColumn col in dt.PrimaryKey)
1097+
{
1098+
keyCols[col.Ordinal] = true;
1099+
hasKey = true;
1100+
}
1101+
}
1102+
1103+
for (int i = 0; i < dt.Columns.Count; i++)
1104+
{
1105+
fields.Add(MSS.MetaDataUtilsSmi.SmiMetaDataFromDataColumn(dt.Columns[i], dt));
1106+
1107+
// DataColumn uniqueness is only for a single column, so don't add
1108+
// more than one. (keyCols.Count first for assumed minimal perf benefit)
1109+
if (!hasKey && dt.Columns[i].Unique)
1110+
{
1111+
keyCols[i] = true;
1112+
hasKey = true;
1113+
}
1114+
}
1115+
1116+
// Add unique key property, if any found.
1117+
if (hasKey)
1118+
{
1119+
props = new SmiMetaDataPropertyCollection();
1120+
props[MSS.SmiPropertySelector.UniqueKey] = new MSS.SmiUniqueKeyProperty(new List<bool>(keyCols));
1121+
}
1122+
}
1123+
else if (value is SqlDataReader)
10821124
{
10831125
fields = new List<MSS.SmiExtendedMetaData>(((SqlDataReader)value).GetInternalSmiMetaData());
10841126
if (fields.Count <= 0)

src/System.Data.SqlClient/src/System/Data/SqlClient/SqlUtil.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,14 @@ internal static Exception BulkLoadPendingOperation()
533533
{
534534
return ADP.InvalidOperation(SR.GetString(SR.SQL_BulkLoadPendingOperation));
535535
}
536-
536+
internal static Exception InvalidTableDerivedPrecisionForTvp(string columnName, byte precision)
537+
{
538+
return ADP.InvalidOperation(SR.GetString(SR.SqlParameter_InvalidTableDerivedPrecisionForTvp, precision, columnName, System.Data.SqlTypes.SqlDecimal.MaxPrecision));
539+
}
540+
internal static Exception InvalidColumnMaxLength(string columnName, long maxLength)
541+
{
542+
return ADP.Argument(SR.GetString(SR.SqlProvider_InvalidDataColumnMaxLength, columnName, maxLength));
543+
}
537544
//
538545
// transactions.
539546
//

0 commit comments

Comments
 (0)