| File: System\Data\RecordManager.cs | Web Access |
| Project: src\runtime\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; namespace System.Data { internal sealed class RecordManager { private readonly DataTable _table; private int _lastFreeRecord; private int _minimumCapacity = 50; private int _recordCapacity; private readonly List<int> _freeRecordList = new List<int>(); private DataRow?[] _rows = default!; // Always late-initialized by callers via NewRecordBase internal RecordManager(DataTable table) { if (table == null) { throw ExceptionBuilder.ArgumentNull(nameof(table)); } _table = table; } private void GrowRecordCapacity() { RecordCapacity = NewCapacity(_recordCapacity) < NormalizedMinimumCapacity(_minimumCapacity) ? NormalizedMinimumCapacity(_minimumCapacity) : NewCapacity(_recordCapacity); // set up internal map : record --> row DataRow[] newRows = _table.NewRowArray(_recordCapacity); if (_rows != null) { Array.Copy(_rows, newRows, Math.Min(_lastFreeRecord, _rows.Length)); } _rows = newRows; } internal int LastFreeRecord => _lastFreeRecord; internal int MinimumCapacity { get { return _minimumCapacity; } set { if (_minimumCapacity != value) { if (value < 0) { throw ExceptionBuilder.NegativeMinimumCapacity(); } _minimumCapacity = value; } } } internal int RecordCapacity { get { return _recordCapacity; } set { if (_recordCapacity != value) { for (int i = 0; i < _table.Columns.Count; i++) { _table.Columns[i].SetCapacity(value); } _recordCapacity = value; } } } internal static int NewCapacity(int capacity) => (capacity < 128) ? 128 : (capacity + capacity); // Normalization: 64, 256, 1024, 2k, 3k, .... private static int NormalizedMinimumCapacity(int capacity) { if (capacity < 1024 - 10) { if (capacity < 256 - 10) { if (capacity < 54) return 64; return 256; } return 1024; } return (((capacity + 10) >> 10) + 1) << 10; } internal int NewRecordBase() { int record; if (_freeRecordList.Count != 0) { record = _freeRecordList[_freeRecordList.Count - 1]; _freeRecordList.RemoveAt(_freeRecordList.Count - 1); } else { if (_lastFreeRecord >= _recordCapacity) { GrowRecordCapacity(); } record = _lastFreeRecord; _lastFreeRecord++; } Debug.Assert(record >= 0 && record < _recordCapacity, "NewRecord: Invalid record"); return record; } internal void FreeRecord(ref int record) { Debug.Assert(-1 <= record && record < _recordCapacity, "invalid record"); // Debug.Assert(record < lastFreeRecord, "Attempt to Free() <outofbounds> record"); if (-1 != record) { _rows[record] = null; int count = _table._columnCollection.Count; for (int i = 0; i < count; ++i) { _table._columnCollection[i].FreeRecord(record); } // if freeing the last record, recycle it if (_lastFreeRecord == record + 1) { _lastFreeRecord--; } else if (record < _lastFreeRecord) { _freeRecordList.Add(record); } record = -1; } } internal void Clear(bool clearAll) { if (clearAll) { for (int record = 0; record < _recordCapacity; ++record) { _rows[record] = null; } int count = _table._columnCollection.Count; for (int i = 0; i < count; ++i) { // this improves performance by caching the column instead of obtaining it for each row DataColumn column = _table._columnCollection[i]; for (int record = 0; record < _recordCapacity; ++record) { column.FreeRecord(record); } } _lastFreeRecord = 0; _freeRecordList.Clear(); } else { // just clear attached rows _freeRecordList.Capacity = _freeRecordList.Count + _table.Rows.Count; for (int record = 0; record < _recordCapacity; ++record) { if (_rows[record] is DataRow row && row.rowID != -1) { int tempRecord = record; FreeRecord(ref tempRecord); } } } } internal DataRow this[int record] { get { Debug.Assert(record >= 0 && record < _rows.Length, "Invalid record number"); return _rows[record]!; } set { Debug.Assert(record >= 0 && record < _rows.Length, "Invalid record number"); _rows[record] = value; } } internal static void SetKeyValues(int record, DataKey key, object[] keyValues) { for (int i = 0; i < keyValues.Length; i++) { key.ColumnsReference[i][record] = keyValues[i]; } } // Increases AutoIncrementCurrent internal int ImportRecord(DataTable src, int record) { return CopyRecord(src, record, -1); } // No impact on AutoIncrementCurrent if over written internal int CopyRecord(DataTable src, int record, int copy) { Debug.Assert(src != null, "Can not Merge record without a table"); if (record == -1) { return copy; } int newRecord = -1; try { newRecord = copy == -1 ? _table.NewUninitializedRecord() : copy; int count = _table.Columns.Count; for (int i = 0; i < count; ++i) { DataColumn dstColumn = _table.Columns[i]; DataColumn? srcColumn = src.Columns[dstColumn.ColumnName]; if (null != srcColumn) { object value = srcColumn[record]; ICloneable? cloneableObject = value as ICloneable; if (null != cloneableObject) { dstColumn[newRecord] = cloneableObject.Clone(); } else { dstColumn[newRecord] = value; } } else if (-1 == copy) { dstColumn.Init(newRecord); } } } catch (Exception e) when (Common.ADP.IsCatchableOrSecurityExceptionType(e)) { if (-1 == copy) { FreeRecord(ref newRecord); } throw; } return newRecord; } internal void SetRowCache(DataRow[] newRows) { _rows = newRows; _lastFreeRecord = _rows.Length; _recordCapacity = _lastFreeRecord; } [Conditional("DEBUG")] internal void VerifyRecord(int record) { Debug.Assert((record < _lastFreeRecord) && (-1 == _freeRecordList.IndexOf(record)), "accessing free record"); var r = _rows[record]; Debug.Assert((null == r) || (record == r._oldRecord) || (record == r._newRecord) || (record == r._tempRecord), "record of a different row"); } [Conditional("DEBUG")] internal void VerifyRecord(int record, DataRow? row) { Debug.Assert((record < _lastFreeRecord) && (-1 == _freeRecordList.IndexOf(record)), "accessing free record"); Debug.Assert((null == _rows[record]) || (row == _rows[record]), "record of a different row"); } } }