Today we will implement the DbDataReader class which enforces a contract that defines how to make data available to applications.
- First Post: Active Directory Data Access Layer
- Second Post: Active Directory Connection Strings
- Third Post: AdConnection: Enforcing Active Directory Communication Best Practices
- Fourth Post: AdCommandTextParser: Parsing SQL Statements
- Fifth Post: AdCommand: Running Active Directory Queries
- Sixth Post: AdDataReader: Providing Controlled Access to AD Values
- Seventh Post: AdDataAdapter: Managing Active Directory Data
- Eighth Post: AD Query: Putting it All Together
AdDataReader Class Diagram
The AdDataReader class has several properties that assist with data conversion. Unfortunately with Active Directory attributes there are some attribute types that must continue to be manually converted. Take for example the GetByte method. There are a couple of different ways to convert the value to byte. Currently this class hasn’t been configured to use the schema to correctly decode the values.
AdDataReader uses the AdConvert class to help with the data conversion. AdConvert can be used to convert the object value as necessary. Or a custom routine can be constructed to convert the object value from the AdDataReader when necessary.
The AdSchemaBuilder class assists with creating the schema DataTable for the AdDataReader. AdSchemaBuilder is also used by the AdCommand to identify when an attribute is a multivalued attribute or a single-valued attribute.
AdDataReader uses both AdCommand and AdConnection to manage retrieving the data from Active Directory. When batched commands are executed the NextResult method can be used to advance to the next result set. The Read method continues to provide access to the next available record returning false when there are no more records to read.
With the AdConnection, AdCommand and AdDataReader classes we are able to use standard data access interfaces to manage Active Directory.
AdConvert
Namespace Data.ActiveDirectory ''' <summary> ''' Converts an ADSI base data type to another base data type. ''' </summary> ''' <remarks></remarks> Public Class AdConvert ''' <summary> ''' Gets the value of the specified column as a Boolean. ''' </summary> ''' <param name="value"> ''' The value to convert to Boolean. ''' </param> ''' <remarks></remarks> Public Shared Function ToBoolean(ByVal value As Object) As Boolean If IsNumeric(value) Then If value.ToString.IndexOf(".") >= 0 Then Return Convert.ToBoolean(Convert.ToDecimal(value)) Else Return Convert.ToBoolean(Convert.ToInt64(value)) End If Else Return Convert.ToBoolean(value) End If End Function ''' <summary> ''' Gets the value of the specified column as a byte. ''' </summary> ''' <param name="value"> ''' The value to convert to Byte. ''' </param> ''' <remarks></remarks> Public Shared Function ToByte(ByVal value As Object) As Byte Return Convert.ToByte(value) End Function ''' <summary> ''' Converts a character string to a byte array (e.g. "abcdefg" to [97,98,99,100,101,102,103]). ''' </summary> ''' <param name="value">The value to convert to a Byte array.</param> ''' <returns>Byte Array</returns> ''' <remarks></remarks> Public Shared Function ToByteArray(ByVal value As Object) As Byte() Dim s As Char() = value.ToString.ToCharArray Dim b(s.Length - 1) As Byte For i As Integer = 0 To s.Length - 1 b(i) = System.Convert.ToByte(s(i)) Next Return b End Function ''' <summary> ''' Converts a hexadecimal value to a byte array. ''' </summary> ''' <param name="hexString">The hex string value to convert a Byte array.</param> ''' <returns>Hex byte array.</returns> ''' <remarks></remarks> Public Shared Function HexToByteArray(ByVal hexString As String) As Byte() If hexString.Length Mod 2 <> 0 Then Throw New ApplicationException("Hex string must be multiple of 2 in length") End If Dim byteCount As Integer = System.Convert.ToInt32(hexString.Length / 2) Dim byteValues(byteCount) As Byte For i As Integer = 0 To byteCount - 1 byteValues(i) = System.Convert.ToByte(hexString.Substring(i * 2, 2), 16) Next Return byteValues End Function ''' <summary> ''' Gets the value of the specified column as a single character. ''' </summary> ''' <param name="value"> ''' The value to convert to Char. ''' </param> ''' <remarks></remarks> Public Shared Function ToChar(ByVal value As Object) As Char If value IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(value.ToString) Then value = value.ToString.Trim.Substring(0, 1) Return Convert.ToChar(value) End Function ''' <summary> ''' Reads a stream of characters from the specified column, ''' starting at location indicated by dataIndex, into the buffer, ''' starting at the location indicated by bufferIndex. ''' </summary> ''' <param name="value"> ''' The value to convert to an array of Char. ''' </param> ''' <param name="dataOffset"> ''' The index within the row from which to begin the read operation. ''' </param> ''' <param name="buffer"> ''' The buffer into which to copy the data. ''' </param> ''' <param name="bufferOffset"> ''' The index with the buffer to which the data will be copied. ''' </param> ''' <param name="length"> ''' The maximum number of characters to read. ''' </param> ''' <returns> ''' The actual number of characters read. ''' </returns> ''' <remarks></remarks> Public Shared Function ToChars(ByVal value As Object, ByVal dataOffset As Long, ByVal buffer() As Char, ByVal bufferOffset As Integer, ByVal length As Integer) As Long If length < 0 Then Throw New IndexOutOfRangeException("Length is out of range.") If buffer IsNot Nothing Then If bufferOffset < 0 OrElse bufferOffset >= buffer.Length Then Throw New IndexOutOfRangeException("bufferOffset is out of range.") ElseIf (length + bufferOffset) >= buffer.Length Then Throw New IndexOutOfRangeException("Length + bufferOffset is out of range.") End If End If Dim holdCharArray() As Char = value.ToString.ToCharArray Dim charactersRead As Long If dataOffset >= holdCharArray.Length Then 'Begin reading outside bounds of char array charactersRead = 0 ElseIf dataOffset + length >= holdCharArray.Length Then 'Stop reading outside bounds of char array charactersRead = holdCharArray.Length - dataOffset - 1 Else 'Reading occurs within bounds of char array charactersRead = length End If If charactersRead > 0 Then Array.Copy(holdCharArray, dataOffset, buffer, bufferOffset, charactersRead) End If Return charactersRead End Function ''' <summary> ''' Gets the value of the specified column as a DateTime object. ''' </summary> ''' <param name="value"> ''' The value to convert to DateTime. ''' </param> ''' <remarks></remarks> Public Shared Function ToDateTime(ByVal value As Object) As Date If TypeOf value Is DateTime Then Return CType(value, DateTime) ElseIf IsNumeric(value) Then Return DateTime.FromFileTime(Convert.ToInt64(value)) Else Try Return Convert.ToDateTime(value) Catch ex As Exception Throw New InvalidCastException(String.Format("Cannot convert {0} to DateTime", value)) End Try End If End Function ''' <summary> ''' Gets the value of the specified column as a Decimal object. ''' </summary> ''' <param name="value"> ''' The value to convert to Decimal. ''' </param> ''' <remarks></remarks> Public Shared Function ToDecimal(ByVal value As Object) As Decimal If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToDecimal(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to decimal", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as a double-precision floating point number. ''' </summary> ''' <param name="value"> ''' The value to convert to Double. ''' </param> ''' <remarks></remarks> Public Shared Function ToDouble(ByVal value As Object) As Double If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToDouble(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to double", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as a single-precision floating point number. ''' </summary> ''' <param name="value"> ''' The value to convert to Single. ''' </param> ''' <remarks></remarks> Public Shared Function ToFloat(ByVal value As Object) As Single If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToSingle(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to single", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as a globally-unique identifier (GUID). ''' </summary> ''' <param name="value"> ''' The value to convert to Guid. ''' </param> ''' <remarks></remarks> Public Shared Function ToGuid(ByVal value As Object) As System.Guid Dim strValue As String = Nothing Try strValue = ToString(value) Return Guid.Parse(strValue) Catch ex As Exception If String.IsNullOrWhiteSpace(strValue) Then Throw New InvalidCastException(String.Format("Cannot convert {0} to Guid", value.ToString)) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to Guid", strValue)) End If End Try End Function ''' <summary> ''' Gets the value of the specified column as a 16-bit signed integer. ''' </summary> ''' <param name="value"> ''' The value to convert to Short. ''' </param> ''' <remarks></remarks> Public Shared Function ToInt16(ByVal value As Object) As Short If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToInt16(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to Int16", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as a 32-bit signed integer. ''' </summary> ''' <param name="value"> ''' The value to convert to Integer. ''' </param> ''' <remarks></remarks> Public Shared Function ToInt32(ByVal value As Object) As Integer If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToInt32(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to Int32", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as a 64-bit signed integer. ''' </summary> ''' <param name="value"> ''' The value to convert to Long. ''' </param> ''' <remarks></remarks> Public Shared Function ToInt64(ByVal value As Object) As Long If value Is Nothing OrElse IsNumeric(value) Then Return Convert.ToInt64(value) Else Throw New InvalidCastException(String.Format("Cannot convert {0} to Int64", value)) End If End Function ''' <summary> ''' Gets the value of the specified column as an instance of String. ''' </summary> ''' <param name="value"> ''' The value to convert to String. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Public Overloads Shared Function ToString(ByVal value As Object) As String Try If value Is Nothing Then Return String.Empty ElseIf TypeOf value Is System.Byte Then Dim _ByteArray() As System.Byte = CType(value, System.Byte()) Return System.BitConverter.ToString(_ByteArray, 0, _ByteArray.Length) ElseIf value.GetType.FullName = "System.__ComObject" Then 'ComObject type conversion not supported. Throw New AdException("Cannot convert type __ComObject") Else Return Convert.ToString(value) End If Catch ex As Exception Throw New InvalidCastException(String.Format("Cannot convert {0} to string", value), ex) End Try End Function End Class End Namespace
AdSchemaBuilder
Namespace Data.ActiveDirectory ''' <summary> ''' Constructs the schema for the specified class name. ''' </summary> ''' <remarks></remarks> Public Class AdSchemaBuilder #Region "Constructor Section" Private _Connection As AdConnection Public Sub New(ByVal connection As AdConnection) _Connection = connection End Sub #End Region #Region "Property Section" Private ReadOnly Property DomainController As String Get Dim connectionBuilder As New AdConnectionStringBuilder(_Connection.ConnectionString) If String.IsNullOrWhiteSpace(connectionBuilder.DomainController) Then Dim domainName As String = connectionBuilder.DomainName If String.IsNullOrWhiteSpace(domainName) Then domainName = connectionBuilder.DefaultDomainName End If Return connectionBuilder.GetDefaultDomainController(domainName) Else Return connectionBuilder.DomainController End If End Get End Property #End Region #Region "Exposed Behavior" ''' <summary> ''' Get the list of multivalued mandatory and optional properties for the specified className from the schema. ''' </summary> ''' <param name="className"> ''' The className to query. ''' </param> ''' <returns> ''' Returns a string array of mandatory and optional property names for the specified className. ''' </returns> ''' <remarks></remarks> Public Shared Function GetMultivaluedNames(ByVal className As String) As String() Dim currentSchema As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema = Nothing Dim schemaClass As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass = Nothing Dim propertyNames() As String Try currentSchema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetCurrentSchema schemaClass = currentSchema.FindClass(className) Dim mandatoryProperties As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaPropertyCollection = schemaClass.MandatoryProperties Dim optionalProperties As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaPropertyCollection = schemaClass.OptionalProperties 'Construct Property Array to Store Mandatory and Optional Properties ReDim propertyNames(mandatoryProperties.Count + optionalProperties.Count - 1) Dim propertyIndex As Integer = 0 'Get Mandatory Properties For Each adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty In mandatoryProperties If Not adProperty.IsSingleValued Then propertyNames(propertyIndex) = adProperty.Name propertyIndex += 1 End If Next 'Get Optional Properties For Each adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty In optionalProperties If Not adProperty.IsSingleValued Then propertyNames(propertyIndex) = adProperty.Name propertyIndex += 1 End If Next Catch ex As Exception Throw New AdException("An error occurred while getting the property names from the schema for class " & className & ".", ex) Finally If schemaClass IsNot Nothing Then schemaClass.Dispose() If currentSchema IsNot Nothing Then currentSchema.Dispose() schemaClass = Nothing currentSchema = Nothing End Try Return propertyNames End Function ''' <summary> ''' Get the list of mandatory and optional properties for the specified className from the schema. ''' </summary> ''' <param name="className"> ''' The className to query. ''' </param> ''' <returns> ''' Returns a string array of mandatory and optional property names for the specified className. ''' </returns> ''' <remarks></remarks> Public Shared Function GetPropertyNames(ByVal className As String) As String() Dim currentSchema As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema = Nothing Dim schemaClass As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass = Nothing Dim propertyNames() As String Try currentSchema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetCurrentSchema schemaClass = currentSchema.FindClass(className) Dim mandatoryProperties As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaPropertyCollection = schemaClass.MandatoryProperties Dim optionalProperties As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaPropertyCollection = schemaClass.OptionalProperties 'Construct Property Array to Store Mandatory and Optional Properties ReDim propertyNames(mandatoryProperties.Count + optionalProperties.Count - 1) Dim propertyIndex As Integer = 0 'Get Mandatory Properties For Each adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty In mandatoryProperties propertyNames(propertyIndex) = adProperty.Name propertyIndex += 1 Next 'Get Optional Properties For Each adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty In optionalProperties propertyNames(propertyIndex) = adProperty.Name propertyIndex += 1 Next Catch ex As Exception Throw New AdException("An error occurred while getting the property names from the schema for class " & className & ".", ex) Finally If schemaClass IsNot Nothing Then schemaClass.Dispose() If currentSchema IsNot Nothing Then currentSchema.Dispose() schemaClass = Nothing currentSchema = Nothing End Try Return propertyNames End Function ''' <summary> ''' Gets the schema for the specified className. ''' </summary> ''' <param name="className"> ''' The name of the class to get the schema. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Public Function Build(ByVal className As String) As System.Data.DataTable Return Build(className, Nothing) End Function ''' <summary> ''' Gets the schema for the specified className. ''' </summary> ''' <param name="className"> ''' The name of the class to get the schema. ''' </param> ''' <param name="propertyNames"> ''' A list of attributes to add to the schema. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Public Function Build(ByVal className As String, ByVal ParamArray propertyNames() As String) As System.Data.DataTable If propertyNames Is Nothing OrElse propertyNames.Length = 0 Then propertyNames = GetPropertyNames(className) End If Dim schemaTable As New DataTable("SchemaTable") schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture With schemaTable.Columns .Add("ColumnName", GetType(String)) 'Column Name - attributeName .Add("ColumnOrdinal", GetType(Integer)) 'Column Index .Add("ColumnSize", GetType(Integer)) 'Column Size .Add("NumericPrecision", GetType(Short)) '-1 if not number. .Add("NumericScale", GetType(Short)) '-1 if not number. Precesion right of the decimal point. .Add("DataType", GetType(Type)) '.Net Data Type .Add("ProviderType", GetType(Type)) '-1 for COM Object else same as data type .Add("IsLong", GetType(Boolean)) .Add("AllowDBNull", GetType(Boolean)) .Add("IsReadOnly", GetType(Boolean)) .Add("IsUnique", GetType(Boolean)) .Add("IsKey", GetType(Boolean)) .Add("IsAutoIncrement", GetType(Boolean)) 'Always false .Add("IsSingleValued", GetType(Boolean)) .Add("IsMultiValued", GetType(Boolean)) .Add("IsTupleIndexed", GetType(Boolean)) .Add("IsMandatory", GetType(Boolean)) .Add("IsOptional", GetType(Boolean)) .Add("Link", GetType(String)) .Add("LinkID", GetType(Integer)) .Add("Oid", GetType(String)) .Add("RangeLower", GetType(Integer)) .Add("RangeUpper", GetType(Integer)) .Add("BaseSchemaName", GetType(String)) 'empty string .Add("BaseCatalogName", GetType(String)) 'domain controller .Add("BaseTableName", GetType(String)) 'objectClass .Add("BaseColumnName", GetType(String)) 'attributeName End With Dim domainController As String = Me.DomainController Dim currentSchema As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema = Nothing Dim schemaClass As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass = Nothing Try currentSchema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetCurrentSchema schemaClass = currentSchema.FindClass(className) AddPropertiesToSchema(schemaTable, schemaClass.CommonName, True, schemaClass.MandatoryProperties, propertyNames) AddPropertiesToSchema(schemaTable, schemaClass.CommonName, False, schemaClass.OptionalProperties, propertyNames) Catch ex As Exception Throw New AdException("An error occurred while getting the schema for class " & className & ".", ex) Finally If schemaClass IsNot Nothing Then schemaClass.Dispose() If currentSchema IsNot Nothing Then currentSchema.Dispose() schemaClass = Nothing currentSchema = Nothing End Try Return schemaTable End Function ''' <summary> ''' Appends the schema for the specified className to the provided schema. ''' </summary> ''' <param name="className"> ''' The name of the class to get the schema. ''' </param> ''' <param name="schema"> ''' The schema to append the classNames results. ''' </param> ''' <returns> ''' The combined schema results. ''' </returns> ''' <remarks></remarks> Public Function AppendSchema(ByVal className As String, ByVal schema As System.Data.DataTable) As System.Data.DataTable Return AppendSchema(className, schema, Nothing) End Function ''' <summary> ''' Appends the schema for the specified className to the provided schema. ''' </summary> ''' <param name="className"> ''' The name of the class to get the schema. ''' </param> ''' <param name="schema"> ''' The schema to append the classNames results. ''' </param> ''' <param name="propertyNames"> ''' A list of attributes to add to the schema. ''' </param> ''' <returns> ''' The combined schema results. ''' </returns> ''' <remarks></remarks> Public Function AppendSchema(ByVal className As String, ByVal schema As System.Data.DataTable, ByVal ParamArray propertyNames() As String) As System.Data.DataTable Dim appendTable As System.Data.DataTable = Build(className, propertyNames) If schema Is Nothing Then schema = appendTable Else 'Verify There is a Name If String.IsNullOrWhiteSpace(schema.TableName) Then schema.TableName = appendTable.TableName End If 'Verify Column Names Match For Each appendCol As System.Data.DataColumn In appendTable.Columns If Not schema.Columns.Contains(appendCol.ColumnName) Then schema.Columns.Add(appendCol.ColumnName) End If Next 'Copy rows to schema if they do not already exist in the schema For Each appendRow As System.Data.DataRow In appendTable.Rows Dim dataFound As Boolean = False For Each schemaRow As System.Data.DataRow In schema.Rows If String.Compare(schemaRow.Item("ColumnName").ToString, appendRow.Item("ColumnName").ToString, True) = 0 Then dataFound = True Exit For End If Next If Not dataFound Then schema.Rows.Add(appendRow) Next End If Return schema End Function #End Region #Region "MetaData Helpers" Private Sub AddPropertiesToSchema(ByVal schema As DataTable, ByVal classCommonName As String, ByVal isMandatory As Boolean, ByVal properties As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaPropertyCollection, ByVal ParamArray propertyNames() As String) For Each adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty In properties For Each propertyName As String In propertyNames If String.Compare(adProperty.Name, propertyName, True) = 0 Then Dim schemaRow As System.Data.DataRow = schema.NewRow Dim adSyntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax = adProperty.Syntax With adProperty schemaRow.Item("IsMandatory") = isMandatory schemaRow.Item("IsOptional") = (Not isMandatory) schemaRow.Item("ColumnName") = .Name schemaRow.Item("ColumnOrdinal") = schema.Rows.Count schemaRow.Item("ColumnSize") = GetColumnSize(adSyntax) schemaRow.Item("NumericPrecision") = GetNumericPrecision(adSyntax) schemaRow.Item("NumericScale") = GetNumericScale(adSyntax) schemaRow.Item("DataType") = GetDataType(adSyntax) schemaRow.Item("ProviderType") = GetProviderType(adSyntax) schemaRow.Item("IsLong") = GetIsLong(adSyntax) schemaRow.Item("AllowDBNull") = GetAllowDBNull(adProperty) schemaRow.Item("IsReadOnly") = False schemaRow.Item("IsUnique") = GetIsUnique(propertyName) schemaRow.Item("IsKey") = GetIsKey(adProperty) schemaRow.Item("IsAutoIncrement") = False schemaRow.Item("IsSingleValued") = .IsSingleValued schemaRow.Item("IsMultiValued") = Not .IsSingleValued schemaRow.Item("IsTupleIndexed") = .IsTupleIndexed If .Link IsNot Nothing AndAlso .Link.Name IsNot Nothing Then schemaRow.Item("Link") = .Link.Name If .LinkId IsNot Nothing Then schemaRow.Item("LinkID") = .LinkId schemaRow.Item("Oid") = .Oid If .RangeLower IsNot Nothing Then schemaRow.Item("RangeLower") = .RangeLower If .RangeUpper IsNot Nothing Then schemaRow.Item("RangeUpper") = .RangeUpper schemaRow.Item("BaseSchemaName") = String.Empty schemaRow.Item("BaseCatalogName") = DomainController schemaRow.Item("BaseTableName") = classCommonName schemaRow.Item("BaseColumnName") = .CommonName End With schema.Rows.Add(schemaRow) End If Next Next End Sub ''' <summary> ''' Gets a boolean value that indicates if the specified attribute is unique. ''' </summary> ''' <param name="attributeName"></param> ''' <returns></returns> ''' <remarks> ''' A return value of false does not mean that the attribute is not unique. ''' There is no way to identify if an attribute is unique. Therefore this ''' routine checks the attributeName against a known list of unique names. ''' </remarks> Private Function GetIsUnique(ByVal attributeName As String) As Boolean Dim uniqueNames() As String = {"sAMAccountName", "userPrincipalName", "distinguishedName", "objectGuid", "objectSid", "sIDHistory"} For Each uniqueName As String In uniqueNames If String.Compare(uniqueName, attributeName, True) = 0 Then Return True End If Next Return False End Function ''' <summary> ''' Gets a value that indicates if the attribute can contain a null value. ''' </summary> ''' <param name="adProperty"> ''' The attribute to test for nullability. ''' </param> ''' <returns> ''' Returns true if the attribute can store an empty value, false otherwise. ''' </returns> ''' <remarks></remarks> Private Function GetAllowDBNull(ByVal adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty) As Boolean Return (Not adProperty.IsIndexed AndAlso Not adProperty.IsIndexedOverContainer) End Function ''' <summary> ''' Gets a value that indicates if the attribute is a primary key attribute. ''' </summary> ''' <param name="adProperty"> ''' The attribute to test for primary key enforcement. ''' </param> ''' <returns> ''' Returns true if the key is a known primary key, false otherwise. ''' </returns> ''' <remarks></remarks> Private Function GetIsKey(ByVal adProperty As System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty) As Boolean Return ((adProperty.IsIndexed OrElse adProperty.IsIndexedOverContainer) AndAlso GetIsUnique(adProperty.Name)) End Function ''' <summary> ''' Gets the column size for the specified syntax. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Private Function GetColumnSize(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Integer Dim dataType As Type = GetDataType(syntax) If dataType = GetType(String) Then 'The maximum number of characters in a string Return Integer.MaxValue ElseIf dataType = GetType(Int32) Then 'The number of bytes is the length Return 32 ElseIf dataType = GetType(Int64) Then 'The number of bytes is the length Return 64 Else Return 0 End If End Function ''' <summary> ''' Gets the numeric precision for the specified syntax. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns> ''' Returns the numeric precision value or DBNull if precision does not apply. ''' </returns> ''' <remarks> ''' Precision is the number of placements to the left of the decimal point. ''' </remarks> Private Function GetNumericPrecision(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Integer Dim dataType As Type = GetDataType(syntax) If dataType = GetType(Int32) Then Return Int32.MaxValue.ToString.Length ElseIf dataType = GetType(Int64) Then Return Int64.MaxValue.ToString.Length Else Return -1 End If End Function ''' <summary> ''' Gets the numeric scale for the specified syntax. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns> ''' Returns the numeric scale value or DBNull if scale does not apply. ''' </returns> ''' <remarks> ''' Scale is the number of placements to the right of the decimal point. ''' </remarks> Private Function GetNumericScale(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Integer Dim dataType As Type = GetDataType(syntax) If dataType = GetType(Int32) Then Return 0 ElseIf dataType = GetType(Int64) Then Return 0 Else Return -1 End If End Function ''' <summary> ''' Gets the dot net equivalent of the provider data type. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Private Function GetDataType(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Type Select Case syntax Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.AccessPointDN : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Bool : Return GetType(Boolean) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.CaseExactString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.CaseIgnoreString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DirectoryString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DN : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DNWithBinary : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DNWithString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Enumeration : Return GetType(Int32) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.GeneralizedTime : Return GetType(DateTime) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.IA5String : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Int : Return GetType(Int32) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Int64 : Return GetType(Int64) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.NumericString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.OctetString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Oid : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.ORName : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.PresentationAddress : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.PrintableString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.ReplicaLink : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.SecurityDescriptor : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Sid : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.UtcTime : Return GetType(DateTime) Case Else : Return GetType(String) End Select End Function ''' <summary> ''' Gets the provider data type. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Private Function GetProviderType(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Type Select Case syntax Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.AccessPointDN : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Bool : Return GetType(Boolean) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.CaseExactString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.CaseIgnoreString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DirectoryString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DN : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DNWithBinary : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.DNWithString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Enumeration : Return GetType(Int32) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.GeneralizedTime : Return GetType(DateTime) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.IA5String : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Int : Return GetType(Int32) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Int64 : Return GetType(Int64) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.NumericString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.OctetString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Oid : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.ORName : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.PresentationAddress : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.PrintableString : Return GetType(String) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.ReplicaLink : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.SecurityDescriptor : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Sid : Return GetType(Byte()) Case DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.UtcTime : Return GetType(DateTime) Case Else : Return GetType(String) End Select End Function ''' <summary> ''' Gets a value that indicates if the syntax is a long data type. ''' </summary> ''' <param name="syntax"> ''' The property syntax type. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Private Function GetIsLong(ByVal syntax As System.DirectoryServices.ActiveDirectory.ActiveDirectorySyntax) As Boolean If syntax = DirectoryServices.ActiveDirectory.ActiveDirectorySyntax.Int64 Then Return True Return False End Function #End Region End Class End Namespace
AdDataReader
Namespace Data.ActiveDirectory ''' <summary> ''' Reads a forward-only stream of rows from an Active Directory data source. ''' </summary> ''' <remarks></remarks> Public Class AdDataReader Inherits System.Data.Common.DbDataReader #Region "Definition Section" Private _Command As AdCommand Private _Connection As AdConnection Private _CommandBehavior As System.Data.CommandBehavior Private _Searcher As System.DirectoryServices.DirectorySearcher Private _CurrentSet As System.DirectoryServices.SearchResultCollection Private _CurrentRow As System.DirectoryServices.SearchResult Private _CurrentSetIndex As Integer Private _CurrentRowIndex As Integer Private _RecordsAffected As Integer Private _IsClosed As Boolean = False Private _RecordSetParser As AdCommandTextParser Private _Schema As System.Data.DataTable Private _MultiValuedAttributes As System.Collections.Specialized.StringCollection #End Region #Region "Constructor Section" ''' <summary> ''' Instantiates a new AdDataReader instance. ''' </summary> ''' <param name="command"> ''' The command class that created the AdDataReader instance. ''' </param> ''' <param name="behavior"> ''' Specifies how the AdDataReader should behave when finished reading data. ''' </param> ''' <remarks> ''' To get an AdDataReader you must use AdCommand.ExecuteReader. ''' </remarks> Friend Sub New(ByVal command As AdCommand, ByVal behavior As System.Data.CommandBehavior) _CurrentSetIndex = -1 _HasRows = False _Command = command _Connection = DirectCast(command.Connection, AdConnection) _CommandBehavior = behavior For Each parser As AdCommandTextParser In _Command.InnerCommandSets If parser.OrderBy.Count > 1 Then _Connection.OnInfoMessage("WARNING! AdDataReader supports sorting on only the first column. Multiple sorts are not supported.") End If Next 'Get First Recordset NextResult() End Sub ''' <summary> ''' Returns the index to the current recordset. ''' </summary> ''' <remarks></remarks> Public ReadOnly Property DatasetIndex As Integer Get Return _CurrentSetIndex End Get End Property #End Region #Region "Helper Routines" ''' <summary> ''' Test condition to see if class supports given CommandBehavior. ''' </summary> ''' <param name="condition"> ''' The command behavior to test for support. ''' </param> ''' <returns> ''' Returns true if the specified behavior is supported, false otherwise. ''' </returns> ''' <remarks></remarks> Private Function IsCommandBehavior(ByVal condition As System.Data.CommandBehavior) As Boolean Return (condition = (condition And _CommandBehavior)) End Function ''' <summary> ''' Throws an InvalidOperationiException when their are no results. ''' </summary> ''' <remarks></remarks> Private Sub ThrowErrorOnInvalidReadNext() If _Command.InnerCommandSets Is Nothing _ OrElse _Command.InnerCommandSets.Count = 0 Then Throw New InvalidOperationException("Attempted to read an invalid record set.") End If End Sub ''' <summary> ''' Throws an InvalidOperationiException when the current row is null. ''' </summary> ''' <remarks></remarks> Private Sub ThrowErrorOnInvalidRead() If _CurrentRow Is Nothing Then Throw New InvalidOperationException("Attempted to read an invalid record.") End If End Sub ''' <summary> ''' Get a value that indicates that the field is capable of storing an array of values. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <returns> ''' Returns true when the specified field stores an array of values, false otherwise. ''' </returns> ''' <remarks></remarks> Public Function IsFieldMultiValued(ByVal ordinal As Integer) As Boolean Return IsFieldMultiValued(GetName(ordinal)) End Function ''' <summary> ''' Get a value that indicates that the field is capable of storing an array of values. ''' </summary> ''' <param name="name"> ''' The attribute name. ''' </param> ''' <returns> ''' Returns true when the specified field stores an array of values, false otherwise. ''' </returns> ''' <remarks></remarks> Public Function IsFieldMultiValued(ByVal name As String) As Boolean 'Track all Multi-valued attributes If _MultiValuedAttributes Is Nothing Then If _Schema Is Nothing Then _Schema = GetSchemaTable() _MultiValuedAttributes = New System.Collections.Specialized.StringCollection For Each row As DataRow In _Schema.Rows If Not row.IsNull("IsMultiValued") _ AndAlso row.Item("IsMultiValued").ToString = "True" Then _MultiValuedAttributes.Add(row.Item("ColumnName").ToString) End If Next End If 'Identify if the specified field name is a multi-valued attribute Dim innerName As String = name.Trim For Each attributeName As String In _MultiValuedAttributes If String.Compare(innerName, attributeName, True) = 0 Then Return True End If Next Return False End Function ''' <summary> ''' Use Range Retrieval to get all the values assigned to a multi-valued attribute. ''' </summary> ''' <param name="adsPath"> ''' Reference to current object containing the attribute to use range retrieval to query. ''' </param> ''' <param name="attributeName"> ''' The name of the attribute to query. ''' </param> ''' <remarks> ''' Avoid using the routine if at all possible as there will be a negative performance impact. ''' Use of this routine requires a secondary DirectorySearcher object and multiple calls to ''' get each object's values. ''' References: http://msdn.microsoft.com/en-us/library/ms180907(v=vs.80).aspx#Y192 ''' </remarks> Private Function RangeRetrieval(ByVal adsPath As Path, ByVal attributeName As String) As Object() Dim searcher As DirectoryServices.DirectorySearcher = Nothing Dim objList As New System.Collections.ArrayList Try Dim pageSize As Integer = _Connection.PageSize Dim pageStartIndex As Integer = 0 Dim pageStopIndex As Integer = pageStartIndex + pageSize - 1 Dim onLastPage As Boolean = False Dim allValuesRetrieved As Boolean = False Dim filter As String = String.Format("(distinguishedName={0})", adsPath.DnValue) searcher = _Connection.CreateDirectorySearcher(filter, adsPath.Parent.ToString, DirectoryServices.SearchScope.OneLevel) Do Dim attributeWithRange As String If onLastPage Then attributeWithRange = String.Format("{0};range={1}-*", attributeName, pageStartIndex) Else attributeWithRange = String.Format("{0};range={1}-{2}", attributeName, pageStartIndex, pageStopIndex) End If searcher.PropertiesToLoad.Clear() searcher.PropertiesToLoad.Add(attributeWithRange) Dim results As DirectoryServices.SearchResult = searcher.FindOne If results.Properties.Contains(attributeWithRange) Then For Each itemValue As Object In results.Properties(attributeWithRange) objList.Add(itemValue) Next If onLastPage Then allValuesRetrieved = True Else pageStartIndex = pageStopIndex + 1 pageStopIndex = pageStartIndex + pageSize - 1 End If Else onLastPage = True End If Loop Until allValuesRetrieved Catch ex As Exception Throw ex Finally If searcher IsNot Nothing Then _Connection.FreeResource(searcher) End Try 'Copy list into array If objList.Count > 0 Then Dim arrayValues(objList.Count - 1) As Object objList.CopyTo(arrayValues, 0) Return arrayValues Else Return Nothing End If End Function #End Region #Region "Implement AdDataReader" Public Overrides Sub Close() If Not Me.IsClosed Then _IsClosed = True If _Searcher IsNot Nothing Then _Connection.FreeResource(_Searcher) _Searcher = Nothing End If If _Command IsNot Nothing _ AndAlso _Connection IsNot Nothing _ AndAlso IsCommandBehavior(System.Data.CommandBehavior.CloseConnection) Then _Connection.Close() End If End If End Sub ''' <summary> ''' Gets a value indicating the depth of nesting for the current row. ''' </summary> ''' <remarks></remarks> Public Overrides ReadOnly Property Depth As Integer Get Return 0 End Get End Property ''' <summary> ''' Gets the number of columns in the current row. ''' </summary> ''' <remarks></remarks> Public Overrides ReadOnly Property FieldCount As Integer Get Return _RecordSetParser.Parameters.Count End Get End Property ''' <summary> ''' Gets the value of the specified column as a Boolean. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetBoolean(ByVal ordinal As Integer) As Boolean Return AdConvert.ToBoolean(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a byte. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetByte(ByVal ordinal As Integer) As Byte Return AdConvert.ToByte(Item(ordinal)) End Function ''' <summary> ''' Reads a stream of bytes from the specified column, starting at location ''' indicated by dataOffset, into the buffer, starting at the location ''' indicated by bufferOffset. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <param name="dataOffset"> ''' The index within the row from which to begin the read operation. ''' </param> ''' <param name="buffer"> ''' The buffer into which to copy the data. ''' </param> ''' <param name="bufferOffset"> ''' The index with the buffer to which the data will be copied. ''' </param> ''' <param name="length"> ''' The maximum number of characters to read. ''' </param> ''' <returns> ''' The actual number of bytes read. ''' </returns> ''' <remarks></remarks> Public Overrides Function GetBytes(ByVal ordinal As Integer, ByVal dataOffset As Long, ByVal buffer() As Byte, ByVal bufferOffset As Integer, ByVal length As Integer) As Long Dim fieldType As Type = Me.GetFieldType(ordinal) If fieldType Is GetType(String) Then buffer = AdConvert.ToByteArray(Item(ordinal)) Else buffer = AdConvert.HexToByteArray(Item(ordinal).ToString) End If Return 0 End Function ''' <summary> ''' Gets the value of the specified column as a single character. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetChar(ByVal ordinal As Integer) As Char Return AdConvert.ToChar(Item(ordinal)) End Function ''' <summary> ''' Reads a stream of characters from the specified column, ''' starting at location indicated by dataIndex, into the buffer, ''' starting at the location indicated by bufferIndex. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <param name="dataOffset"> ''' The index within the row from which to begin the read operation. ''' </param> ''' <param name="buffer"> ''' The buffer into which to copy the data. ''' </param> ''' <param name="bufferOffset"> ''' The index with the buffer to which the data will be copied. ''' </param> ''' <param name="length"> ''' The maximum number of characters to read. ''' </param> ''' <returns> ''' The actual number of characters read. ''' </returns> ''' <remarks></remarks> Public Overrides Function GetChars(ByVal ordinal As Integer, ByVal dataOffset As Long, ByVal buffer() As Char, ByVal bufferOffset As Integer, ByVal length As Integer) As Long Return AdConvert.ToChars(Item(ordinal), dataOffset, buffer, bufferOffset, length) End Function ''' <summary> ''' Gets the name of the data type of the specified column. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <returns> ''' A string representing the name of the data type. ''' </returns> ''' <remarks></remarks> Public Overrides Function GetDataTypeName(ByVal ordinal As Integer) As String Return GetFieldType(ordinal).Name End Function ''' <summary> ''' Gets the value of the specified column as a DateTime object. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetDateTime(ByVal ordinal As Integer) As Date Return AdConvert.ToDateTime(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a Decimal object. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetDecimal(ByVal ordinal As Integer) As Decimal Return AdConvert.ToDecimal(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a double-precision floating point number. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetDouble(ByVal ordinal As Integer) As Double Return AdConvert.ToDouble(Item(ordinal)) End Function ''' <summary> ''' Gets an IEnumerator that can be used to iterate through the rows in the data reader. ''' </summary> ''' <remarks></remarks> Public Overrides Function GetEnumerator() As System.Collections.IEnumerator Return New System.Data.Common.DbEnumerator(Me, IsCommandBehavior(System.Data.CommandBehavior.CloseConnection)) End Function ''' <summary> ''' Gets the data type of the specified column. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetFieldType(ByVal ordinal As Integer) As System.Type GetSchemaTable() Return DirectCast(_Schema.Rows(ordinal).Item("DataType"), System.Type) End Function ''' <summary> ''' Gets the value of the specified column as a single-precision floating point number. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetFloat(ByVal ordinal As Integer) As Single Return AdConvert.ToFloat(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a globally-unique identifier (GUID). ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetGuid(ByVal ordinal As Integer) As System.Guid Return AdConvert.ToGuid(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a 16-bit signed integer. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetInt16(ByVal ordinal As Integer) As Short Return AdConvert.ToInt16(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a 32-bit signed integer. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetInt32(ByVal ordinal As Integer) As Integer Return AdConvert.ToInt32(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as a 64-bit signed integer. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetInt64(ByVal ordinal As Integer) As Long Return AdConvert.ToInt64(Item(ordinal)) End Function ''' <summary> ''' Gets the column name of the given ordinal position. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks></remarks> Public Overrides Function GetName(ByVal ordinal As Integer) As String Dim propertyIndex As Integer = 0 For Each parm As AdParameter In _RecordSetParser.Parameters If propertyIndex = ordinal Then Return parm.ParameterName Else propertyIndex += 1 End If Next Return String.Empty End Function ''' <summary> ''' Gets the column ordinal given the name of the column. ''' </summary> ''' <param name="name"> ''' The name of the column. ''' </param> ''' <remarks> ''' Returns the position of the ordinal if the column name is found, a negative value otherwise. ''' </remarks> Public Overrides Function GetOrdinal(ByVal name As String) As Integer Dim propertyIndex As Integer = 0 For Each parm As AdParameter In _RecordSetParser.Parameters If String.Compare(parm.ParameterName, name, True) = 0 Then Return propertyIndex Else propertyIndex += 1 End If Next Return -1 End Function ''' <summary> ''' Returns a DataTable that describes the column metadata of the DbDataReader. ''' </summary> ''' <returns> ''' A DataTable that describes the column metadata. ''' </returns> ''' <remarks></remarks> Public Overrides Function GetSchemaTable() As System.Data.DataTable \ If _Schema Is Nothing Then 'Get Class Name and Property Names Dim className As String = _RecordSetParser.ObjectCategory Dim propertyNames(_RecordSetParser.Parameters.Count - 1) As String For parmIndex As Integer = 0 To _RecordSetParser.Parameters.Count - 1 propertyNames(parmIndex) = _RecordSetParser.Parameters.Item(parmIndex).ParameterName Next 'Track Schema Information Dim schema As New ActiveDirectory.AdSchemaBuilder(_Connection) _Schema = schema.Build(className, propertyNames) 'Reset multi-valued attribute metadata _MultiValuedAttributes = Nothing End If Return _Schema End Function ''' <summary> ''' Gets the value of the specified column as an instance of String. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <returns></returns> ''' <remarks></remarks> Public Overrides Function GetString(ByVal ordinal As Integer) As String Return AdConvert.ToString(Item(ordinal)) End Function ''' <summary> ''' Gets the value of the specified column as an instance of Object ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <returns> ''' The value of the specified column. ''' </returns> ''' <remarks> ''' Multi-valued attributes are always returned as an array of objects and ''' single-valued attributes as an object. ''' </remarks> Public Overrides Function GetValue(ByVal ordinal As Integer) As Object Return Item(ordinal) End Function ''' <summary> ''' Pouplates an instantiated array of objects with the column values of the current row. ''' </summary> ''' <param name="values"> ''' An array of Object into which to copy the attribute columns. ''' </param> ''' <returns> ''' The number of instances of Object in the array. ''' </returns> ''' <remarks></remarks> Public Overrides Function GetValues(ByVal values() As Object) As Integer ThrowErrorOnInvalidRead() Dim propertyIndex As Integer = 0 Dim maxFieldsToRead As Integer = _CurrentRow.Properties.PropertyNames.Count If values.Length < maxFieldsToRead Then maxFieldsToRead = values.Length For Each parm As AdParameter In _RecordSetParser.Parameters values(propertyIndex) = Item(parm.ParameterName) propertyIndex += 1 Next Return maxFieldsToRead End Function ''' <summary> ''' Gets a value that indicates whether this DbDataReader contains one or more rows. ''' </summary> ''' <remarks></remarks> Public Overrides ReadOnly Property HasRows As Boolean Get Return _HasRows End Get End Property Private _HasRows As Boolean ''' <summary> ''' Gets a value indicating whether the DbDataReader is closed. ''' </summary> ''' <remarks></remarks> Public Overrides ReadOnly Property IsClosed As Boolean Get If _Connection.State = ConnectionState.Closed Then _IsClosed = True Return _IsClosed End Get End Property ''' <summary> ''' Gets a value that indicates whether the column contains nonexistent or missing values. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <returns> ''' True if the specified column is equivalent to DBNull, false otherwise. ''' </returns> ''' <remarks></remarks> Public Overrides Function IsDBNull(ByVal ordinal As Integer) As Boolean Return Item(ordinal) Is DBNull.Value End Function ''' <summary> ''' Gets the value of the specified column as an instance of Object. ''' </summary> ''' <param name="ordinal"> ''' The zero-based column ordinal. ''' </param> ''' <remarks> ''' Multi-valued attributes are always returned as an array of objects and ''' single-valued attributes as an object. ''' </remarks> Default Public Overloads Overrides ReadOnly Property Item(ByVal ordinal As Integer) As Object Get ThrowErrorOnInvalidRead() Return Item(GetName(ordinal)) End Get End Property ''' <summary> ''' Gets the value fo the specified column as an instance of Object. ''' </summary> ''' <param name="name"> ''' The name of the column. ''' </param> ''' <remarks> ''' Multi-valued attributes are always returned as an array of objects and ''' single-valued attributes as an object. ''' </remarks> Default Public Overloads Overrides ReadOnly Property Item(ByVal name As String) As Object Get ThrowErrorOnInvalidRead() Dim propertyValues As System.DirectoryServices.ResultPropertyValueCollection propertyValues = _CurrentRow.Properties.Item(name) If IsFieldMultiValued(name) Then If _Connection.PageSize <= 500 _ OrElse propertyValues.Count <= _Connection.PageSize - 25 Then Dim arrayValues(propertyValues.Count - 1) As Object propertyValues.CopyTo(arrayValues, 0) Return arrayValues Else 'If we are close to the paging limit go ahead and use range retrieval to be safe. 'Note: Anytime range retrieval must be used performance will be negatively impacted. Dim dsPath As New Path(_CurrentRow.Properties.Item("ADsPath").Item(0).ToString) Return RangeRetrieval(dsPath, name) End If ElseIf propertyValues.Count > 0 Then Return propertyValues.Item(0) Else Return Nothing End If End Get End Property ''' <summary> ''' Advances the reader to the next result when reading the results of a batch statements. ''' </summary> ''' <returns> ''' Returns true if there are more resultsets, false otherwise. ''' </returns> ''' <remarks> ''' This method allows you to process multiple resultsets returned when a batch ''' is submitted to the data provider. ''' </remarks> Public Overrides Function NextResult() As Boolean ThrowErrorOnInvalidReadNext() 'Free Resources If _Searcher IsNot Nothing Then _Connection.FreeResource(_Searcher) _Searcher = Nothing End If 'Initialize RecordSet _Schema = Nothing _RecordsAffected = 0 _CurrentSetIndex += 1 _CurrentRowIndex = -1 _CurrentSet = Nothing _RecordSetParser = Nothing Dim setsAvailable As Boolean = _CurrentSetIndex < _Command.InnerCommandSets.Count If setsAvailable Then DirectCast(_Connection, AdConnection).OnStateChange(ConnectionState.Executing) _RecordSetParser = _Command.InnerCommandSets(_CurrentSetIndex) If (_RecordSetParser.CommandType Or StatementTypes.Delete) = StatementTypes.Delete _ OrElse (_RecordSetParser.CommandType Or StatementTypes.Select) = StatementTypes.Select _ OrElse (_RecordSetParser.CommandType Or StatementTypes.Update) = StatementTypes.Update Then _Searcher = _Command.CreateSearcher(_CurrentSetIndex) _CurrentSet = _Searcher.FindAll() _HasRows = _CurrentSet IsNot Nothing Else _HasRows = False End If DirectCast(_Connection, AdConnection).OnStateChange(ConnectionState.Open) End If Return setsAvailable End Function ''' <summary> ''' Advances the reader to the next record in a result set. ''' </summary> ''' <returns> ''' Returns true if there are more rows, false otherwise. ''' </returns> ''' <remarks> ''' The default position of a data reader is before the first record. ''' Therefore, you must call Read to begin accessing data. ''' </remarks> Public Overrides Function Read() As Boolean If _CurrentSet IsNot Nothing Then _CurrentRowIndex += 1 If _CurrentSet.Count > _CurrentRowIndex Then DirectCast(_Connection, AdConnection).OnStateChange(ConnectionState.Fetching) _CurrentRow = _CurrentSet.Item(_CurrentRowIndex) _RecordsAffected += 1 Else _CurrentRow = Nothing End If End If _HasRows = _CurrentSet IsNot Nothing AndAlso _CurrentRow IsNot Nothing If Not HasRows AndAlso _CurrentSetIndex + 1 >= _Command.InnerCommandSets.Count Then Close() Return _HasRows End Function ''' <summary> ''' Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. ''' </summary> ''' <remarks> ''' The RecordsAffected property is not set until all rows are read and you close the SqlDataReader. ''' </remarks> Public Overrides ReadOnly Property RecordsAffected As Integer Get Return _RecordsAffected End Get End Property #End Region End Class End Namespace
Leave a Reply