1 //------------------------------------------------------------------------------
2 // <copyright file="BridgeDataReader.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // @backupOwner Microsoft
7 //---------------------------------------------------------------------
9 namespace System
.Data
.Query
.ResultAssembly
{
11 using System
.Collections
;
12 using System
.Collections
.Generic
;
13 using System
.ComponentModel
;
15 using System
.Data
.Common
;
16 using System
.Data
.Common
.Internal
.Materialization
;
17 using System
.Data
.Common
.Utils
;
18 using System
.Data
.Metadata
.Edm
;
19 using System
.Data
.Query
.InternalTrees
;
20 using System
.Data
.Query
.PlanCompiler
;
21 using System
.Diagnostics
;
24 /// DbDataReader functionality for the bridge.
26 internal sealed class BridgeDataReader
: DbDataReader
, IExtendedDataRecord
{
31 /// Object that holds the state needed by the coordinator and the root enumerator
33 private Shaper
<RecordState
> Shaper
;
36 /// Enumerator over shapers for NextResult() calls.
37 /// Null for nested data readers (depth > 0);
39 private IEnumerator
<KeyValuePair
<Shaper
<RecordState
>, CoordinatorFactory
<RecordState
>>> NextResultShaperInfoEnumerator
;
42 /// The coordinator we're responsible for returning results for.
44 private CoordinatorFactory
<RecordState
> CoordinatorFactory
;
47 /// The default record (pre-read/past-end) state
49 private RecordState DefaultRecordState
;
52 /// We delegate to this on our getters, to avoid duplicate code.
54 private BridgeDataRecord DataRecord
;
57 /// Do we have a row to read? Determined in the constructor and
58 /// should not be changed.
60 private bool _hasRows
;
63 /// Set to true only when we've been closed through the Close() method
65 private bool _isClosed
;
72 /// Constructor used by the ResultColumn when doing GetValue, and by the Create factory
75 /// <param name="collection"></param>
76 /// <param name="isRoot"></param>
77 /// <param name="nextResultShaperInfos">enumrator of the shapers for NextResult() calls</param>
78 internal BridgeDataReader(Shaper
<RecordState
> shaper
, CoordinatorFactory
<RecordState
> coordinatorFactory
, int depth
, IEnumerator
<KeyValuePair
<Shaper
<RecordState
>, CoordinatorFactory
<RecordState
>>> nextResultShaperInfos
)
80 Debug
.Assert(null != shaper
, "null shaper?");
81 Debug
.Assert(null != coordinatorFactory
, "null coordinatorFactory?");
82 Debug
.Assert(depth
== 0 || nextResultShaperInfos
== null, "Nested data readers should not have multiple result sets.");
84 NextResultShaperInfoEnumerator
= nextResultShaperInfos
!= null ? nextResultShaperInfos
: null;
85 SetShaper(shaper
, coordinatorFactory
, depth
);
88 private void SetShaper(Shaper
<RecordState
> shaper
, CoordinatorFactory
<RecordState
> coordinatorFactory
, int depth
)
91 CoordinatorFactory
= coordinatorFactory
;
92 DataRecord
= new BridgeDataRecord(shaper
, depth
);
94 // To determine whether there are any rows for this coordinator at this place in
95 // the root enumerator, we pretty much just look at it's current record (we'll read
96 // one if there isn't one waiting) and if it matches our coordinator, we've got rows.
99 if (!Shaper
.DataWaiting
) {
100 Shaper
.DataWaiting
= Shaper
.RootEnumerator
.MoveNext();
102 if (Shaper
.DataWaiting
) {
103 RecordState currentRecord
= Shaper
.RootEnumerator
.Current
;
105 if (null != currentRecord
) {
106 _hasRows
= (currentRecord
.CoordinatorFactory
== CoordinatorFactory
);
110 // Once we've created the root enumerator, we can get the default record state
111 DefaultRecordState
= coordinatorFactory
.GetDefaultRecordState(Shaper
);
112 Debug
.Assert(null != DefaultRecordState
, "no default?");
116 /// The primary factory method to produce the BridgeDataReader; given a store data
117 /// reader and a column map, create the BridgeDataReader, hooking up the IteratorSources
118 /// and ResultColumn Hierarchy. All construction of top level data readers go through
121 /// <param name="storeDataReader"></param>
122 /// <param name="columnMap">column map of the first result set</param>
123 /// <param name="nextResultColumnMaps">enumerable of the column maps for NextResult() calls.</param>
124 /// <returns></returns>
125 static internal DbDataReader
Create(DbDataReader storeDataReader
, ColumnMap columnMap
, MetadataWorkspace workspace
, IEnumerable
<ColumnMap
> nextResultColumnMaps
) {
126 Debug
.Assert(storeDataReader
!= null, "null storeDataReaders?");
127 Debug
.Assert(columnMap
!= null, "null columnMap?");
128 Debug
.Assert(workspace
!= null, "null workspace?");
130 var shaperInfo
= CreateShaperInfo(storeDataReader
, columnMap
, workspace
);
131 DbDataReader result
= new BridgeDataReader(shaperInfo
.Key
, shaperInfo
.Value
, /*depth:*/ 0, GetNextResultShaperInfo(storeDataReader
, workspace
, nextResultColumnMaps
).GetEnumerator());
135 private static KeyValuePair
<Shaper
<RecordState
>, CoordinatorFactory
<RecordState
>> CreateShaperInfo(DbDataReader storeDataReader
, ColumnMap columnMap
, MetadataWorkspace workspace
)
137 Debug
.Assert(storeDataReader
!= null, "null storeDataReaders?");
138 Debug
.Assert(columnMap
!= null, "null columnMap?");
139 Debug
.Assert(workspace
!= null, "null workspace?");
141 System
.Data
.Common
.QueryCache
.QueryCacheManager cacheManager
= workspace
.GetQueryCacheManager();
142 const System
.Data
.Objects
.MergeOption NoTracking
= System
.Data
.Objects
.MergeOption
.NoTracking
;
144 ShaperFactory
<RecordState
> shaperFactory
= Translator
.TranslateColumnMap
<RecordState
>(cacheManager
, columnMap
, workspace
, null, NoTracking
, true);
145 Shaper
<RecordState
> recordShaper
= shaperFactory
.Create(storeDataReader
, null, workspace
, System
.Data
.Objects
.MergeOption
.NoTracking
, true);
147 return new KeyValuePair
<Shaper
<RecordState
>, CoordinatorFactory
<RecordState
>>(recordShaper
, recordShaper
.RootCoordinator
.TypedCoordinatorFactory
);
150 private static IEnumerable
<KeyValuePair
<Shaper
<RecordState
>, CoordinatorFactory
<RecordState
>>> GetNextResultShaperInfo(DbDataReader storeDataReader
, MetadataWorkspace workspace
, IEnumerable
<ColumnMap
> nextResultColumnMaps
)
152 foreach (var nextResultColumnMap
in nextResultColumnMaps
)
154 yield return CreateShaperInfo(storeDataReader
, nextResultColumnMap
, workspace
);
163 /// Implicitly close this (nested) data reader; will be called whenever
164 /// the user has done a GetValue() or a Read() on a parent reader/record
165 /// to ensure that we consume all our results. We do that because we
166 /// our design requires us to be positioned at the next nested reader's
169 internal void CloseImplicitly() {
171 DataRecord
.CloseImplicitly();
175 /// Reads to the end of the source enumerator provided
177 private void Consume() {
178 while (ReadInternal()) ;
182 /// Figure out the CLR type from the TypeMetadata object; For scalars,
183 /// we can get this from the metadata workspace, but for the rest, we
184 /// just guess at "Object". You need to use the DataRecordInfo property
185 /// to get better information for those.
187 /// <param name="typeUsage"></param>
188 /// <returns></returns>
189 internal static Type
GetClrTypeFromTypeMetadata(TypeUsage typeUsage
) {
192 PrimitiveType primitiveType
;
193 if (TypeHelpers
.TryGetEdmType
<PrimitiveType
>(typeUsage
, out primitiveType
)) {
194 result
= primitiveType
.ClrEquivalentType
;
197 if (TypeSemantics
.IsReferenceType(typeUsage
)) {
198 result
= typeof(EntityKey
);
200 else if (TypeUtils
.IsStructuredType(typeUsage
)) {
201 result
= typeof(DbDataRecord
);
203 else if (TypeUtils
.IsCollectionType(typeUsage
)) {
204 result
= typeof(DbDataReader
);
206 else if (TypeUtils
.IsEnumerationType(typeUsage
)) {
207 result
= ((EnumType
)typeUsage
.EdmType
).UnderlyingType
.ClrEquivalentType
;
210 result
= typeof(object);
218 #region data reader specific properties and methods
221 /// implementation for DbDataReader.Depth property
223 override public int Depth
{
225 AssertReaderIsOpen("Depth");
226 return DataRecord
.Depth
;
231 /// implementation for DbDataReader.HasRows property
233 override public bool HasRows
{
235 AssertReaderIsOpen("HasRows");
241 /// implementation for DbDataReader.IsClosed property
243 override public bool IsClosed
{
245 // Rather that try and track this in two places; we just delegate
246 // to the data record that we constructed; it has more reasons to
247 // have to know this than we do in the data reader. (Of course,
248 // we look at our own closed state too...)
249 return ((_isClosed
) || DataRecord
.IsClosed
);
254 /// implementation for DbDataReader.RecordsAffected property
256 override public int RecordsAffected
{
258 int result
= -1; // For nested readers, return -1 which is the default for queries.
260 // We defer to the store reader for rows affected count. Note that for queries,
261 // the provider is generally expected to return -1.
263 if (DataRecord
.Depth
== 0) {
264 result
= Shaper
.Reader
.RecordsAffected
;
271 /// Ensures that the reader is actually open, and throws an exception if not
273 private void AssertReaderIsOpen(string methodName
) {
275 if (DataRecord
.IsImplicitlyClosed
) {
276 throw EntityUtil
.ImplicitlyClosedDataReaderError();
278 if (DataRecord
.IsExplicitlyClosed
) {
279 throw EntityUtil
.DataReaderClosed(methodName
);
285 /// implementation for DbDataReader.Close() method
287 override public void Close() {
288 // Make sure we explicitly closed the data record, since that's what
289 // where using to track closed state.
290 DataRecord
.CloseExplicitly();
295 if (0 == DataRecord
.Depth
) {
296 // If we're the root collection, we want to ensure the remainder of
297 // the result column hierarchy is closed out, to avoid dangling
298 // references to it, should it be reused. We also want to physically
299 // close out the source reader as well.
300 Shaper
.Reader
.Close();
303 // For non-root collections, we have to consume all the data, or we'll
304 // not be positioned propertly for what comes afterward.
309 if (NextResultShaperInfoEnumerator
!= null)
311 NextResultShaperInfoEnumerator
.Dispose();
312 NextResultShaperInfoEnumerator
= null;
317 /// implementation for DbDataReader.GetEnumerator() method
319 [EditorBrowsableAttribute(EditorBrowsableState
.Never
)]
320 override public IEnumerator
GetEnumerator() {
321 IEnumerator result
= new DbEnumerator((IDataReader
)this, true); // We always want to close the reader;
326 /// implementation for DbDataReader.GetSchemaTable() method
328 /// This is awaiting some common code
330 /// <returns></returns>
331 /// <exception cref="NotSupportedException">GetSchemaTable is not supported at this time</exception>
332 override public DataTable
GetSchemaTable() {
333 throw EntityUtil
.NotSupported(System
.Data
.Entity
.Strings
.ADP_GetSchemaTableIsNotSupported
);
337 /// implementation for DbDataReader.NextResult() method
339 /// <returns></returns>
340 override public bool NextResult() {
341 AssertReaderIsOpen("NextResult");
343 // If there is a next result set available, serve it.
344 if (NextResultShaperInfoEnumerator
!= null &&
345 Shaper
.Reader
.NextResult() &&
346 NextResultShaperInfoEnumerator
.MoveNext())
348 Debug
.Assert(DataRecord
.Depth
== 0, "Nested data readers should not have multiple result sets.");
349 var nextResultShaperInfo
= NextResultShaperInfoEnumerator
.Current
;
350 DataRecord
.CloseImplicitly();
351 SetShaper(nextResultShaperInfo
.Key
, nextResultShaperInfo
.Value
, depth
: 0);
355 if (0 == DataRecord
.Depth
) {
356 // NOTE:: this is required to ensure that output parameter values
357 // are set in SQL Server, and other providers where they come after
359 CommandHelper
.ConsumeReader(Shaper
.Reader
);
362 // For nested readers, make sure we're positioned properly for
363 // the following columns...
367 // SQLBUDT #631726 - ensure we close the records that may be
369 // SQLBUDT #632494 - do this after we consume the underlying reader
370 // so we don't run result assembly through it...
373 // Reset any state on our attached data record, since we've now
374 // gone past the end of the reader.
375 DataRecord
.SetRecordSource(null, false);
381 /// implementation for DbDataReader.Read() method
383 /// <returns></returns>
384 override public bool Read() {
385 AssertReaderIsOpen("Read");
387 // First of all we need to inform each of the nested records that
388 // have been returned that they're "implicitly" closed -- that is
389 // we've moved on. This will also ensure that any records remaining
390 // in any active nested readers are consumed
391 DataRecord
.CloseImplicitly();
393 // OK, now go ahead and advance the source enumerator and set the
395 bool result
= ReadInternal();
396 DataRecord
.SetRecordSource(Shaper
.RootEnumerator
.Current
, result
);
401 /// Internal read method; does the work of advancing the root enumerator
402 /// as needed and determining whether it's current record is for our
403 /// coordinator. The public Read method does the assertions and such that
404 /// we don't want to do when we're called from internal methods to do things
405 /// like consume the rest of the reader's contents.
407 /// <param name="rootEnumerator"></param>
408 /// <returns></returns>
409 private bool ReadInternal() {
412 // If there's nothing waiting for the root enumerator, then attempt
414 if (!Shaper
.DataWaiting
) {
415 Shaper
.DataWaiting
= Shaper
.RootEnumerator
.MoveNext();
418 // If we have some data (we may have just read it above) then figure
419 // out who it belongs to-- us or someone else. We also skip over any
420 // records that are for our children (nested readers); if we're being
421 // asked to read, it's too late for them to read them.
422 while (Shaper
.DataWaiting
423 && Shaper
.RootEnumerator
.Current
.CoordinatorFactory
!= CoordinatorFactory
424 && Shaper
.RootEnumerator
.Current
.CoordinatorFactory
.Depth
> CoordinatorFactory
.Depth
) {
425 Shaper
.DataWaiting
= Shaper
.RootEnumerator
.MoveNext();
428 if (Shaper
.DataWaiting
) {
429 // We found something, go ahead and indicate to the shaper we want
430 // this record, set up the data record, etc.
431 if (Shaper
.RootEnumerator
.Current
.CoordinatorFactory
== CoordinatorFactory
) {
432 Shaper
.DataWaiting
= false;
433 Shaper
.RootEnumerator
.Current
.AcceptPendingValues();
442 #region metadata properties and methods
445 /// implementation for DbDataReader.DataRecordInfo property
447 public DataRecordInfo DataRecordInfo
{
449 AssertReaderIsOpen("DataRecordInfo");
451 DataRecordInfo result
;
452 if (DataRecord
.HasData
) {
453 result
= DataRecord
.DataRecordInfo
;
456 result
= DefaultRecordState
.DataRecordInfo
;
463 /// implementation for DbDataReader.FieldCount property
465 override public int FieldCount
{
467 AssertReaderIsOpen("FieldCount");
469 // In this method, we need to return a constant value, regardless
470 // of how polymorphic the result is, because there is a lot of code
471 // in the wild that expects it to be constant; Ideally, we'd return
472 // the number of columns in the actual type that we have, but since
473 // that would probably break folks, I'm leaving it at returning the
474 // base set of columns that all rows will have.
476 int result
= DefaultRecordState
.ColumnCount
;
482 /// implementation for DbDataReader.GetDataTypeName() method
484 /// <param name="ordinal"></param>
485 /// <returns></returns>
486 override public string GetDataTypeName(int ordinal
) {
487 AssertReaderIsOpen("GetDataTypeName");
489 if (DataRecord
.HasData
) {
490 result
= DataRecord
.GetDataTypeName(ordinal
);
493 result
= TypeHelpers
.GetFullName(DefaultRecordState
.GetTypeUsage(ordinal
));
499 /// implementation for DbDataReader.GetFieldType() method
501 /// <param name="ordinal"></param>
502 /// <returns></returns>
503 override public Type
GetFieldType(int ordinal
) {
504 AssertReaderIsOpen("GetFieldType");
506 if (DataRecord
.HasData
) {
507 result
= DataRecord
.GetFieldType(ordinal
);
510 result
= GetClrTypeFromTypeMetadata(DefaultRecordState
.GetTypeUsage(ordinal
));
516 /// implementation for DbDataReader.GetName() method
518 /// <param name="ordinal"></param>
519 /// <returns></returns>
520 override public string GetName(int ordinal
) {
521 AssertReaderIsOpen("GetName");
523 if (DataRecord
.HasData
) {
524 result
= DataRecord
.GetName(ordinal
);
527 result
= DefaultRecordState
.GetName(ordinal
);
533 /// implementation for DbDataReader.GetOrdinal() method
535 /// <param name="name"></param>
536 /// <returns></returns>
538 override public int GetOrdinal(string name
) {
539 AssertReaderIsOpen("GetOrdinal");
541 if (DataRecord
.HasData
) {
542 result
= DataRecord
.GetOrdinal(name
);
545 result
= DefaultRecordState
.GetOrdinal(name
);
551 /// implementation for DbDataReader.GetProviderSpecificFieldType() method
553 /// <param name="ordinal"></param>
554 /// <returns></returns>
555 /// <exception cref="NotSupportedException">GetProviderSpecificFieldType is not supported at this time</exception>
556 [EditorBrowsableAttribute(EditorBrowsableState
.Never
)]
557 override public Type
GetProviderSpecificFieldType(int ordinal
) {
558 throw EntityUtil
.NotSupported();
563 #region data record properties and methods
565 ////////////////////////////////////////////////////////////////////////
566 ////////////////////////////////////////////////////////////////////////
567 ////////////////////////////////////////////////////////////////////////
569 // The remaining methods on this class delegate to the inner data record
571 ////////////////////////////////////////////////////////////////////////
572 ////////////////////////////////////////////////////////////////////////
573 ////////////////////////////////////////////////////////////////////////
575 #region general getter methods and indexer properties
578 /// implementation for DbDataReader[ordinal] indexer value getter
580 override public object this[int ordinal
] {
582 return DataRecord
[ordinal
];
587 /// implementation for DbDataReader[name] indexer value getter
589 override public object this[string name
] {
591 int ordinal
= GetOrdinal(name
);
592 return DataRecord
[ordinal
];
597 /// implementation for DbDataReader.GetProviderSpecificValue() method
599 /// <param name="ordinal"></param>
600 /// <returns></returns>
601 /// <exception cref="NotSupportedException">GetProviderSpecificValue is not supported at this time</exception>
602 [EditorBrowsableAttribute(EditorBrowsableState
.Never
)]
603 public override object GetProviderSpecificValue(int ordinal
) {
604 throw EntityUtil
.NotSupported();
608 /// implementation for DbDataReader.GetProviderSpecificValues() method
610 /// <param name="values"></param>
611 /// <returns></returns>
612 /// <exception cref="NotSupportedException">GetProviderSpecificValues is not supported at this time</exception>
613 [EditorBrowsableAttribute(EditorBrowsableState
.Never
)]
614 public override int GetProviderSpecificValues(object[] values
) {
615 throw EntityUtil
.NotSupported();
619 /// implementation for DbDataReader.GetValue() method
621 /// <param name="ordinal"></param>
622 /// <returns></returns>
623 override public Object
GetValue(int ordinal
) {
624 return DataRecord
.GetValue(ordinal
);
628 /// implementation for DbDataReader.GetValues() method
630 /// <param name="values"></param>
631 /// <returns></returns>
632 override public int GetValues(object[] values
) {
633 return DataRecord
.GetValues(values
);
638 #region simple scalar value getter methods
641 /// implementation for DbDataReader.GetBoolean() method
643 /// <param name="ordinal"></param>
644 /// <returns></returns>
645 override public bool GetBoolean(int ordinal
) {
646 return DataRecord
.GetBoolean(ordinal
);
650 /// implementation for DbDataReader.GetByte() method
652 /// <param name="ordinal"></param>
653 /// <returns></returns>
654 override public byte GetByte(int ordinal
) {
655 return DataRecord
.GetByte(ordinal
);
659 /// implementation for DbDataReader.GetChar() method
661 /// <param name="ordinal"></param>
662 /// <returns></returns>
663 override public char GetChar(int ordinal
) {
664 return DataRecord
.GetChar(ordinal
);
668 /// implementation for DbDataReader.GetDateTime() method
670 /// <param name="ordinal"></param>
671 /// <returns></returns>
672 override public DateTime
GetDateTime(int ordinal
) {
673 return DataRecord
.GetDateTime(ordinal
);
677 /// implementation for DbDataReader.GetDecimal() method
679 /// <param name="ordinal"></param>
680 /// <returns></returns>
681 override public Decimal
GetDecimal(int ordinal
) {
682 return DataRecord
.GetDecimal(ordinal
);
686 /// implementation for DbDataReader.GetDouble() method
688 /// <param name="ordinal"></param>
689 /// <returns></returns>
690 override public double GetDouble(int ordinal
) {
691 return DataRecord
.GetDouble(ordinal
);
695 /// implementation for DbDataReader.GetFloat() method
697 /// <param name="ordinal"></param>
698 /// <returns></returns>
699 override public float GetFloat(int ordinal
) {
700 return DataRecord
.GetFloat(ordinal
);
704 /// implementation for DbDataReader.GetGuid() method
706 /// <param name="ordinal"></param>
707 /// <returns></returns>
708 override public Guid
GetGuid(int ordinal
) {
709 return DataRecord
.GetGuid(ordinal
);
713 /// implementation for DbDataReader.GetInt16() method
715 /// <param name="ordinal"></param>
716 /// <returns></returns>
717 override public Int16
GetInt16(int ordinal
) {
718 return DataRecord
.GetInt16(ordinal
);
722 /// implementation for DbDataReader.GetInt32() method
724 /// <param name="ordinal"></param>
725 /// <returns></returns>
726 override public Int32
GetInt32(int ordinal
) {
727 return DataRecord
.GetInt32(ordinal
);
731 /// implementation for DbDataReader.GetInt64() method
733 /// <param name="ordinal"></param>
734 /// <returns></returns>
735 override public Int64
GetInt64(int ordinal
) {
736 return DataRecord
.GetInt64(ordinal
);
740 /// implementation for DbDataReader.GetString() method
742 /// <param name="ordinal"></param>
743 /// <returns></returns>
744 override public String
GetString(int ordinal
) {
745 return DataRecord
.GetString(ordinal
);
750 /// implementation for DbDataReader.IsDBNull() method
752 /// <param name="ordinal"></param>
753 /// <returns></returns>
754 override public bool IsDBNull(int ordinal
) {
755 return DataRecord
.IsDBNull(ordinal
);
760 #region array scalar value getter methods
763 /// implementation for DbDataReader.GetBytes() method
765 /// <param name="ordinal"></param>
766 /// <param name="dataOffset"></param>
767 /// <param name="buffer"></param>
768 /// <param name="bufferOffset"></param>
769 /// <param name="length"></param>
770 /// <returns></returns>
771 override public long GetBytes(int ordinal
, long dataOffset
, byte[] buffer
, int bufferOffset
, int length
) {
772 return DataRecord
.GetBytes(ordinal
, dataOffset
, buffer
, bufferOffset
, length
);
776 /// implementation for DbDataReader.GetChars() method
778 /// <param name="ordinal"></param>
779 /// <param name="dataOffset"></param>
780 /// <param name="buffer"></param>
781 /// <param name="bufferOffset"></param>
782 /// <param name="length"></param>
783 /// <returns></returns>
784 override public long GetChars(int ordinal
, long dataOffset
, char[] buffer
, int bufferOffset
, int length
) {
785 return DataRecord
.GetChars(ordinal
, dataOffset
, buffer
, bufferOffset
, length
);
790 #region complex type getters
793 /// implementation for DbDataReader.GetData() method
795 /// <param name="ordinal"></param>
796 /// <returns></returns>
797 override protected DbDataReader
GetDbDataReader(int ordinal
) {
798 return (DbDataReader
)DataRecord
.GetData(ordinal
);
802 /// implementation for DbDataReader.GetDataRecord() method
804 /// <param name="ordinal"></param>
805 /// <returns></returns>
806 public DbDataRecord
GetDataRecord(int ordinal
) {
807 return DataRecord
.GetDataRecord(ordinal
);
811 /// Used to return a nested result
813 /// <param name="ordinal"></param>
814 /// <returns></returns>
815 public DbDataReader
GetDataReader(int ordinal
) {
816 return this.GetDbDataReader(ordinal
);