@@ -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}
0 commit comments