/// <summary> /// Recursively pin the retrieve buffers in the JET_RETRIEVECOLUMN /// structures and then retrieve the columns. This is done to avoid /// creating GCHandles which is very expensive. This function pins /// the current retrievecolumn structure (indicated by i) and then /// recursively calls itself until all structures are pinned. This /// is done because it isn't possible to create an arbitrary number /// of pinned variables in a method. /// </summary> /// <param name="sesid"> /// The session to use. /// </param> /// <param name="tableid"> /// The table to retrieve from. /// </param> /// <param name="nativeretrievecolumns"> /// The nativeretrievecolumns structure.</param> /// <param name="retrievecolumns"> /// The managed retrieve columns structure. /// </param> /// <param name="numColumns">The number of columns.</param> /// <param name="i">The column currently being processed.</param> /// <returns>An error code from JetRetrieveColumns.</returns> private static unsafe int PinColumnsAndRetrieve( JET_SESID sesid, JET_TABLEID tableid, NATIVE_RETRIEVECOLUMN *nativeretrievecolumns, JET_RETRIEVECOLUMN[] retrievecolumns, int numColumns, int i) { retrievecolumns[i].CheckDataSize(); nativeretrievecolumns[i] = retrievecolumns[i].GetNativeRetrievecolumn(); fixed(byte *pinnedBuffer = retrievecolumns[i].pvData) { nativeretrievecolumns[i].pvData = new IntPtr(pinnedBuffer); if (numColumns - 1 == i) { return(Impl.JetRetrieveColumns(sesid, tableid, nativeretrievecolumns, numColumns)); } return(PinColumnsAndRetrieve(sesid, tableid, nativeretrievecolumns, retrievecolumns, numColumns, i + 1)); } }
/// <summary> /// Recursively pin the retrieve buffers in the JET_RETRIEVECOLUMN /// structures and then retrieve the columns. This is done to avoid /// creating GCHandles, which are expensive. This function pins /// the current retrievecolumn structure (indicated by i) and then /// recursively calls itself until all structures are pinned. This /// is done because it isn't possible to create an arbitrary number /// of pinned variables in a method. /// </summary> /// <param name="sesid"> /// The session to use. /// </param> /// <param name="tableid"> /// The table to retrieve from. /// </param> /// <param name="nativeretrievecolumns"> /// The nativeretrievecolumns structure.</param> /// <param name="retrievecolumns"> /// The managed retrieve columns structure. /// </param> /// <param name="numColumns">The number of columns.</param> /// <param name="i">The column currently being processed.</param> /// <returns>An error code from JetRetrieveColumns.</returns> private static unsafe int PinColumnsAndRetrieve( JET_SESID sesid, JET_TABLEID tableid, NATIVE_RETRIEVECOLUMN *nativeretrievecolumns, IList <JET_RETRIEVECOLUMN> retrievecolumns, int numColumns, int i) { // If consecutive JET_RETRIEVECOLUMN structures are using the same buffer then only pin it once. fixed(byte *pinnedBuffer = retrievecolumns[i].pvData) { do { retrievecolumns[i].CheckDataSize(); retrievecolumns[i].GetNativeRetrievecolumn(ref nativeretrievecolumns[i]); nativeretrievecolumns[i].pvData = new IntPtr(pinnedBuffer + retrievecolumns[i].ibData); i++; }while (i < numColumns && retrievecolumns[i].pvData == retrievecolumns[i - 1].pvData); return(i == numColumns? Impl.JetRetrieveColumns(sesid, tableid, nativeretrievecolumns, numColumns) : PinColumnsAndRetrieve(sesid, tableid, nativeretrievecolumns, retrievecolumns, numColumns, i)); } }
public static unsafe extern int JetRetrieveColumns( IntPtr sesid, IntPtr tableid, [In][Out] NATIVE_RETRIEVECOLUMN *psetcolumn, uint csetcolumn);
/// <summary> /// Recursive RetrieveColumns method for data pinning. This should pin a buffer and /// call the inherited RetrieveColumns method. /// </summary> /// <param name="sesid">The session to use.</param> /// <param name="tableid"> /// The table to retrieve the columns from. /// </param> /// <param name="columnValues"> /// Column values to retrieve. /// </param> /// <returns>An error code.</returns> internal static int RetrieveColumns(JET_SESID sesid, JET_TABLEID tableid, ColumnValue[] columnValues) { if (columnValues.Length > 1024) { throw new ArgumentOutOfRangeException("columnValues", columnValues.Length, "Too many column values"); } int err; unsafe { NATIVE_RETRIEVECOLUMN *nativeRetrievecolumns = stackalloc NATIVE_RETRIEVECOLUMN[columnValues.Length]; byte[] buffer = memoryCache.Allocate(); fixed(byte *pinnedBuffer = buffer) { byte *currentBuffer = pinnedBuffer; int numVariableLengthColumns = columnValues.Length; // First the fixed-size columns for (int i = 0; i < columnValues.Length; ++i) { if (0 != columnValues[i].Size) { columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]); nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer); nativeRetrievecolumns[i].cbData = checked ((uint)columnValues[i].Size); currentBuffer += nativeRetrievecolumns[i].cbData; Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer"); numVariableLengthColumns--; } } // Now the variable-length columns if (numVariableLengthColumns > 0) { int bufferUsed = checked ((int)(currentBuffer - pinnedBuffer)); int bufferRemaining = checked (buffer.Length - bufferUsed); int bufferPerColumn = bufferRemaining / numVariableLengthColumns; Debug.Assert(bufferPerColumn > 0, "Not enough buffer left to retrieve variable length columns"); // Now the variable-size columns for (int i = 0; i < columnValues.Length; ++i) { if (0 == columnValues[i].Size) { columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]); nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer); nativeRetrievecolumns[i].cbData = checked ((uint)bufferPerColumn); currentBuffer += nativeRetrievecolumns[i].cbData; Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer"); } } } // Retrieve the columns err = Api.Impl.JetRetrieveColumns(sesid, tableid, nativeRetrievecolumns, columnValues.Length); // Propagate the errors. for (int i = 0; i < columnValues.Length; ++i) { columnValues[i].Error = (JET_err)nativeRetrievecolumns[i].err; } // Now parse out the columns that were retrieved successfully for (int i = 0; i < columnValues.Length; ++i) { if (nativeRetrievecolumns[i].err >= (int)JET_err.Success && nativeRetrievecolumns[i].err != (int)JET_wrn.BufferTruncated) { byte *columnBuffer = (byte *)nativeRetrievecolumns[i].pvData; int startIndex = checked ((int)(columnBuffer - pinnedBuffer)); columnValues[i].GetValueFromBytes( buffer, startIndex, checked ((int)nativeRetrievecolumns[i].cbActual), nativeRetrievecolumns[i].err); } } } memoryCache.Free(buffer); // Finally retrieve the buffers where the columns weren't large enough. RetrieveTruncatedBuffers(sesid, tableid, columnValues, nativeRetrievecolumns); } return(err); }
/// <summary> /// Retrieve the value for columns whose buffers were truncated. /// </summary> /// <param name="sesid">The session to use.</param> /// <param name="tableid">The table to use.</param> /// <param name="columnValues">The column values.</param> /// <param name="nativeRetrievecolumns"> /// The native retrieve columns that match the column values. /// </param> private static unsafe void RetrieveTruncatedBuffers(JET_SESID sesid, JET_TABLEID tableid, ColumnValue[] columnValues, NATIVE_RETRIEVECOLUMN *nativeRetrievecolumns) { for (int i = 0; i < columnValues.Length; ++i) { if (nativeRetrievecolumns[i].err == (int)JET_wrn.BufferTruncated) { var buffer = new byte[nativeRetrievecolumns[i].cbActual]; int actualSize; int err; var retinfo = new JET_RETINFO { itagSequence = columnValues[i].ItagSequence }; // Pin the buffer and retrieve the data fixed(byte *pinnedBuffer = buffer) { err = Api.Impl.JetRetrieveColumn( sesid, tableid, columnValues[i].Columnid, new IntPtr(pinnedBuffer), buffer.Length, out actualSize, columnValues[i].RetrieveGrbit, retinfo); } // Set the error in the ColumnValue before checkin it columnValues[i].Error = (JET_err)err; Api.Check(err); // For BytesColumnValue this will copy the data to a new array. // If this situation becomes common we should simply use the array. columnValues[i].GetValueFromBytes(buffer, 0, actualSize, err); } } }
/// <summary> /// Retrieve the value for columns whose buffers were truncated. /// </summary> /// <param name="sesid">The session to use.</param> /// <param name="tableid">The table to use.</param> /// <param name="columnValues">The column values.</param> /// <param name="nativeRetrievecolumns"> /// The native retrieve columns that match the column values. /// </param> private static unsafe void RetrieveTruncatedBuffers(JET_SESID sesid, JET_TABLEID tableid, IList <ColumnValue> columnValues, NATIVE_RETRIEVECOLUMN *nativeRetrievecolumns) { for (int i = 0; i < columnValues.Count; ++i) { if (nativeRetrievecolumns[i].err == (int)JET_wrn.BufferTruncated) { var buffer = new byte[nativeRetrievecolumns[i].cbActual]; int actualSize; int err; var retinfo = new JET_RETINFO { itagSequence = columnValues[i].ItagSequence }; // Pin the buffer and retrieve the data fixed(byte *pinnedBuffer = buffer) { err = Api.Impl.JetRetrieveColumn( sesid, tableid, columnValues[i].Columnid, new IntPtr(pinnedBuffer), buffer.Length, out actualSize, columnValues[i].RetrieveGrbit, retinfo); } if (JET_wrn.BufferTruncated == (JET_wrn)err) { string error = string.Format( CultureInfo.CurrentCulture, "Column size changed from {0} to {1}. The record was probably updated by another thread.", buffer.Length, actualSize); Trace.TraceError(error); throw new InvalidOperationException(error); } // Throw errors, but put warnings in the structure Api.Check(err); columnValues[i].Error = (JET_wrn)err; // For BytesColumnValue this will copy the data to a new array. // If this situation becomes common we should simply use the array. columnValues[i].GetValueFromBytes(buffer, 0, actualSize, err); } } }
/// <summary> /// Recursive RetrieveColumns method for data pinning. This should pin a buffer and /// call the inherited RetrieveColumns method. /// </summary> /// <param name="sesid">The session to use.</param> /// <param name="tableid"> /// The table to retrieve the columns from. /// </param> /// <param name="columnValues"> /// Column values to retrieve. /// </param> internal static void RetrieveColumns(JET_SESID sesid, JET_TABLEID tableid, ColumnValue[] columnValues) { const int MaxColumnValues = 1024; if (columnValues.Length > MaxColumnValues) { throw new ArgumentOutOfRangeException("columnValues", columnValues.Length, "Too many column values"); } unsafe { byte[] buffer = null; NATIVE_RETRIEVECOLUMN *nativeRetrievecolumns = stackalloc NATIVE_RETRIEVECOLUMN[columnValues.Length]; try { buffer = Caches.ColumnCache.Allocate(); Debug.Assert(MaxColumnValues * 16 < buffer.Length, "Maximum size of fixed columns could exceed buffer size"); fixed(byte *pinnedBuffer = buffer) { byte *currentBuffer = pinnedBuffer; int numVariableLengthColumns = columnValues.Length; // First the fixed-size columns for (int i = 0; i < columnValues.Length; ++i) { if (0 != columnValues[i].Size) { columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]); nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer); nativeRetrievecolumns[i].cbData = checked ((uint)columnValues[i].Size); currentBuffer += nativeRetrievecolumns[i].cbData; Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer"); numVariableLengthColumns--; } } // Now the variable-length columns if (numVariableLengthColumns > 0) { int bufferUsed = checked ((int)(currentBuffer - pinnedBuffer)); int bufferRemaining = checked (buffer.Length - bufferUsed); int bufferPerColumn = bufferRemaining / numVariableLengthColumns; Debug.Assert(bufferPerColumn > 0, "Not enough buffer left to retrieve variable length columns"); // Now the variable-size columns for (int i = 0; i < columnValues.Length; ++i) { if (0 == columnValues[i].Size) { columnValues[i].MakeNativeRetrieveColumn(ref nativeRetrievecolumns[i]); nativeRetrievecolumns[i].pvData = new IntPtr(currentBuffer); nativeRetrievecolumns[i].cbData = checked ((uint)bufferPerColumn); currentBuffer += nativeRetrievecolumns[i].cbData; Debug.Assert(currentBuffer <= pinnedBuffer + buffer.Length, "Moved past end of pinned buffer"); } } } // Retrieve the columns Api.Check(Api.Impl.JetRetrieveColumns(sesid, tableid, nativeRetrievecolumns, columnValues.Length)); // Propagate the warnings and output. for (int i = 0; i < columnValues.Length; ++i) { columnValues[i].Error = (JET_wrn)nativeRetrievecolumns[i].err; columnValues[i].ItagSequence = (int)nativeRetrievecolumns[i].itagSequence; } // Now parse out the columns that were retrieved successfully for (int i = 0; i < columnValues.Length; ++i) { if (nativeRetrievecolumns[i].err != (int)JET_wrn.BufferTruncated) { byte *columnBuffer = (byte *)nativeRetrievecolumns[i].pvData; int startIndex = checked ((int)(columnBuffer - pinnedBuffer)); columnValues[i].GetValueFromBytes( buffer, startIndex, checked ((int)nativeRetrievecolumns[i].cbActual), nativeRetrievecolumns[i].err); } } } // Finally retrieve the buffers where the columns weren't large enough. RetrieveTruncatedBuffers(sesid, tableid, columnValues, nativeRetrievecolumns); } finally { if (buffer != null) { Caches.ColumnCache.Free(ref buffer); } } } }