|
// 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace System.Data
{
/// <summary>
/// Represents one table of in-memory data.
/// </summary>
[ToolboxItem(false)]
[DesignTimeVisible(false)]
[DefaultProperty(nameof(TableName))]
[DefaultEvent(nameof(RowChanging))]
[Editor("Microsoft.VSDesigner.Data.Design.DataTableEditor, Microsoft.VSDesigner, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
[XmlSchemaProvider(nameof(GetDataTableSchema))]
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
public class DataTable : MarshalByValueComponent, IListSource, ISupportInitializeNotification, ISerializable, IXmlSerializable
{
private DataSet? _dataSet;
private DataView? _defaultView;
/// <summary>
/// Monotonically increasing number representing the order <see cref="DataRow"/> have been added to <see cref="DataRowCollection"/>.
/// </summary>
/// <remarks>This limits <see cref="DataRowCollection.Add(DataRow)"/> to <see cref="int.MaxValue"/> operations.</remarks>
internal long _nextRowID;
internal readonly DataRowCollection _rowCollection;
// columns
internal readonly DataColumnCollection _columnCollection;
// constraints
private readonly ConstraintCollection _constraintCollection;
//SimpleContent implementation
private int _elementColumnCount;
// relations
internal DataRelationCollection? _parentRelationsCollection;
internal DataRelationCollection? _childRelationsCollection;
// RecordManager
internal readonly RecordManager _recordManager;
// index mgmt
internal readonly List<Index> _indexes;
private List<Index>? _shadowIndexes;
private int _shadowCount;
// props
internal PropertyCollection? _extendedProperties;
private string _tableName = string.Empty;
internal string? _tableNamespace;
private string _tablePrefix = string.Empty;
internal DataExpression? _displayExpression;
internal bool _fNestedInDataset = true;
// globalization stuff
private CultureInfo _culture;
private bool _cultureUserSet;
private CompareInfo? _compareInfo;
private CompareOptions _compareFlags = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth;
private IFormatProvider? _formatProvider;
private StringComparer? _hashCodeProvider;
private bool _caseSensitive;
private bool _caseSensitiveUserSet;
// XML properties
internal string? _encodedTableName; // For XmlDataDocument only
internal DataColumn? _xmlText; // text values of a complex xml element
internal DataColumn? _colUnique;
internal decimal _minOccurs = 1; // default = 1
internal decimal _maxOccurs = 1; // default = 1
internal bool _repeatableElement;
private object? _typeName;
// primary key info
internal UniqueConstraint? _primaryKey;
internal IndexField[] _primaryIndex = Array.Empty<IndexField>();
private DataColumn[]? _delayedSetPrimaryKey;
// Loading Schema and/or Data related optimization
private Index? _loadIndex;
private Index? _loadIndexwithOriginalAdded;
private Index? _loadIndexwithCurrentDeleted;
private int _suspendIndexEvents;
private bool _savedEnforceConstraints;
private bool _inDataLoad;
private bool _initialLoad;
private bool _enforceConstraints = true;
internal bool _suspendEnforceConstraints;
protected internal bool fInitInProgress;
internal bool _fInLoadDiffgram;
private byte _isTypedDataTable; // 0 == unknown, 1 = yes, 2 = No
private DataRow[]? _emptyDataRowArray;
// Property Descriptor Cache for DataBinding
private PropertyDescriptorCollection? _propertyDescriptorCollectionCache;
// Cache for relation that has this table as nested child table.
private DataRelation[] _nestedParentRelations = Array.Empty<DataRelation>();
// Dependent column list for expression evaluation
internal List<DataColumn>? _dependentColumns;
// events
private bool _mergingData;
private DataRowChangeEventHandler? _onRowChangedDelegate;
private DataRowChangeEventHandler? _onRowChangingDelegate;
private DataRowChangeEventHandler? _onRowDeletingDelegate;
private DataRowChangeEventHandler? _onRowDeletedDelegate;
private DataColumnChangeEventHandler? _onColumnChangedDelegate;
private DataColumnChangeEventHandler? _onColumnChangingDelegate;
private DataTableClearEventHandler? _onTableClearingDelegate;
private DataTableClearEventHandler? _onTableClearedDelegate;
private DataTableNewRowEventHandler? _onTableNewRowDelegate;
private PropertyChangedEventHandler? _onPropertyChangingDelegate;
private EventHandler? _onInitialized;
// misc
private readonly DataRowBuilder _rowBuilder;
private const string KEY_XMLSCHEMA = "XmlSchema";
private const string KEY_XMLDIFFGRAM = "XmlDiffGram";
internal readonly List<DataView> _delayedViews = new List<DataView>();
private readonly List<DataViewListener> _dataViewListeners = new List<DataViewListener>();
internal Hashtable? _rowDiffId;
internal readonly ReaderWriterLockSlim _indexesLock = new ReaderWriterLockSlim();
internal int _ukColumnPositionForInference = -1;
// default remoting format is Xml
private SerializationFormat _remotingFormat = SerializationFormat.Xml;
private static int s_objectTypeCount; // Bid counter
private readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount);
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.DataTable'/> class with no arguments.
/// </summary>
public DataTable()
{
GC.SuppressFinalize(this);
DataCommonEventSource.Log.Trace("<ds.DataTable.DataTable|API> {0}", ObjectID);
_nextRowID = 1;
_recordManager = new RecordManager(this);
_culture = CultureInfo.CurrentCulture;
_columnCollection = new DataColumnCollection(this);
_constraintCollection = new ConstraintCollection(this);
_rowCollection = new DataRowCollection(this);
_indexes = new List<Index>();
_rowBuilder = new DataRowBuilder(this, -1);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.DataTable'/> class with the specified table
/// name.
/// </summary>
public DataTable(string? tableName) : this()
{
_tableName = tableName ?? "";
}
public DataTable(string? tableName, string? tableNamespace) : this(tableName)
{
Namespace = tableNamespace;
}
// Deserialize the table from binary/xml stream.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
Justification = "CreateInstance's use of GetType uses only the parameterless constructor. Warnings are about serialization related constructors.")]
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
[Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
protected DataTable(SerializationInfo info, StreamingContext context) : this()
{
bool isSingleTable = context.Context != null ? Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture) : true;
SerializationFormat remotingFormat = SerializationFormat.Xml;
SerializationInfoEnumerator e = info.GetEnumerator();
while (e.MoveNext())
{
switch (e.Name)
{
case "DataTable.RemotingFormat": //DataTable.RemotingFormat does not exist in V1/V1.1 versions
remotingFormat = (SerializationFormat)e.Value!;
break;
}
}
if (remotingFormat == SerializationFormat.Binary &&
!LocalAppContextSwitches.AllowUnsafeSerializationFormatBinary)
{
throw ExceptionBuilder.SerializationFormatBinaryNotSupported();
}
DeserializeDataTable(info, isSingleTable, remotingFormat);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Binary serialization is unsafe in general and is planned to be obsoleted. We do not want to mark interface or ctors of this class as unsafe as that would show many unnecessary warnings elsewhere.")]
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "Binary serialization is unsafe in general and is planned to be obsoleted. We do not want to mark interface or ctors of this class as unsafe as that would show many unnecessary warnings elsewhere.")]
[Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
SerializationFormat remotingFormat = RemotingFormat;
bool isSingleTable = context.Context != null ? Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture) : true;
SerializeDataTable(info, isSingleTable, remotingFormat);
}
// Serialize the table schema and data.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
private void SerializeDataTable(SerializationInfo info, bool isSingleTable, SerializationFormat remotingFormat)
{
info.AddValue("DataTable.RemotingVersion", new Version(2, 0));
// SqlHotFix 299, SerializationFormat enumeration types don't exist in V1.1 SP1
if (SerializationFormat.Xml != remotingFormat)
{
info.AddValue("DataTable.RemotingFormat", remotingFormat);
}
if (remotingFormat != SerializationFormat.Xml)
{
//Binary
SerializeTableSchema(info, isSingleTable);
if (isSingleTable)
{
SerializeTableData(info, 0);
}
}
else
{
//XML/V1.0/V1.1
string tempDSNamespace = string.Empty;
bool fCreatedDataSet = false;
if (_dataSet == null)
{
DataSet ds = new DataSet("tmpDataSet");
// if user set values on DataTable, it isn't necessary
// to set them on the DataSet because they won't be inherited
// but it is simpler to set them in both places
// if user did not set values on DataTable, it is required
// to set them on the DataSet so the table will inherit
// the value already on the Datatable
ds.SetLocaleValue(_culture, _cultureUserSet);
ds.CaseSensitive = CaseSensitive;
ds._namespaceURI = Namespace;
Debug.Assert(ds.RemotingFormat == SerializationFormat.Xml, "RemotingFormat must be SerializationFormat.Xml");
ds.Tables.Add(this);
fCreatedDataSet = true;
}
else
{
tempDSNamespace = _dataSet.Namespace;
_dataSet._namespaceURI = Namespace;
}
info.AddValue(KEY_XMLSCHEMA, _dataSet!.GetXmlSchemaForRemoting(this));
info.AddValue(KEY_XMLDIFFGRAM, DataSet.GetRemotingDiffGram(this));
if (fCreatedDataSet)
{
_dataSet.Tables.Remove(this);
}
else
{
_dataSet._namespaceURI = tempDSNamespace;
}
}
}
// Deserialize the table schema and data.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
internal void DeserializeDataTable(SerializationInfo info, bool isSingleTable, SerializationFormat remotingFormat)
{
if (remotingFormat != SerializationFormat.Xml)
{
//Binary
DeserializeTableSchema(info, isSingleTable);
if (isSingleTable)
{
DeserializeTableData(info, 0);
ResetIndexes();
}
}
else
{
//XML/V1.0/V1.1
string? strSchema = (string?)info.GetValue(KEY_XMLSCHEMA, typeof(string));
string? strData = (string?)info.GetValue(KEY_XMLDIFFGRAM, typeof(string));
if (strSchema != null)
{
DataSet ds = new DataSet();
ds.ReadXmlSchema(new XmlTextReader(new StringReader(strSchema)));
Debug.Assert(ds.Tables.Count == 1, "There should be exactly 1 table here");
DataTable table = ds.Tables[0];
table.CloneTo(this, null, false);
//this is to avoid the cascading rules in the namespace
Namespace = table.Namespace;
if (strData != null)
{
ds.Tables.Remove(ds.Tables[0]);
ds.Tables.Add(this);
ds.ReadXml(new XmlTextReader(new StringReader(strData)), XmlReadMode.DiffGram);
ds.Tables.Remove(this);
}
}
}
}
// Serialize the columns
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void SerializeTableSchema(SerializationInfo info, bool isSingleTable)
{
//DataTable basic properties
info.AddValue("DataTable.TableName", TableName);
info.AddValue("DataTable.Namespace", Namespace);
info.AddValue("DataTable.Prefix", Prefix);
info.AddValue("DataTable.CaseSensitive", _caseSensitive);
info.AddValue("DataTable.caseSensitiveAmbient", !_caseSensitiveUserSet);
info.AddValue("DataTable.LocaleLCID", Locale.LCID);
info.AddValue("DataTable.MinimumCapacity", _recordManager.MinimumCapacity);
//DataTable state internal properties
info.AddValue("DataTable.NestedInDataSet", _fNestedInDataset);
info.AddValue("DataTable.TypeName", TypeName.ToString());
info.AddValue("DataTable.RepeatableElement", _repeatableElement);
//ExtendedProperties
info.AddValue("DataTable.ExtendedProperties", ExtendedProperties);
//Columns
info.AddValue("DataTable.Columns.Count", Columns.Count);
//Check for closure of expression in case of single table.
if (isSingleTable)
{
List<DataTable> list = new List<DataTable>();
list.Add(this);
if (!CheckForClosureOnExpressionTables(list))
{
throw ExceptionBuilder.CanNotRemoteDataTable();
}
}
IFormatProvider formatProvider = CultureInfo.InvariantCulture;
for (int i = 0; i < Columns.Count; i++)
{
//DataColumn basic properties
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ColumnName", i), Columns[i].ColumnName);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.Namespace", i), Columns[i]._columnUri);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.Prefix", i), Columns[i].Prefix);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ColumnMapping", i), Columns[i].ColumnMapping);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AllowDBNull", i), Columns[i].AllowDBNull);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrement", i), Columns[i].AutoIncrement);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementStep", i), Columns[i].AutoIncrementStep);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementSeed", i), Columns[i].AutoIncrementSeed);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.Caption", i), Columns[i].Caption);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DefaultValue", i), Columns[i].DefaultValue);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ReadOnly", i), Columns[i].ReadOnly);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.MaxLength", i), Columns[i].MaxLength);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DataType_AssemblyQualifiedName", i), Columns[i].DataType.AssemblyQualifiedName);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.XmlDataType", i), Columns[i].XmlDataType);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.SimpleType", i), Columns[i].SimpleType);
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DateTimeMode", i), Columns[i].DateTimeMode);
//DataColumn internal state properties
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementCurrent", i), Columns[i].AutoIncrementCurrent);
//Expression
if (isSingleTable)
{
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.Expression", i), Columns[i].Expression);
}
//ExtendedProperties
info.AddValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ExtendedProperties", i), Columns[i]._extendedProperties);
}
//Constraints
if (isSingleTable)
{
SerializeConstraints(info, 0, false);
}
}
// Deserialize all the Columns
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void DeserializeTableSchema(SerializationInfo info, bool isSingleTable)
{
//DataTable basic properties
_tableName = info.GetString("DataTable.TableName")!;
_tableNamespace = info.GetString("DataTable.Namespace");
_tablePrefix = info.GetString("DataTable.Prefix")!;
bool caseSensitive = info.GetBoolean("DataTable.CaseSensitive");
SetCaseSensitiveValue(caseSensitive, true, false);
_caseSensitiveUserSet = !info.GetBoolean("DataTable.caseSensitiveAmbient");
int lcid = (int)info.GetValue("DataTable.LocaleLCID", typeof(int))!;
CultureInfo culture = new CultureInfo(lcid);
SetLocaleValue(culture, true, false);
_cultureUserSet = true;
MinimumCapacity = info.GetInt32("DataTable.MinimumCapacity");
//DataTable state internal properties
_fNestedInDataset = info.GetBoolean("DataTable.NestedInDataSet");
string tName = info.GetString("DataTable.TypeName")!;
_typeName = new XmlQualifiedName(tName);
_repeatableElement = info.GetBoolean("DataTable.RepeatableElement");
//ExtendedProperties
_extendedProperties = (PropertyCollection?)info.GetValue("DataTable.ExtendedProperties", typeof(PropertyCollection));
//Columns
int colCount = info.GetInt32("DataTable.Columns.Count");
string?[] expressions = new string?[colCount];
Debug.Assert(Columns.Count == 0, "There is column in Table");
IFormatProvider formatProvider = CultureInfo.InvariantCulture;
for (int i = 0; i < colCount; i++)
{
DataColumn dc = new DataColumn();
//DataColumn public state properties
dc.ColumnName = info.GetString(string.Format(formatProvider, "DataTable.DataColumn_{0}.ColumnName", i));
dc._columnUri = info.GetString(string.Format(formatProvider, "DataTable.DataColumn_{0}.Namespace", i));
dc.Prefix = info.GetString(string.Format(formatProvider, "DataTable.DataColumn_{0}.Prefix", i));
string typeName = (string)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DataType_AssemblyQualifiedName", i), typeof(string))!;
dc.DataType = Type.GetType(typeName, throwOnError: true);
dc.XmlDataType = (string?)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.XmlDataType", i), typeof(string));
dc.SimpleType = (SimpleType?)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.SimpleType", i), typeof(SimpleType));
dc.ColumnMapping = (MappingType)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ColumnMapping", i), typeof(MappingType))!;
dc.DateTimeMode = (DataSetDateTime)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DateTimeMode", i), typeof(DataSetDateTime))!;
dc.AllowDBNull = info.GetBoolean(string.Format(formatProvider, "DataTable.DataColumn_{0}.AllowDBNull", i));
dc.AutoIncrement = info.GetBoolean(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrement", i));
dc.AutoIncrementStep = info.GetInt64(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementStep", i));
dc.AutoIncrementSeed = info.GetInt64(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementSeed", i));
dc.Caption = info.GetString(string.Format(formatProvider, "DataTable.DataColumn_{0}.Caption", i));
dc.DefaultValue = info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.DefaultValue", i), typeof(object));
dc.ReadOnly = info.GetBoolean(string.Format(formatProvider, "DataTable.DataColumn_{0}.ReadOnly", i));
dc.MaxLength = info.GetInt32(string.Format(formatProvider, "DataTable.DataColumn_{0}.MaxLength", i));
//DataColumn internal state properties
dc.AutoIncrementCurrent = info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.AutoIncrementCurrent", i), typeof(object))!;
//Expression
if (isSingleTable)
{
expressions[i] = info.GetString(string.Format(formatProvider, "DataTable.DataColumn_{0}.Expression", i));
}
//ExtendedProperties
dc._extendedProperties = (PropertyCollection?)info.GetValue(string.Format(formatProvider, "DataTable.DataColumn_{0}.ExtendedProperties", i), typeof(PropertyCollection));
Columns.Add(dc);
}
if (isSingleTable)
{
for (int i = 0; i < colCount; i++)
{
if (expressions[i] != null)
{
Columns[i].Expression = expressions[i];
}
}
}
//Constraints
if (isSingleTable)
{
DeserializeConstraints(info, /*table index */ 0, /* serialize all constraints */false); // since single table, send table index as 0, meanwhile passing
// false for 'allConstraints' means, handle all the constraint related to the table
}
}
// Serialize constraints available on the table - note this function is marked internal because it is called by the DataSet deserializer.
// ***Schema for Serializing ArrayList of Constraints***
// Unique Constraint - ["U"]->[constraintName]->[columnIndexes]->[IsPrimaryKey]->[extendedProperties]
// Foriegn Key Constraint - ["F"]->[constraintName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[AcceptRejectRule, UpdateRule, DeleteRule]->[extendedProperties]
internal void SerializeConstraints(SerializationInfo info, int serIndex, bool allConstraints)
{
if (allConstraints)
{
Debug.Assert(DataSet != null);
}
ArrayList constraintList = new ArrayList();
for (int i = 0; i < Constraints.Count; i++)
{
Constraint c = Constraints[i];
UniqueConstraint? uc = c as UniqueConstraint;
if (uc != null)
{
int[] colInfo = new int[uc.Columns.Length];
for (int j = 0; j < colInfo.Length; j++)
{
colInfo[j] = uc.Columns[j].Ordinal;
}
ArrayList list = new ArrayList();
list.Add("U");
list.Add(uc.ConstraintName);
list.Add(colInfo);
list.Add(uc.IsPrimaryKey);
list.Add(uc.ExtendedProperties);
constraintList.Add(list);
}
else
{
ForeignKeyConstraint? fk = c as ForeignKeyConstraint;
Debug.Assert(fk != null);
bool shouldSerialize = allConstraints || (fk.Table == this && fk.RelatedTable == this);
if (shouldSerialize)
{
int[] parentInfo = new int[fk.RelatedColumns.Length + 1];
parentInfo[0] = allConstraints ? DataSet!.Tables.IndexOf(fk.RelatedTable) : 0;
for (int j = 1; j < parentInfo.Length; j++)
{
parentInfo[j] = fk.RelatedColumns[j - 1].Ordinal;
}
int[] childInfo = new int[fk.Columns.Length + 1];
childInfo[0] = allConstraints ? DataSet!.Tables.IndexOf(fk.Table) : 0; //Since the constraint is on the current table, this is the child table.
for (int j = 1; j < childInfo.Length; j++)
{
childInfo[j] = fk.Columns[j - 1].Ordinal;
}
ArrayList list = new ArrayList();
list.Add("F");
list.Add(fk.ConstraintName);
list.Add(parentInfo);
list.Add(childInfo);
list.Add(new int[] { (int)fk.AcceptRejectRule, (int)fk.UpdateRule, (int)fk.DeleteRule });
list.Add(fk.ExtendedProperties);
constraintList.Add(list);
}
}
}
info.AddValue(string.Format(CultureInfo.InvariantCulture, "DataTable_{0}.Constraints", serIndex), constraintList);
}
// Deserialize the constraints on the table.
// ***Schema for Serializing ArrayList of Constraints***
// Unique Constraint - ["U"]->[constraintName]->[columnIndexes]->[IsPrimaryKey]->[extendedProperties]
// Foriegn Key Constraint - ["F"]->[constraintName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[AcceptRejectRule, UpdateRule, DeleteRule]->[extendedProperties]
internal void DeserializeConstraints(SerializationInfo info, int serIndex, bool allConstraints)
{
ArrayList constraintList = (ArrayList)info.GetValue(string.Format(CultureInfo.InvariantCulture, "DataTable_{0}.Constraints", serIndex), typeof(ArrayList))!;
foreach (ArrayList list in constraintList)
{
string con = (string)list[0]!;
if (con.Equals("U"))
{
//Unique Constraints
string constraintName = (string)list[1]!;
int[] keyColumnIndexes = (int[])list[2]!;
bool isPrimaryKey = (bool)list[3]!;
PropertyCollection? extendedProperties = (PropertyCollection?)list[4];
DataColumn[] keyColumns = new DataColumn[keyColumnIndexes.Length];
for (int i = 0; i < keyColumnIndexes.Length; i++)
{
keyColumns[i] = Columns[keyColumnIndexes[i]];
}
//Create the constraint.
UniqueConstraint uc = new UniqueConstraint(constraintName, keyColumns, isPrimaryKey);
uc._extendedProperties = extendedProperties;
//Add the unique constraint and it will in turn set the primary keys also if needed.
Constraints.Add(uc);
}
else
{
//ForeignKeyConstraints
Debug.Assert(con.Equals("F"));
string constraintName = (string)list[1]!;
int[] parentInfo = (int[])list[2]!;
int[] childInfo = (int[])list[3]!;
int[] rules = (int[])list[4]!;
PropertyCollection? extendedProperties = (PropertyCollection?)list[5];
//ParentKey Columns.
DataTable parentTable = (allConstraints == false) ? this : DataSet!.Tables[parentInfo[0]];
DataColumn[] parentkeyColumns = new DataColumn[parentInfo.Length - 1];
for (int i = 0; i < parentkeyColumns.Length; i++)
{
parentkeyColumns[i] = parentTable.Columns[parentInfo[i + 1]];
}
//ChildKey Columns.
DataTable childTable = (allConstraints == false) ? this : DataSet!.Tables[childInfo[0]];
DataColumn[] childkeyColumns = new DataColumn[childInfo.Length - 1];
for (int i = 0; i < childkeyColumns.Length; i++)
{
childkeyColumns[i] = childTable.Columns[childInfo[i + 1]];
}
//Create the Constraint.
ForeignKeyConstraint fk = new ForeignKeyConstraint(constraintName, parentkeyColumns, childkeyColumns);
fk.AcceptRejectRule = (AcceptRejectRule)rules[0];
fk.UpdateRule = (Rule)rules[1];
fk.DeleteRule = (Rule)rules[2];
fk._extendedProperties = extendedProperties;
//Add just the foreign key constraint without creating unique constraint.
Constraints.Add(fk, false);
}
}
}
// Serialize the expressions on the table - Marked internal so that DataSet deserializer can call into this
internal void SerializeExpressionColumns(SerializationInfo info, int serIndex)
{
int colCount = Columns.Count;
for (int i = 0; i < colCount; i++)
{
info.AddValue(string.Format(CultureInfo.InvariantCulture, "DataTable_{0}.DataColumn_{1}.Expression", serIndex, i), Columns[i].Expression);
}
}
// Deserialize the expressions on the table - Marked internal so that DataSet deserializer can call into this
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void DeserializeExpressionColumns(SerializationInfo info, int serIndex)
{
int colCount = Columns.Count;
for (int i = 0; i < colCount; i++)
{
string expr = info.GetString(string.Format(CultureInfo.InvariantCulture, "DataTable_{0}.DataColumn_{1}.Expression", serIndex, i))!;
if (0 != expr.Length)
{
Columns[i].Expression = expr;
}
}
}
// Serialize all the Rows.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void SerializeTableData(SerializationInfo info, int serIndex)
{
//Cache all the column count, row count
int colCount = Columns.Count;
int rowCount = Rows.Count;
int modifiedRowCount = 0;
int editRowCount = 0;
//Compute row states and assign the bits accordingly - 00[Unchanged], 01[Added], 10[Modifed], 11[Deleted]
BitArray rowStates = new BitArray(rowCount * 3, false); //All bit flags are set to false on initialization of the BitArray.
for (int i = 0; i < rowCount; i++)
{
int bitIndex = i * 3;
DataRow row = Rows[i];
DataRowState rowState = row.RowState;
switch (rowState)
{
case DataRowState.Unchanged:
//rowStates[bitIndex] = false;
//rowStates[bitIndex + 1] = false;
break;
case DataRowState.Added:
//rowStates[bitIndex] = false;
rowStates[bitIndex + 1] = true;
break;
case DataRowState.Modified:
rowStates[bitIndex] = true;
//rowStates[bitIndex + 1] = false;
modifiedRowCount++;
break;
case DataRowState.Deleted:
rowStates[bitIndex] = true;
rowStates[bitIndex + 1] = true;
break;
default:
throw ExceptionBuilder.InvalidRowState(rowState);
}
if (-1 != row._tempRecord)
{
rowStates[bitIndex + 2] = true;
editRowCount++;
}
}
//Compute the actual storage records that need to be created.
int recordCount = rowCount + modifiedRowCount + editRowCount;
//Create column storages.
ArrayList storeList = new ArrayList();
ArrayList nullbitList = new ArrayList();
if (recordCount > 0)
{
//Create the storage only if have records.
for (int i = 0; i < colCount; i++)
{
object store = Columns[i].GetEmptyColumnStore(recordCount);
storeList.Add(store);
BitArray nullbits = new BitArray(recordCount);
nullbitList.Add(nullbits);
}
}
//Copy values into column storages
int recordsConsumed = 0;
Hashtable rowErrors = new Hashtable();
Hashtable colErrors = new Hashtable();
for (int i = 0; i < rowCount; i++)
{
int recordsPerRow = Rows[i].CopyValuesIntoStore(storeList, nullbitList, recordsConsumed);
GetRowAndColumnErrors(i, rowErrors, colErrors);
recordsConsumed += recordsPerRow;
}
IFormatProvider formatProvider = CultureInfo.InvariantCulture;
//Serialize all the computed values.
info.AddValue(string.Format(formatProvider, "DataTable_{0}.Rows.Count", serIndex), rowCount);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.Records.Count", serIndex), recordCount);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.RowStates", serIndex), rowStates);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.Records", serIndex), storeList);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.NullBits", serIndex), nullbitList);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.RowErrors", serIndex), rowErrors);
info.AddValue(string.Format(formatProvider, "DataTable_{0}.ColumnErrors", serIndex), colErrors);
}
// Deserialize all the Rows.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void DeserializeTableData(SerializationInfo info, int serIndex)
{
bool enforceConstraintsOrg = _enforceConstraints;
bool inDataLoadOrg = _inDataLoad;
try
{
_enforceConstraints = false;
_inDataLoad = true;
IFormatProvider formatProvider = CultureInfo.InvariantCulture;
int rowCount = info.GetInt32(string.Format(formatProvider, "DataTable_{0}.Rows.Count", serIndex));
int recordCount = info.GetInt32(string.Format(formatProvider, "DataTable_{0}.Records.Count", serIndex));
BitArray rowStates = (BitArray)info.GetValue(string.Format(formatProvider, "DataTable_{0}.RowStates", serIndex), typeof(BitArray))!;
ArrayList storeList = (ArrayList)info.GetValue(string.Format(formatProvider, "DataTable_{0}.Records", serIndex), typeof(ArrayList))!;
ArrayList nullbitList = (ArrayList)info.GetValue(string.Format(formatProvider, "DataTable_{0}.NullBits", serIndex), typeof(ArrayList))!;
Hashtable rowErrors = (Hashtable)info.GetValue(string.Format(formatProvider, "DataTable_{0}.RowErrors", serIndex), typeof(Hashtable))!;
rowErrors.OnDeserialization(this); //OnDeSerialization must be called since the hashtable gets deserialized after the whole graph gets deserialized
Hashtable colErrors = (Hashtable)info.GetValue(string.Format(formatProvider, "DataTable_{0}.ColumnErrors", serIndex), typeof(Hashtable))!;
colErrors.OnDeserialization(this); //OnDeSerialization must be called since the hashtable gets deserialized after the whole graph gets deserialized
if (recordCount <= 0)
{
//No need for deserialization of the storage and errors if there are no records.
return;
}
//Point the record manager storage to the deserialized values.
for (int i = 0; i < Columns.Count; i++)
{
Columns[i].SetStorage(storeList[i]!, (BitArray)nullbitList[i]!);
}
//Create rows and set the records appropriately.
int recordIndex = 0;
DataRow[] rowArr = new DataRow[recordCount];
for (int i = 0; i < rowCount; i++)
{
//Create a new row which sets old and new records to -1.
DataRow row = NewEmptyRow();
rowArr[recordIndex] = row;
int bitIndex = i * 3;
switch (ConvertToRowState(rowStates, bitIndex))
{
case DataRowState.Unchanged:
row._oldRecord = recordIndex;
row._newRecord = recordIndex;
recordIndex += 1;
break;
case DataRowState.Added:
row._oldRecord = -1;
row._newRecord = recordIndex;
recordIndex += 1;
break;
case DataRowState.Modified:
row._oldRecord = recordIndex;
row._newRecord = recordIndex + 1;
rowArr[recordIndex + 1] = row;
recordIndex += 2;
break;
case DataRowState.Deleted:
row._oldRecord = recordIndex;
row._newRecord = -1;
recordIndex += 1;
break;
}
if (rowStates[bitIndex + 2])
{
row._tempRecord = recordIndex;
rowArr[recordIndex] = row;
recordIndex += 1;
}
else
{
row._tempRecord = -1;
}
Rows.ArrayAdd(row);
row.rowID = _nextRowID;
_nextRowID++;
ConvertToRowError(i, rowErrors, colErrors);
}
_recordManager.SetRowCache(rowArr);
ResetIndexes();
}
finally
{
_enforceConstraints = enforceConstraintsOrg;
_inDataLoad = inDataLoadOrg;
}
}
// Constructs the RowState from the two bits in the bitarray.
private static DataRowState ConvertToRowState(BitArray bitStates, int bitIndex)
{
Debug.Assert(bitStates != null);
Debug.Assert(bitStates.Length > bitIndex);
bool b1 = bitStates[bitIndex];
bool b2 = bitStates[bitIndex + 1];
if (!b1 && !b2)
{
return DataRowState.Unchanged;
}
else if (!b1 && b2)
{
return DataRowState.Added;
}
else if (b1 && !b2)
{
return DataRowState.Modified;
}
else if (b1 && b2)
{
return DataRowState.Deleted;
}
else
{
throw ExceptionBuilder.InvalidRowBitPattern();
}
}
// Get the error on the row and columns - Marked internal so that DataSet deserializer can call into this
internal void GetRowAndColumnErrors(int rowIndex, Hashtable rowErrors, Hashtable colErrors)
{
Debug.Assert(Rows.Count > rowIndex);
Debug.Assert(rowErrors != null);
Debug.Assert(colErrors != null);
DataRow row = Rows[rowIndex];
if (row.HasErrors)
{
rowErrors.Add(rowIndex, row.RowError);
DataColumn[] dcArr = row.GetColumnsInError();
if (dcArr.Length > 0)
{
int[] columnsInError = new int[dcArr.Length];
string[] columnErrors = new string[dcArr.Length];
for (int i = 0; i < dcArr.Length; i++)
{
columnsInError[i] = dcArr[i].Ordinal;
columnErrors[i] = row.GetColumnError(dcArr[i]);
}
ArrayList list = new ArrayList();
list.Add(columnsInError);
list.Add(columnErrors);
colErrors.Add(rowIndex, list);
}
}
}
// Set the row and columns in error..
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void ConvertToRowError(int rowIndex, Hashtable rowErrors, Hashtable colErrors)
{
Debug.Assert(Rows.Count > rowIndex);
Debug.Assert(rowErrors != null);
Debug.Assert(colErrors != null);
DataRow row = Rows[rowIndex];
if (rowErrors.ContainsKey(rowIndex))
{
row.RowError = (string)rowErrors[rowIndex]!;
}
if (colErrors.ContainsKey(rowIndex))
{
ArrayList list = (ArrayList)colErrors[rowIndex]!;
int[] columnsInError = (int[])list[0]!;
string[] columnErrors = (string[])list[1]!;
Debug.Assert(columnsInError.Length == columnErrors.Length);
for (int i = 0; i < columnsInError.Length; i++)
{
row.SetColumnError(columnsInError[i], columnErrors[i]);
}
}
}
/// <summary>
/// Indicates whether string comparisons within the table are case-sensitive.
/// </summary>
public bool CaseSensitive
{
get { return _caseSensitive; }
set
{
if (_caseSensitive != value)
{
bool oldValue = _caseSensitive;
bool oldUserSet = _caseSensitiveUserSet;
_caseSensitive = value;
_caseSensitiveUserSet = true;
if (DataSet != null && !DataSet.ValidateCaseConstraint())
{
_caseSensitive = oldValue;
_caseSensitiveUserSet = oldUserSet;
throw ExceptionBuilder.CannotChangeCaseLocale();
}
SetCaseSensitiveValue(value, true, true);
}
_caseSensitiveUserSet = true;
}
}
internal bool AreIndexEventsSuspended => 0 < _suspendIndexEvents;
internal void RestoreIndexEvents(bool forceReset)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.RestoreIndexEvents|Info> {0}, {1}", ObjectID, _suspendIndexEvents);
if (0 < _suspendIndexEvents)
{
_suspendIndexEvents--;
if (0 == _suspendIndexEvents)
{
Exception? first = null;
SetShadowIndexes();
try
{
// the length of shadowIndexes will not change
// but the array instance may change during
// events during Index.Reset
int numIndexes = _shadowIndexes!.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
try
{
if (forceReset || ndx.HasRemoteAggregate)
{
ndx.Reset(); // resets & fires
}
else
{
ndx.FireResetEvent(); // fire the Reset event we were firing
}
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ExceptionBuilder.TraceExceptionWithoutRethrow(e);
if (null == first)
{
first = e;
}
}
}
if (null != first)
{
throw first;
}
}
finally
{
RestoreShadowIndexes();
}
}
}
}
internal void SuspendIndexEvents()
{
DataCommonEventSource.Log.Trace("<ds.DataTable.SuspendIndexEvents|Info> {0}, {1}", ObjectID, _suspendIndexEvents);
_suspendIndexEvents++;
}
[Browsable(false)]
public bool IsInitialized => !fInitInProgress;
private bool IsTypedDataTable
{
get
{
switch (_isTypedDataTable)
{
case 0:
_isTypedDataTable = (byte)((GetType() != typeof(DataTable)) ? 1 : 2);
return (1 == _isTypedDataTable);
case 1:
return true;
default:
return false;
}
}
}
internal bool SetCaseSensitiveValue(bool isCaseSensitive, bool userSet, bool resetIndexes)
{
if (userSet || (!_caseSensitiveUserSet && (_caseSensitive != isCaseSensitive)))
{
_caseSensitive = isCaseSensitive;
if (isCaseSensitive)
{
_compareFlags = CompareOptions.None;
}
else
{
_compareFlags = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth;
}
if (resetIndexes)
{
ResetIndexes();
foreach (Constraint constraint in Constraints)
{
constraint.CheckConstraint();
}
}
return true;
}
return false;
}
private void ResetCaseSensitive()
{
// this method is used design-time scenarios via reflection
// by the property grid context menu to show the Reset option or not
SetCaseSensitiveValue((null != _dataSet) && _dataSet.CaseSensitive, true, true);
_caseSensitiveUserSet = false;
}
internal bool ShouldSerializeCaseSensitive()
{
// this method is used design-time scenarios via reflection
// by the property grid to show the CaseSensitive property in bold or not
// by the code dom for persisting the CaseSensitive property or not
return _caseSensitiveUserSet;
}
internal bool SelfNested
{
get
{
// Is this correct? if ((top[i].nestedParentRelation!= null) && (top[i].nestedParentRelation.ParentTable == top[i]))
foreach (DataRelation rel in ParentRelations)
{
if (rel.Nested && rel.ParentTable == this)
{
return true;
}
}
return false;
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
internal List<Index> LiveIndexes
{
get
{
if (!AreIndexEventsSuspended)
{
for (int i = _indexes.Count - 1; 0 <= i; --i)
{
Index index = _indexes[i];
if (index.RefCount <= 1)
{
index.RemoveRef();
}
}
}
return _indexes;
}
}
[DefaultValue(SerializationFormat.Xml)]
public SerializationFormat RemotingFormat
{
get { return _remotingFormat; }
set
{
switch (value)
{
case SerializationFormat.Xml:
break;
case SerializationFormat.Binary:
if (LocalAppContextSwitches.AllowUnsafeSerializationFormatBinary)
{
break;
}
throw ExceptionBuilder.SerializationFormatBinaryNotSupported();
default:
throw ExceptionBuilder.InvalidRemotingFormat(value);
}
// table can not have different format than its dataset, unless it is stand alone datatable
if (DataSet != null && value != DataSet.RemotingFormat)
{
throw ExceptionBuilder.CanNotSetRemotingFormat();
}
_remotingFormat = value;
}
}
// used to keep temporary state of unique Key posiotion to be added for inference only
internal int UKColumnPositionForInference
{
get { return _ukColumnPositionForInference; }
set { _ukColumnPositionForInference = value; }
}
/// <summary>
/// Gets the collection of child relations for this <see cref='System.Data.DataTable'/>.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DataRelationCollection ChildRelations =>
_childRelationsCollection ??= new DataRelationCollection.DataTableRelationCollection(this, false);
/// <summary>
/// Gets the collection of columns that belong to this table.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DataColumnCollection Columns => _columnCollection;
private void ResetColumns()
{
// this method is used design-time scenarios via reflection
// by the property grid context menu to show the Reset option or not
Columns.Clear();
}
private CompareInfo CompareInfo => _compareInfo ??= Locale.CompareInfo;
/// <summary>
/// Gets the collection of constraints maintained by this table.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ConstraintCollection Constraints => _constraintCollection;
/// <summary>
/// Resets the <see cref='System.Data.DataTable.Constraints'/> property to its default state.
/// </summary>
private void ResetConstraints() => Constraints.Clear();
/// <summary>
/// Gets the <see cref='System.Data.DataSet'/> that this table belongs to.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
public DataSet? DataSet => _dataSet;
/// <summary>
/// Internal method for setting the DataSet pointer.
/// </summary>
internal void SetDataSet(DataSet? dataSet)
{
if (_dataSet != dataSet)
{
_dataSet = dataSet;
if (DataSet != null)
{
_defaultView = null;
}
//Set the remoting format variable directly
if (dataSet != null)
{
_remotingFormat = dataSet.RemotingFormat;
}
}
}
/// <summary>
/// Gets a customized view of the table which may include a
/// filtered view, or a cursor position.
/// </summary>
[Browsable(false)]
public DataView DefaultView
{
get
{
DataView? view = _defaultView;
if (null == view)
{
if (null != _dataSet)
{
view = _dataSet.DefaultViewManager.CreateDataView(this);
}
else
{
view = new DataView(this, true);
view.SetIndex2("", DataViewRowState.CurrentRows, null, true);
}
view = Interlocked.CompareExchange(ref _defaultView, view, null);
if (null == view)
{
view = _defaultView;
}
}
return view;
}
}
/// <summary>
/// Gets or sets the expression that will return a value used to represent
/// this table in UI.
/// </summary>
[DefaultValue("")]
[AllowNull]
public string DisplayExpression
{
get { return DisplayExpressionInternal; }
[RequiresUnreferencedCode("Members from types used in the expressions may be trimmed if not referenced directly.")]
set
{
_displayExpression = !string.IsNullOrEmpty(value) ?
new DataExpression(this, value) :
null;
}
}
internal string DisplayExpressionInternal => _displayExpression != null ? _displayExpression.Expression : string.Empty;
internal bool EnforceConstraints
{
get
{
if (SuspendEnforceConstraints)
{
return false;
}
if (_dataSet != null)
{
return _dataSet.EnforceConstraints;
}
return _enforceConstraints;
}
set
{
if (_dataSet == null && _enforceConstraints != value)
{
if (value)
{
EnableConstraints();
}
_enforceConstraints = value;
}
}
}
internal bool SuspendEnforceConstraints
{
get { return _suspendEnforceConstraints; }
set { _suspendEnforceConstraints = value; }
}
internal void EnableConstraints()
{
bool errors = false;
foreach (Constraint constr in Constraints)
{
if (constr is UniqueConstraint)
{
errors |= constr.IsConstraintViolated();
}
}
foreach (DataColumn column in Columns)
{
if (!column.AllowDBNull)
{
errors |= column.IsNotAllowDBNullViolated();
}
if (column.MaxLength >= 0)
{
errors |= column.IsMaxLengthViolated();
}
}
if (errors)
{
EnforceConstraints = false;
throw ExceptionBuilder.EnforceConstraint();
}
}
/// <summary>
/// Gets the collection of customized user information.
/// </summary>
[Browsable(false)]
public PropertyCollection ExtendedProperties => _extendedProperties ??= new PropertyCollection();
internal IFormatProvider FormatProvider
{
get
{
// used for Formating/Parsing
// https://learn.microsoft.com/dotnet/api/system.globalization.cultureinfo.isneutralculture
if (null == _formatProvider)
{
CultureInfo culture = Locale;
if (culture.IsNeutralCulture)
{
culture = CultureInfo.InvariantCulture;
}
_formatProvider = culture;
}
return _formatProvider;
}
}
/// <summary>
/// Gets a value indicating whether there are errors in any of the rows in any of
/// the tables of the <see cref='System.Data.DataSet'/> to which the table belongs.
/// </summary>
[Browsable(false)]
public bool HasErrors
{
get
{
for (int i = 0; i < Rows.Count; i++)
{
if (Rows[i].HasErrors)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Gets or sets the locale information used to compare strings within the table.
/// Also used for locale sensitive, case,kana,width insensitive column name lookups
/// Also used for converting values to and from string
/// </summary>
public CultureInfo Locale
{
get
{
// used for Comparing not Formatting/Parsing
Debug.Assert(null != _culture, "null culture");
return _culture;
}
set
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.set_Locale|API> {0}", ObjectID);
try
{
bool userSet = true;
if (null == value)
{
// reset Locale to inherit from DataSet
userSet = false;
value = (null != _dataSet) ? _dataSet.Locale : _culture;
}
if (_culture != value && !_culture.Equals(value))
{
bool flag = false;
bool exceptionThrown = false;
CultureInfo oldLocale = _culture;
bool oldUserSet = _cultureUserSet;
try
{
_cultureUserSet = true;
SetLocaleValue(value, true, false);
if ((null == DataSet) || DataSet.ValidateLocaleConstraint())
{
flag = false;
SetLocaleValue(value, true, true);
flag = true;
}
}
catch
{
exceptionThrown = true;
throw;
}
finally
{
if (!flag)
{
// reset old locale if ValidationFailed or exception thrown
try
{
SetLocaleValue(oldLocale, true, true);
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
// failed to reset all indexes for all constraints
ADP.TraceExceptionWithoutRethrow(e);
}
_cultureUserSet = oldUserSet;
if (!exceptionThrown)
{
throw ExceptionBuilder.CannotChangeCaseLocale(null);
}
}
}
SetLocaleValue(value, true, true);
}
_cultureUserSet = userSet;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
}
internal bool SetLocaleValue(CultureInfo culture, bool userSet, bool resetIndexes)
{
Debug.Assert(null != culture, "SetLocaleValue: no locale");
if (userSet || resetIndexes || (!_cultureUserSet && !_culture.Equals(culture)))
{
_culture = culture;
_compareInfo = null;
_formatProvider = null;
_hashCodeProvider = null;
foreach (DataColumn column in Columns)
{
column._hashCode = GetSpecialHashCode(column.ColumnName);
}
if (resetIndexes)
{
ResetIndexes();
foreach (Constraint constraint in Constraints)
{
constraint.CheckConstraint();
}
}
return true;
}
return false;
}
internal bool ShouldSerializeLocale()
{
// this method is used design-time scenarios via reflection
// by the property grid to show the Locale property in bold or not
// by the code dom for persisting the Locale property or not
// we always want the locale persisted if set by user or different the current thread if standalone table
// but that logic should by performed by the serializion code
return _cultureUserSet;
}
/// <summary>
/// Gets or sets the initial starting size for this table.
/// </summary>
[DefaultValue(50)]
public int MinimumCapacity
{
get { return _recordManager.MinimumCapacity; }
set
{
if (value != _recordManager.MinimumCapacity)
{
_recordManager.MinimumCapacity = value;
}
}
}
internal int RecordCapacity => _recordManager.RecordCapacity;
internal int ElementColumnCount
{
get { return _elementColumnCount; }
set
{
if ((value > 0) && (_xmlText != null))
{
throw ExceptionBuilder.TableCannotAddToSimpleContent();
}
else
{
_elementColumnCount = value;
}
}
}
/// <summary>
/// Gets the collection of parent relations for this <see cref='System.Data.DataTable'/>.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DataRelationCollection ParentRelations => _parentRelationsCollection ??= new DataRelationCollection.DataTableRelationCollection(this, true);
internal bool MergingData
{
get { return _mergingData; }
set { _mergingData = value; }
}
internal DataRelation[] NestedParentRelations
{
get
{
#if DEBUG
DataRelation[] nRel = FindNestedParentRelations();
Debug.Assert(nRel.Length == _nestedParentRelations.Length, "nestedParent cache is broken");
for (int i = 0; i < nRel.Length; i++)
{
Debug.Assert(null != nRel[i], "null relation");
Debug.Assert(null != _nestedParentRelations[i], "null relation");
Debug.Assert(nRel[i] == _nestedParentRelations[i], "unequal relations");
}
#endif
return _nestedParentRelations;
}
}
internal void CacheNestedParent()
{
_nestedParentRelations = FindNestedParentRelations();
}
private DataRelation[] FindNestedParentRelations()
{
List<DataRelation>? nestedParents = null;
foreach (DataRelation relation in ParentRelations)
{
if (relation.Nested)
{
if (null == nestedParents)
{
nestedParents = new List<DataRelation>();
}
nestedParents.Add(relation);
}
}
return (null == nestedParents) || (nestedParents.Count == 0) ?
Array.Empty<DataRelation>() :
nestedParents.ToArray();
}
internal int NestedParentsCount
{
get
{
int count = 0;
foreach (DataRelation relation in ParentRelations)
{
if (relation.Nested)
{
count++;
}
}
return count;
}
}
/// <summary>
/// Gets or sets an array of columns that function as primary keys for the data table.
/// </summary>
[Editor("Microsoft.VSDesigner.Data.Design.PrimaryKeyEditor, Microsoft.VSDesigner, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
[TypeConverter(typeof(PrimaryKeyTypeConverter))]
[AllowNull]
public DataColumn[] PrimaryKey
{
get
{
UniqueConstraint? primayKeyConstraint = _primaryKey;
if (null != primayKeyConstraint)
{
Debug.Assert(2 <= primayKeyConstraint.ConstraintIndex.RefCount, "bad primaryKey index RefCount");
return primayKeyConstraint.Key.ToArray();
}
return Array.Empty<DataColumn>();
}
set
{
UniqueConstraint? key = null;
UniqueConstraint? existingKey;
// Loading with persisted property
if (fInitInProgress && value != null)
{
_delayedSetPrimaryKey = value;
return;
}
if ((value != null) && (value.Length != 0))
{
int count = 0;
for (int i = 0; i < value.Length; i++)
{
if (value[i] != null)
{
count++;
}
else
{
break;
}
}
if (count != 0)
{
DataColumn[] newValue = value;
if (count != value.Length)
{
newValue = new DataColumn[count];
for (int i = 0; i < count; i++)
{
newValue[i] = value[i];
}
}
key = new UniqueConstraint(newValue);
if (key.Table != this)
throw ExceptionBuilder.TableForeignPrimaryKey();
}
}
if (key == _primaryKey || (key != null && key.Equals(_primaryKey)))
{
return;
}
// Use an existing UniqueConstraint that matches if one exists
if ((existingKey = (UniqueConstraint?)Constraints.FindConstraint(key)) != null)
{
key!.ColumnsReference.CopyTo(existingKey.Key.ColumnsReference, 0);
key = existingKey;
}
UniqueConstraint? oldKey = _primaryKey;
_primaryKey = null;
if (oldKey != null)
{
oldKey.ConstraintIndex.RemoveRef();
// if PrimaryKey is removed, reset LoadDataRow indexes
if (null != _loadIndex)
{
_loadIndex.RemoveRef();
_loadIndex = null;
}
if (null != _loadIndexwithOriginalAdded)
{
_loadIndexwithOriginalAdded.RemoveRef();
_loadIndexwithOriginalAdded = null;
}
if (null != _loadIndexwithCurrentDeleted)
{
_loadIndexwithCurrentDeleted.RemoveRef();
_loadIndexwithCurrentDeleted = null;
}
Constraints.Remove(oldKey);
}
// Add the key if there isnt an existing matching key in collection
if (key != null && existingKey == null)
{
Constraints.Add(key);
}
_primaryKey = key;
Debug.Assert(_primaryKey == null || Constraints.FindConstraint(_primaryKey) == _primaryKey, "PrimaryKey is not in ConstraintCollection");
_primaryIndex = (key != null) ? key.Key.GetIndexDesc() : Array.Empty<IndexField>();
if (_primaryKey != null)
{
// must set index for DataView.Sort before setting AllowDBNull which can fail
key!.ConstraintIndex.AddRef();
for (int i = 0; i < key.ColumnsReference.Length; i++)
{
key.ColumnsReference[i].AllowDBNull = false;
}
}
}
}
/// <summary>
/// Indicates whether the <see cref='System.Data.DataTable.PrimaryKey'/> property should be persisted.
/// </summary>
private bool ShouldSerializePrimaryKey() => _primaryKey != null;
/// <summary>
/// Resets the <see cref='System.Data.DataTable.PrimaryKey'/> property to its default state.
/// </summary>
private void ResetPrimaryKey()
{
PrimaryKey = null;
}
/// <summary>
/// Gets the collection of rows that belong to this table.
/// </summary>
[Browsable(false)]
public DataRowCollection Rows => _rowCollection;
/// <summary>
/// Gets or sets the name of the table.
/// </summary>
[RefreshProperties(RefreshProperties.All)]
[DefaultValue("")]
[AllowNull]
public string TableName
{
get { return _tableName; }
set
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.set_TableName|API> {0}, value='{1}'", ObjectID, value);
try
{
value ??= string.Empty;
CultureInfo currentLocale = Locale;
if (string.Compare(_tableName, value, true, currentLocale) != 0)
{
if (_dataSet != null)
{
if (value.Length == 0)
{
throw ExceptionBuilder.NoTableName();
}
if ((0 == string.Compare(value, _dataSet.DataSetName, true, _dataSet.Locale)) && !_fNestedInDataset)
{
throw ExceptionBuilder.DatasetConflictingName(_dataSet.DataSetName);
}
DataRelation[] nestedRelations = NestedParentRelations;
if (nestedRelations.Length == 0)
{
_dataSet.Tables.RegisterName(value, Namespace);
}
else
{
foreach (DataRelation rel in nestedRelations)
{
if (!rel.ParentTable.Columns.CanRegisterName(value))
{
throw ExceptionBuilder.CannotAddDuplicate2(value);
}
}
// if it cannot register the following line will throw exception
_dataSet.Tables.RegisterName(value, Namespace);
foreach (DataRelation rel in nestedRelations)
{
rel.ParentTable.Columns.RegisterColumnName(value, null);
rel.ParentTable.Columns.UnregisterName(TableName);
}
}
if (_tableName.Length != 0)
{
_dataSet.Tables.UnregisterName(_tableName);
}
}
RaisePropertyChanging(nameof(TableName));
_tableName = value;
_encodedTableName = null;
}
else if (string.Compare(_tableName, value, false, currentLocale) != 0)
{
RaisePropertyChanging(nameof(TableName));
_tableName = value;
_encodedTableName = null;
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
}
internal string EncodedTableName
{
get
{
string? encodedTblName = _encodedTableName;
if (null == encodedTblName)
{
encodedTblName = XmlConvert.EncodeLocalName(TableName);
_encodedTableName = encodedTblName;
}
return encodedTblName;
}
}
private string GetInheritedNamespace(List<DataTable> visitedTables)
{
// if there is nested relation: ie: this table is nested child of another table and
// if it is not self nested, return parent tables NS: Meanwhile make sure
DataRelation[] nestedRelations = NestedParentRelations;
if (nestedRelations.Length > 0)
{
for (int i = 0; i < nestedRelations.Length; i++)
{
DataRelation rel = nestedRelations[i];
if (rel.ParentTable._tableNamespace != null)
{
return rel.ParentTable._tableNamespace; // if parent table has a non-null NS, return it
}
}
// Assumption, in hierarchy of multiple nested relation, a child table with no NS, has DataRelation
// only and only with parent DataTable witin the same namespace
int j = 0;
while (j < nestedRelations.Length && ((nestedRelations[j].ParentTable == this) || (visitedTables.Contains(nestedRelations[j].ParentTable))))
{
j++;
}
if (j < nestedRelations.Length)
{
DataTable parentTable = nestedRelations[j].ParentTable;
if (!visitedTables.Contains(parentTable))
visitedTables.Add(parentTable);
return parentTable.GetInheritedNamespace(visitedTables); // this is the same as return parentTable.Namespace
}
} // dont put else
if (DataSet != null)
{
// if it cant return from parent tables, return NS from dataset, if exists
return DataSet.Namespace;
}
else
{
return string.Empty;
}
}
/// <summary>
/// Gets or sets the namespace for the <see cref='System.Data.DataTable'/>.
/// </summary>
[AllowNull]
public string Namespace
{
get { return _tableNamespace ?? GetInheritedNamespace(new List<DataTable>()); }
set
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.set_Namespace|API> {0}, value='{1}'", ObjectID, value);
try
{
if (value != _tableNamespace)
{
if (_dataSet != null)
{
string realNamespace = value ?? GetInheritedNamespace(new List<DataTable>());
if (realNamespace != Namespace)
{
// do this extra check only if the namespace is really going to change
// inheritance-wise.
if (_dataSet.Tables.Contains(TableName, realNamespace, true, true))
throw ExceptionBuilder.DuplicateTableName2(TableName, realNamespace);
CheckCascadingNamespaceConflict(realNamespace);
}
}
CheckNamespaceValidityForNestedRelations(value);
DoRaiseNamespaceChange();
}
_tableNamespace = value;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
}
internal bool IsNamespaceInherited() => null == _tableNamespace;
internal void CheckCascadingNamespaceConflict(string realNamespace)
{
foreach (DataRelation rel in ChildRelations)
{
if ((rel.Nested) && (rel.ChildTable != this) && (rel.ChildTable._tableNamespace == null))
{
DataTable childTable = rel.ChildTable;
if (_dataSet!.Tables.Contains(childTable.TableName, realNamespace, false, true))
throw ExceptionBuilder.DuplicateTableName2(TableName, realNamespace);
childTable.CheckCascadingNamespaceConflict(realNamespace);
}
}
}
internal void CheckNamespaceValidityForNestedRelations(string? realNamespace)
{
foreach (DataRelation rel in ChildRelations)
{
if (rel.Nested)
{
if (realNamespace != null)
{
rel.ChildTable.CheckNamespaceValidityForNestedParentRelations(realNamespace, this);
}
else
{
rel.ChildTable.CheckNamespaceValidityForNestedParentRelations(GetInheritedNamespace(new List<DataTable>()), this);
}
}
}
if (realNamespace == null)
{
// this will affect this table if it has parent relations
CheckNamespaceValidityForNestedParentRelations(GetInheritedNamespace(new List<DataTable>()), this);
}
}
internal void CheckNamespaceValidityForNestedParentRelations(string ns, DataTable parentTable)
{
foreach (DataRelation rel in ParentRelations)
{
if (rel.Nested)
{
if (rel.ParentTable != parentTable && rel.ParentTable.Namespace != ns)
{
throw ExceptionBuilder.InValidNestedRelation(TableName);
}
}
}
}
internal void DoRaiseNamespaceChange()
{
RaisePropertyChanging(nameof(Namespace));
// raise column Namespace change
foreach (DataColumn col in Columns)
{
if (col._columnUri == null)
{
col.RaisePropertyChanging(nameof(Namespace));
}
}
foreach (DataRelation rel in ChildRelations)
{
if ((rel.Nested) && (rel.ChildTable != this))
{
rel.ChildTable.DoRaiseNamespaceChange();
}
}
}
/// <summary>
/// Indicates whether the <see cref='System.Data.DataTable.Namespace'/> property should be persisted.
/// </summary>
private bool ShouldSerializeNamespace() => _tableNamespace != null;
/// <summary>
/// Resets the <see cref='System.Data.DataTable.Namespace'/> property to its default state.
/// </summary>
private void ResetNamespace()
{
Namespace = null;
}
public virtual void BeginInit()
{
fInitInProgress = true;
}
public virtual void EndInit()
{
if (_dataSet == null || !_dataSet._fInitInProgress)
{
Columns.FinishInitCollection();
Constraints.FinishInitConstraints();
foreach (DataColumn dc in Columns)
{
if (dc.Computed)
{
dc.CopyExpressionFrom(dc);
}
}
}
fInitInProgress = false; // It is must that we set off this flag after calling FinishInitxxx();
if (_delayedSetPrimaryKey != null)
{
PrimaryKey = _delayedSetPrimaryKey;
_delayedSetPrimaryKey = null;
}
if (_delayedViews.Count > 0)
{
foreach (DataView dv in _delayedViews)
{
dv.EndInit();
}
_delayedViews.Clear();
}
OnInitialized();
}
[DefaultValue("")]
[AllowNull]
public string Prefix
{
get { return _tablePrefix; }
set
{
value ??= string.Empty;
DataCommonEventSource.Log.Trace("<ds.DataTable.set_Prefix|API> {0}, value='{1}'", ObjectID, value);
if ((XmlConvert.DecodeName(value) == value) && (XmlConvert.EncodeName(value) != value))
{
throw ExceptionBuilder.InvalidPrefix(value);
}
_tablePrefix = value;
}
}
internal DataColumn? XmlText
{
get { return _xmlText; }
set
{
if (_xmlText != value)
{
if (_xmlText != null)
{
if (value != null)
{
throw ExceptionBuilder.MultipleTextOnlyColumns();
}
Columns.Remove(_xmlText);
}
else
{
Debug.Assert(value != null, "Value should not be null ??");
Debug.Assert(value.ColumnMapping == MappingType.SimpleContent, "should be text node here");
if (value != Columns[value.ColumnName])
{
Columns.Add(value);
}
}
_xmlText = value;
}
}
}
internal decimal MaxOccurs
{
get { return _maxOccurs; }
set { _maxOccurs = value; }
}
internal decimal MinOccurs
{
get { return _minOccurs; }
set { _minOccurs = value; }
}
internal static void SetKeyValues(DataKey key, object[] keyValues, int record)
{
for (int i = 0; i < keyValues.Length; i++)
{
key.ColumnsReference[i][record] = keyValues[i];
}
}
internal DataRow? FindByIndex(Index ndx, object[] key)
{
Range range = ndx.FindRecords(key);
return range.IsNull ? null : _recordManager[ndx.GetRecord(range.Min)];
}
internal DataRow? FindMergeTarget(DataRow row, DataKey key, Index ndx)
{
DataRow? targetRow = null;
// Primary key match
if (key.HasValue)
{
Debug.Assert(ndx != null);
int findRecord = (row._oldRecord == -1) ? row._newRecord : row._oldRecord;
object[] values = key.GetKeyValues(findRecord);
targetRow = FindByIndex(ndx, values);
}
return targetRow;
}
private void SetMergeRecords(DataRow row, int newRecord, int oldRecord, DataRowAction action)
{
if (newRecord != -1)
{
SetNewRecord(row, newRecord, action, true, true);
SetOldRecord(row, oldRecord);
}
else
{
SetOldRecord(row, oldRecord);
if (row._newRecord != -1)
{
Debug.Assert(action == DataRowAction.Delete, "Unexpected SetNewRecord action in merge function.");
SetNewRecord(row, newRecord, action, true, true);
}
}
}
internal DataRow MergeRow(DataRow row, DataRow? targetRow, bool preserveChanges, Index? idxSearch)
{
if (targetRow == null)
{
targetRow = NewEmptyRow();
targetRow._oldRecord = _recordManager.ImportRecord(row.Table, row._oldRecord);
targetRow._newRecord = targetRow._oldRecord;
if (row._oldRecord != row._newRecord)
{
targetRow._newRecord = _recordManager.ImportRecord(row.Table, row._newRecord);
}
InsertRow(targetRow, -1);
}
else
{
// Record Manager corruption during Merge when target row in edit state
// the newRecord would be freed and overwrite tempRecord (which became the newRecord)
// this would leave the DataRow referencing a freed record and leaking memory for the now lost record
int proposedRecord = targetRow._tempRecord; // by saving off the tempRecord, EndEdit won't free newRecord
targetRow._tempRecord = -1;
try
{
DataRowState saveRowState = targetRow.RowState;
int saveIdxRecord = (saveRowState == DataRowState.Added) ? targetRow._newRecord : targetRow._oldRecord;
int newRecord;
int oldRecord;
if (targetRow.RowState == DataRowState.Unchanged && row.RowState == DataRowState.Unchanged)
{
// unchanged row merging with unchanged row
oldRecord = targetRow._oldRecord;
newRecord = (preserveChanges) ? _recordManager.CopyRecord(this, oldRecord, -1) : targetRow._newRecord;
oldRecord = _recordManager.CopyRecord(row.Table, row._oldRecord, targetRow._oldRecord);
SetMergeRecords(targetRow, newRecord, oldRecord, DataRowAction.Change);
}
else if (row._newRecord == -1)
{
// Incoming row is deleted
oldRecord = targetRow._oldRecord;
if (preserveChanges)
{
newRecord = (targetRow.RowState == DataRowState.Unchanged) ? _recordManager.CopyRecord(this, oldRecord, -1) : targetRow._newRecord;
}
else
newRecord = -1;
oldRecord = _recordManager.CopyRecord(row.Table, row._oldRecord, oldRecord);
// Change index record, need to update index
if (saveIdxRecord != ((saveRowState == DataRowState.Added) ? newRecord : oldRecord))
{
SetMergeRecords(targetRow, newRecord, oldRecord, (newRecord == -1) ? DataRowAction.Delete : DataRowAction.Change);
idxSearch!.Reset();
saveIdxRecord = ((saveRowState == DataRowState.Added) ? newRecord : oldRecord);
}
else
{
SetMergeRecords(targetRow, newRecord, oldRecord, (newRecord == -1) ? DataRowAction.Delete : DataRowAction.Change);
}
}
else
{
// incoming row is added, modified or unchanged (targetRow is not unchanged)
oldRecord = targetRow._oldRecord;
newRecord = targetRow._newRecord;
if (targetRow.RowState == DataRowState.Unchanged)
{
newRecord = _recordManager.CopyRecord(this, oldRecord, -1);
}
oldRecord = _recordManager.CopyRecord(row.Table, row._oldRecord, oldRecord);
if (!preserveChanges)
{
newRecord = _recordManager.CopyRecord(row.Table, row._newRecord, newRecord);
}
SetMergeRecords(targetRow, newRecord, oldRecord, DataRowAction.Change);
}
if (saveRowState == DataRowState.Added && targetRow._oldRecord != -1)
{
idxSearch!.Reset();
}
Debug.Assert(saveIdxRecord == ((saveRowState == DataRowState.Added) ? targetRow._newRecord : targetRow._oldRecord), "oops, you change index record without noticing it");
}
finally
{
targetRow._tempRecord = proposedRecord;
}
}
// Merge all errors
if (row.HasErrors)
{
if (targetRow.RowError.Length == 0)
{
targetRow.RowError = row.RowError;
}
else
{
targetRow.RowError += " ]:[ " + row.RowError;
}
DataColumn[] cols = row.GetColumnsInError();
for (int i = 0; i < cols.Length; i++)
{
DataColumn col = targetRow.Table.Columns[cols[i].ColumnName]!;
targetRow.SetColumnError(col, row.GetColumnError(cols[i]));
}
}
else
{
if (!preserveChanges)
{
targetRow.ClearErrors();
}
}
return targetRow;
}
/// <summary>
/// Commits all the changes made to this table since the last time <see cref='System.Data.DataTable.AcceptChanges'/> was called.
/// </summary>
public void AcceptChanges()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.AcceptChanges|API> {0}", ObjectID);
try
{
DataRow[] oldRows = new DataRow[Rows.Count];
Rows.CopyTo(oldRows, 0);
// delay updating of indexes until after all
// AcceptChange calls have been completed
SuspendIndexEvents();
try
{
for (int i = 0; i < oldRows.Length; ++i)
{
if (oldRows[i].rowID != -1)
{
oldRows[i].AcceptChanges();
}
}
}
finally
{
RestoreIndexEvents(false);
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
// Prevent inlining so that reflection calls are not moved to caller that may be in a different assembly that may have a different grant set.
[MethodImpl(MethodImplOptions.NoInlining)]
protected virtual DataTable CreateInstance() => (DataTable)Activator.CreateInstance(GetType(), true)!;
public virtual DataTable Clone() => Clone(null);
internal DataTable Clone(DataSet? cloneDS)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Clone|INFO> {0}, cloneDS={1}", ObjectID, (cloneDS != null) ? cloneDS.ObjectID : 0);
try
{
DataTable clone = CreateInstance();
if (clone.Columns.Count > 0) // To clean up all the schema in strong typed dataset.
{
clone.Reset();
}
return CloneTo(clone, cloneDS, false);
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
private static DataTable IncrementalCloneTo(DataTable sourceTable, DataTable targetTable)
{
foreach (DataColumn dc in sourceTable.Columns)
{
if (targetTable.Columns[dc.ColumnName] == null)
{
targetTable.Columns.Add(dc.Clone());
}
}
return targetTable;
}
private static DataTable CloneHierarchy(DataTable sourceTable, DataSet ds, Hashtable? visitedMap)
{
visitedMap ??= new Hashtable();
if (visitedMap.Contains(sourceTable))
{
return ((DataTable)visitedMap[sourceTable]!);
}
DataTable? destinationTable = ds.Tables[sourceTable.TableName, sourceTable.Namespace];
if ((destinationTable != null && destinationTable.Columns.Count > 0))
{
destinationTable = IncrementalCloneTo(sourceTable, destinationTable);
// get extra columns from source into destination , increamental read
}
else
{
if (destinationTable == null)
{
destinationTable = new DataTable();
// fxcop: new DataTable values for CaseSensitive, Locale, Namespace will come from CloneTo
ds.Tables.Add(destinationTable);
}
destinationTable = sourceTable.CloneTo(destinationTable, ds, true);
}
visitedMap[sourceTable] = destinationTable;
// start cloning relation
foreach (DataRelation r in sourceTable.ChildRelations)
{
CloneHierarchy(r.ChildTable, ds, visitedMap);
}
return destinationTable;
}
private DataTable CloneTo(DataTable clone, DataSet? cloneDS, bool skipExpressionColumns)
{
// we do clone datatables while we do readxmlschema, so we do not want to clone columnexpressions if we call this from ReadXmlSchema
// it will cause exception to be thrown in cae expression refers to a table that is not in hirerachy or not created yet
Debug.Assert(clone != null, "The table passed in has to be newly created empty DataTable.");
// set All properties
clone._tableName = _tableName;
clone._tableNamespace = _tableNamespace;
clone._tablePrefix = _tablePrefix;
clone._fNestedInDataset = _fNestedInDataset;
clone._culture = _culture;
clone._cultureUserSet = _cultureUserSet;
clone._compareInfo = _compareInfo;
clone._compareFlags = _compareFlags;
clone._formatProvider = _formatProvider;
clone._hashCodeProvider = _hashCodeProvider;
clone._caseSensitive = _caseSensitive;
clone._caseSensitiveUserSet = _caseSensitiveUserSet;
clone._displayExpression = _displayExpression;
clone._typeName = _typeName; //enzol
clone._repeatableElement = _repeatableElement; //enzol
clone.MinimumCapacity = MinimumCapacity;
clone.RemotingFormat = RemotingFormat;
// add all columns
DataColumnCollection clmns = Columns;
for (int i = 0; i < clmns.Count; i++)
{
clone.Columns.Add(clmns[i].Clone());
}
// add all expressions if Clone is invoked only on DataTable otherwise DataSet.Clone will assign expressions after creating all relationships.
if (!skipExpressionColumns && cloneDS == null)
{
for (int i = 0; i < clmns.Count; i++)
{
clone.Columns[clmns[i].ColumnName]!.CopyExpressionFrom(clmns[i]);
}
}
// Create PrimaryKey
DataColumn[] pkey = PrimaryKey;
if (pkey.Length > 0)
{
DataColumn[] key = new DataColumn[pkey.Length];
for (int i = 0; i < pkey.Length; i++)
{
key[i] = clone.Columns[pkey[i].Ordinal];
}
clone.PrimaryKey = key;
}
// now clone all unique constraints
// Rename first
for (int j = 0; j < Constraints.Count; j++)
{
ForeignKeyConstraint? foreign = Constraints[j] as ForeignKeyConstraint;
UniqueConstraint? unique = Constraints[j] as UniqueConstraint;
if (foreign != null)
{
if (foreign.Table == foreign.RelatedTable)
{
if (foreign.Clone(clone) is ForeignKeyConstraint clonedConstraint &&
clone.Constraints.FindConstraint(clonedConstraint) is Constraint oldConstraint)
{
oldConstraint.ConstraintName = Constraints[j].ConstraintName;
}
}
}
else if (unique != null)
{
if (unique.Clone(clone) is UniqueConstraint clonedConstraint &&
clone.Constraints.FindConstraint(clonedConstraint) is Constraint oldConstraint)
{
oldConstraint.ConstraintName = Constraints[j].ConstraintName;
foreach (object key in clonedConstraint.ExtendedProperties.Keys)
{
oldConstraint.ExtendedProperties[key] = clonedConstraint.ExtendedProperties[key];
}
}
}
}
// then add
for (int j = 0; j < Constraints.Count; j++)
{
if (!clone.Constraints.Contains(Constraints[j].ConstraintName, true))
{
ForeignKeyConstraint? foreign = Constraints[j] as ForeignKeyConstraint;
UniqueConstraint? unique = Constraints[j] as UniqueConstraint;
if (foreign != null)
{
if (foreign.Table == foreign.RelatedTable &&
foreign.Clone(clone) is ForeignKeyConstraint newforeign)
{
// we cant make sure that we receive a cloned FKC,since it depends if table and relatedtable be the same
clone.Constraints.Add(newforeign);
}
}
else if (unique != null)
{
clone.Constraints.Add(unique.Clone(clone)!);
}
}
}
// ...Extended Properties...
if (_extendedProperties != null)
{
foreach (object key in _extendedProperties.Keys)
{
clone.ExtendedProperties[key] = _extendedProperties[key];
}
}
return clone;
}
public DataTable Copy()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Copy|API> {0}", ObjectID);
try
{
DataTable destTable = Clone();
foreach (DataRow row in Rows)
{
CopyRow(destTable, row);
}
return destTable;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
/// <summary>
/// Occurs when a value has been submitted for this column.
/// </summary>
public event DataColumnChangeEventHandler? ColumnChanging
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_ColumnChanging|API> {0}", ObjectID);
_onColumnChangingDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_ColumnChanging|API> {0}", ObjectID);
_onColumnChangingDelegate -= value;
}
}
public event DataColumnChangeEventHandler? ColumnChanged
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_ColumnChanged|API> {0}", ObjectID);
_onColumnChangedDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_ColumnChanged|API> {0}", ObjectID);
_onColumnChangedDelegate -= value;
}
}
public event EventHandler? Initialized
{
add { _onInitialized += value; }
remove { _onInitialized -= value; }
}
internal event PropertyChangedEventHandler PropertyChanging
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_PropertyChanging|INFO> {0}", ObjectID);
_onPropertyChangingDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_PropertyChanging|INFO> {0}", ObjectID);
_onPropertyChangingDelegate -= value;
}
}
/// <summary>
/// Occurs after a row in the table has been successfully edited.
/// </summary>
public event DataRowChangeEventHandler? RowChanged
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_RowChanged|API> {0}", ObjectID);
_onRowChangedDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_RowChanged|API> {0}", ObjectID);
_onRowChangedDelegate -= value;
}
}
/// <summary>
/// Occurs when the <see cref='System.Data.DataRow'/> is changing.
/// </summary>
public event DataRowChangeEventHandler? RowChanging
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_RowChanging|API> {0}", ObjectID);
_onRowChangingDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_RowChanging|API> {0}", ObjectID);
_onRowChangingDelegate -= value;
}
}
/// <summary>
/// Occurs before a row in the table is about to be deleted.
/// </summary>
public event DataRowChangeEventHandler? RowDeleting
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_RowDeleting|API> {0}", ObjectID);
_onRowDeletingDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_RowDeleting|API> {0}", ObjectID);
_onRowDeletingDelegate -= value;
}
}
/// <summary>
/// Occurs after a row in the table has been deleted.
/// </summary>
public event DataRowChangeEventHandler? RowDeleted
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_RowDeleted|API> {0}", ObjectID);
_onRowDeletedDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_RowDeleted|API> {0}", ObjectID);
_onRowDeletedDelegate -= value;
}
}
public event DataTableClearEventHandler? TableClearing
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_TableClearing|API> {0}", ObjectID);
_onTableClearingDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_TableClearing|API> {0}", ObjectID);
_onTableClearingDelegate -= value;
}
}
public event DataTableClearEventHandler? TableCleared
{
add
{
DataCommonEventSource.Log.Trace("<ds.DataTable.add_TableCleared|API> {0}", ObjectID);
_onTableClearedDelegate += value;
}
remove
{
DataCommonEventSource.Log.Trace("<ds.DataTable.remove_TableCleared|API> {0}", ObjectID);
_onTableClearedDelegate -= value;
}
}
public event DataTableNewRowEventHandler? TableNewRow
{
add
{
_onTableNewRowDelegate += value;
}
remove
{
_onTableNewRowDelegate -= value;
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override ISite? Site
{
get { return base.Site; }
set
{
ISite? oldSite = Site;
if (value == null && oldSite != null)
{
IContainer? cont = oldSite.Container;
if (cont != null)
{
for (int i = 0; i < Columns.Count; i++)
{
if (Columns[i].Site != null)
{
cont.Remove(Columns[i]);
}
}
}
}
base.Site = value;
}
}
internal DataRow AddRecords(int oldRecord, int newRecord)
{
DataRow row;
if (oldRecord == -1 && newRecord == -1)
{
row = NewRow(-1);
AddRow(row);
}
else
{
row = NewEmptyRow();
row._oldRecord = oldRecord;
row._newRecord = newRecord;
InsertRow(row, -1);
}
return row;
}
internal void AddRow(DataRow row) => AddRow(row, -1);
internal void AddRow(DataRow row, int proposedID) => InsertRow(row, proposedID, -1);
internal void InsertRow(DataRow row, int proposedID, int pos) => InsertRow(row, proposedID, pos, fireEvent: true);
internal void InsertRow(DataRow row, long proposedID, int pos, bool fireEvent)
{
Exception? deferredException;
if (row == null)
{
throw ExceptionBuilder.ArgumentNull(nameof(row));
}
if (row.Table != this)
{
throw ExceptionBuilder.RowAlreadyInOtherCollection();
}
if (row.rowID != -1)
{
throw ExceptionBuilder.RowAlreadyInTheCollection();
}
row.BeginEdit(); // ensure something's there.
int record = row._tempRecord;
row._tempRecord = -1;
if (proposedID == -1)
{
proposedID = _nextRowID;
}
bool rollbackOnException;
if (rollbackOnException = (_nextRowID <= proposedID))
{
_nextRowID = checked(proposedID + 1);
}
try
{
try
{
row.rowID = proposedID;
// this method may cause DataView.OnListChanged in which another row may be added
SetNewRecordWorker(row, record, DataRowAction.Add, false, false, pos, fireEvent, out deferredException); // now we do add the row to collection before OnRowChanged (RaiseRowChanged)
}
catch
{
if (rollbackOnException && (_nextRowID == proposedID + 1))
{
_nextRowID = proposedID;
}
row.rowID = -1;
row._tempRecord = record;
throw;
}
// since expression evaluation occurred in SetNewRecordWorker, there may have been a problem that
// was deferred to this point. If so, throw now since row has already been added.
if (deferredException != null)
throw deferredException;
if (EnforceConstraints)
{
// if we are evaluating expression, we need to validate constraints
int columnCount = _columnCollection.Count;
for (int i = 0; i < columnCount; ++i)
{
DataColumn column = _columnCollection[i];
if (column.Computed)
{
column.CheckColumnConstraint(row, DataRowAction.Add);
}
}
}
}
finally
{
row.ResetLastChangedColumn(); // if expression is evaluated while adding, before return, we want to clear it
}
}
internal static void CheckNotModifying(DataRow row)
{
if (row._tempRecord != -1)
{
row.EndEdit();
}
}
/// <summary>
/// Clears the table of all data.
/// </summary>
public void Clear() => Clear(true);
internal void Clear(bool clearAll)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Clear|INFO> {0}, clearAll={1}", ObjectID, clearAll);
try
{
Debug.Assert(null == _rowDiffId, "wasn't previously cleared");
_rowDiffId = null;
_dataSet?.OnClearFunctionCalled(this);
bool shouldFireClearEvents = (Rows.Count != 0); // if Rows is already empty, this is noop
DataTableClearEventArgs? e = null;
if (shouldFireClearEvents)
{
e = new DataTableClearEventArgs(this);
OnTableClearing(e);
}
if (_dataSet != null && _dataSet.EnforceConstraints)
{
for (ParentForeignKeyConstraintEnumerator constraints = new ParentForeignKeyConstraintEnumerator(_dataSet, this); constraints.GetNext();)
{
ForeignKeyConstraint constraint = constraints.GetForeignKeyConstraint();
constraint.CheckCanClearParentTable(this);
}
}
_recordManager.Clear(clearAll);
// this improves performance by iterating over rows instead of computing by index
foreach (DataRow row in Rows)
{
row._oldRecord = -1;
row._newRecord = -1;
row._tempRecord = -1;
row.rowID = -1;
row.RBTreeNodeId = 0;
}
Rows.ArrayClear();
ResetIndexes();
if (shouldFireClearEvents)
{
OnTableCleared(e!);
}
foreach (DataColumn column in Columns)
{
EvaluateDependentExpressions(column);
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
internal void CascadeAll(DataRow row, DataRowAction action)
{
if (_dataSet != null && _dataSet._fEnableCascading)
{
for (ParentForeignKeyConstraintEnumerator constraints = new ParentForeignKeyConstraintEnumerator(_dataSet, this); constraints.GetNext();)
{
constraints.GetForeignKeyConstraint().CheckCascade(row, action);
}
}
}
internal void CommitRow(DataRow row)
{
// Fire Changing event
DataRowChangeEventArgs? drcevent = OnRowChanging(null, row, DataRowAction.Commit);
if (!_inDataLoad)
{
CascadeAll(row, DataRowAction.Commit);
}
SetOldRecord(row, row._newRecord);
OnRowChanged(drcevent, row, DataRowAction.Commit);
}
internal int Compare(string s1, string s2) => Compare(s1, s2, null);
internal int Compare(string s1, string s2, CompareInfo? comparer)
{
object obj1 = s1;
object obj2 = s2;
if (obj1 == obj2)
{
return 0;
}
if (obj1 == null)
{
return -1;
}
if (obj2 == null)
{
return 1;
}
int leng1 = s1.Length;
int leng2 = s2.Length;
for (; leng1 > 0; leng1--)
{
if (s1[leng1 - 1] != 0x20 && s1[leng1 - 1] != 0x3000) // 0x3000 is Ideographic Whitespace
{
break;
}
}
for (; leng2 > 0; leng2--)
{
if (s2[leng2 - 1] != 0x20 && s2[leng2 - 1] != 0x3000)
{
break;
}
}
return (comparer ?? CompareInfo).Compare(s1, 0, leng1, s2, 0, leng2, _compareFlags);
}
internal int IndexOf(string s1, string s2) => CompareInfo.IndexOf(s1, s2, _compareFlags);
internal bool IsSuffix(string s1, string s2) => CompareInfo.IsSuffix(s1, s2, _compareFlags);
/// <summary>
/// Computes the given expression on the current rows that pass the filter criteria.
/// </summary>
[RequiresUnreferencedCode("Members of types used in the filter or expression might be trimmed.")]
public object Compute(string? expression, string? filter)
{
DataRow[] rows = Select(filter, "", DataViewRowState.CurrentRows);
DataExpression expr = new DataExpression(this, expression);
return expr.Evaluate(rows);
}
bool IListSource.ContainsListCollection => false;
internal static void CopyRow(DataTable table, DataRow row)
{
int oldRecord = -1, newRecord = -1;
if (row == null)
{
return;
}
if (row._oldRecord != -1)
{
oldRecord = table._recordManager.ImportRecord(row.Table, row._oldRecord);
}
if (row._newRecord != -1)
{
if (row._newRecord != row._oldRecord)
{
newRecord = table._recordManager.ImportRecord(row.Table, row._newRecord);
}
else
{
newRecord = oldRecord;
}
}
DataRow targetRow = table.AddRecords(oldRecord, newRecord);
if (row.HasErrors)
{
targetRow.RowError = row.RowError;
DataColumn[] cols = row.GetColumnsInError();
for (int i = 0; i < cols.Length; i++)
{
DataColumn col = targetRow.Table.Columns[cols[i].ColumnName]!;
targetRow.SetColumnError(col, row.GetColumnError(cols[i]));
}
}
}
internal void DeleteRow(DataRow row)
{
if (row._newRecord == -1)
{
throw ExceptionBuilder.RowAlreadyDeleted();
}
// Store.PrepareForDelete(row);
SetNewRecord(row, -1, DataRowAction.Delete, false, true);
}
private void CheckPrimaryKey()
{
if (_primaryKey == null) throw ExceptionBuilder.TableMissingPrimaryKey();
}
internal DataRow? FindByPrimaryKey(object?[] values)
{
CheckPrimaryKey();
return FindRow(_primaryKey!.Key, values);
}
internal DataRow? FindByPrimaryKey(object? value)
{
CheckPrimaryKey();
return FindRow(_primaryKey!.Key, value);
}
private DataRow? FindRow(DataKey key, object?[] values)
{
Index index = GetIndex(NewIndexDesc(key));
Range range = index.FindRecords(values);
if (range.IsNull)
{
return null;
}
return _recordManager[index.GetRecord(range.Min)];
}
private DataRow? FindRow(DataKey key, object? value)
{
Index index = GetIndex(NewIndexDesc(key));
Range range = index.FindRecords(value);
if (range.IsNull)
{
return null;
}
return _recordManager[index.GetRecord(range.Min)];
}
internal static string FormatSortString(IndexField[] indexDesc)
{
var builder = new StringBuilder();
foreach (IndexField field in indexDesc)
{
if (0 < builder.Length)
{
builder.Append(", ");
}
builder.Append(field.Column.ColumnName);
if (field.IsDescending)
{
builder.Append(" DESC");
}
}
return builder.ToString();
}
internal void FreeRecord(ref int record)
{
_recordManager.FreeRecord(ref record);
}
public DataTable? GetChanges()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.GetChanges|API> {0}", ObjectID);
try
{
DataTable dtChanges = Clone();
DataRow? row = null;
for (int i = 0; i < Rows.Count; i++)
{
row = Rows[i];
if (row._oldRecord != row._newRecord)
{
dtChanges.ImportRow(row);
}
}
if (dtChanges.Rows.Count == 0)
{
return null;
}
return dtChanges;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
public DataTable? GetChanges(DataRowState rowStates)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.GetChanges|API> {0}, rowStates={1}", ObjectID, rowStates);
try
{
DataTable dtChanges = Clone();
DataRow? row = null;
// check that rowStates is valid DataRowState
Debug.Assert(Enum.GetUnderlyingType(typeof(DataRowState)) == typeof(int), "Invalid DataRowState type");
for (int i = 0; i < Rows.Count; i++)
{
row = Rows[i];
if ((row.RowState & rowStates) != 0)
{
dtChanges.ImportRow(row);
}
}
if (dtChanges.Rows.Count == 0)
{
return null;
}
return dtChanges;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
/// <summary>
/// Returns an array of <see cref='System.Data.DataRow'/> objects that contain errors.
/// </summary>
public DataRow[] GetErrors()
{
List<DataRow> errorList = new List<DataRow>();
for (int i = 0; i < Rows.Count; i++)
{
DataRow row = Rows[i];
if (row.HasErrors)
{
errorList.Add(row);
}
}
DataRow[] temp = NewRowArray(errorList.Count);
errorList.CopyTo(temp);
return temp;
}
internal Index GetIndex(IndexField[] indexDesc) =>
GetIndex(indexDesc, DataViewRowState.CurrentRows, null);
internal Index GetIndex(string sort, DataViewRowState recordStates, IFilter? rowFilter) =>
GetIndex(ParseSortString(sort), recordStates, rowFilter);
internal Index GetIndex(IndexField[] indexDesc, DataViewRowState recordStates, IFilter? rowFilter)
{
_indexesLock.EnterUpgradeableReadLock();
try
{
for (int i = 0; i < _indexes.Count; i++)
{
Index index = _indexes[i];
if (index != null)
{
if (index.Equal(indexDesc, recordStates, rowFilter))
{
return index;
}
}
}
}
finally
{
_indexesLock.ExitUpgradeableReadLock();
}
Index ndx = new Index(this, indexDesc, recordStates, rowFilter);
ndx.AddRef();
return ndx;
}
IList IListSource.GetList() => DefaultView;
internal List<DataViewListener> GetListeners() => _dataViewListeners;
// We need a HashCodeProvider for Case, Kana and Width insensitive
internal int GetSpecialHashCode(string name)
{
int i;
for (i = 0; (i < name.Length) && (0x3000 > name[i]); ++i) ;
if (name.Length == i)
{
if (null == _hashCodeProvider)
{
// it should use the CaseSensitive property, but V1 shipped this way
_hashCodeProvider = StringComparer.Create(Locale, true);
}
return _hashCodeProvider.GetHashCode(name);
}
else
{
return 0;
}
}
public void ImportRow(DataRow? row)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.ImportRow|API> {0}", ObjectID);
try
{
int oldRecord = -1, newRecord = -1;
if (row == null)
{
return;
}
if (row._oldRecord != -1)
{
oldRecord = _recordManager.ImportRecord(row.Table, row._oldRecord);
}
if (row._newRecord != -1)
{ // row not deleted
if (row.RowState != DataRowState.Unchanged)
{ // not unchanged, it means Added or modified
newRecord = _recordManager.ImportRecord(row.Table, row._newRecord);
}
else
{
newRecord = oldRecord;
}
}
if (oldRecord != -1 || newRecord != -1)
{
DataRow targetRow = AddRecords(oldRecord, newRecord);
if (row.HasErrors)
{
targetRow.RowError = row.RowError;
DataColumn[] cols = row.GetColumnsInError();
for (int i = 0; i < cols.Length; i++)
{
DataColumn col = targetRow.Table.Columns[cols[i].ColumnName]!;
targetRow.SetColumnError(col, row.GetColumnError(cols[i]));
}
}
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
internal void InsertRow(DataRow row, long proposedID)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.InsertRow|INFO> {0}, row={1}", ObjectID, row._objectID);
try
{
if (row.Table != this)
{
throw ExceptionBuilder.RowAlreadyInOtherCollection();
}
if (row.rowID != -1)
{
throw ExceptionBuilder.RowAlreadyInTheCollection();
}
if (row._oldRecord == -1 && row._newRecord == -1)
{
throw ExceptionBuilder.RowEmpty();
}
if (proposedID == -1)
{
proposedID = _nextRowID;
}
row.rowID = proposedID;
if (_nextRowID <= proposedID)
{
_nextRowID = checked(proposedID + 1);
}
DataRowChangeEventArgs? drcevent = null;
if (row._newRecord != -1)
{
row._tempRecord = row._newRecord;
row._newRecord = -1;
try
{
drcevent = RaiseRowChanging(null, row, DataRowAction.Add, true);
}
catch
{
row._tempRecord = -1;
throw;
}
row._newRecord = row._tempRecord;
row._tempRecord = -1;
}
if (row._oldRecord != -1)
{
_recordManager[row._oldRecord] = row;
}
if (row._newRecord != -1)
{
_recordManager[row._newRecord] = row;
}
Rows.ArrayAdd(row);
if (row.RowState == DataRowState.Unchanged)
{
// how about row.oldRecord == row.newRecord both == -1
RecordStateChanged(row._oldRecord, DataViewRowState.None, DataViewRowState.Unchanged);
}
else
{
RecordStateChanged(row._oldRecord, DataViewRowState.None, row.GetRecordState(row._oldRecord),
row._newRecord, DataViewRowState.None, row.GetRecordState(row._newRecord));
}
if (_dependentColumns != null && _dependentColumns.Count > 0)
{
EvaluateExpressions(row, DataRowAction.Add, null);
}
RaiseRowChanged(drcevent, row, DataRowAction.Add);
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
private static IndexField[] NewIndexDesc(DataKey key)
{
Debug.Assert(key.HasValue);
IndexField[] indexDesc = key.GetIndexDesc();
IndexField[] newIndexDesc = new IndexField[indexDesc.Length];
Array.Copy(indexDesc, newIndexDesc, indexDesc.Length);
return newIndexDesc;
}
internal int NewRecord() => NewRecord(-1);
internal int NewUninitializedRecord()
{
return _recordManager.NewRecordBase();
}
internal int NewRecordFromArray(object?[] value)
{
int colCount = _columnCollection.Count; // Perf: use the readonly columnCollection field directly
if (colCount < value.Length)
{
throw ExceptionBuilder.ValueArrayLength();
}
int record = _recordManager.NewRecordBase();
try
{
for (int i = 0; i < value.Length; i++)
{
if (value[i] is object v)
{
_columnCollection[i][record] = v;
}
else
{
_columnCollection[i].Init(record); // Increase AutoIncrementCurrent
}
}
for (int i = value.Length; i < colCount; i++)
{
_columnCollection[i].Init(record);
}
return record;
}
catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e))
{
FreeRecord(ref record);
throw;
}
}
internal int NewRecord(int sourceRecord)
{
int record = _recordManager.NewRecordBase();
int count = _columnCollection.Count;
if (-1 == sourceRecord)
{
for (int i = 0; i < count; ++i)
{
_columnCollection[i].Init(record);
}
}
else
{
for (int i = 0; i < count; ++i)
{
_columnCollection[i].Copy(sourceRecord, record);
}
}
return record;
}
internal DataRow NewEmptyRow()
{
_rowBuilder._record = -1;
DataRow dr = NewRowFromBuilder(_rowBuilder);
_dataSet?.OnDataRowCreated(dr);
return dr;
}
private DataRow NewUninitializedRow() => NewRow(NewUninitializedRecord());
/// <summary>
/// Creates a new <see cref='System.Data.DataRow'/>
/// with the same schema as the table.
/// </summary>
public DataRow NewRow()
{
DataRow dr = NewRow(-1);
NewRowCreated(dr); // this is the only API we want this event to be fired
return dr;
}
// Only initialize DataRelation mapping columns (approximately hidden columns)
internal DataRow CreateEmptyRow()
{
DataRow row = NewUninitializedRow();
foreach (DataColumn c in Columns)
{
if (!XmlToDatasetMap.IsMappedColumn(c))
{
if (!c.AutoIncrement)
{
if (c.AllowDBNull)
{
row[c] = DBNull.Value;
}
else if (c.DefaultValue != null)
{
row[c] = c.DefaultValue;
}
}
else
{
c.Init(row._tempRecord);
}
}
}
return row;
}
private void NewRowCreated(DataRow row)
{
if (null != _onTableNewRowDelegate)
{
DataTableNewRowEventArgs eventArg = new DataTableNewRowEventArgs(row);
OnTableNewRow(eventArg);
}
}
internal DataRow NewRow(int record)
{
if (-1 == record)
{
record = NewRecord(-1);
}
_rowBuilder._record = record;
DataRow row = NewRowFromBuilder(_rowBuilder);
_recordManager[record] = row;
_dataSet?.OnDataRowCreated(row);
return row;
}
// This is what a subclassed dataSet overrides to create a new row.
protected virtual DataRow NewRowFromBuilder(DataRowBuilder builder) => new DataRow(builder);
/// <summary>
/// Gets the row type.
/// </summary>
protected virtual Type GetRowType() => typeof(DataRow);
// Prevent inlining so that reflection calls are not moved to caller that may be in a different assembly that may have a different grant set.
[MethodImpl(MethodImplOptions.NoInlining)]
[UnconditionalSuppressMessage("AOT analysis", "IL3050:RequiresDynamicCode",
Justification = "Array.CreateInstance operates over a reference type making the call safe for AOT")]
protected internal DataRow[] NewRowArray(int size)
{
if (IsTypedDataTable)
{
if (0 == size)
{
if (null == _emptyDataRowArray)
{
_emptyDataRowArray = (DataRow[])Array.CreateInstance(GetRowType(), 0);
}
return _emptyDataRowArray;
}
return (DataRow[])Array.CreateInstance(GetRowType(), size);
}
else
{
return ((0 == size) ? Array.Empty<DataRow>() : new DataRow[size]);
}
}
internal bool NeedColumnChangeEvents =>
(IsTypedDataTable || (null != _onColumnChangingDelegate) || (null != _onColumnChangedDelegate));
protected internal virtual void OnColumnChanging(DataColumnChangeEventArgs e)
{
// intentionally allow exceptions to bubble up. We haven't committed anything yet.
Debug.Assert(e != null, "e should not be null");
if (_onColumnChangingDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnColumnChanging|INFO> {0}", ObjectID);
_onColumnChangingDelegate(this, e);
}
}
protected internal virtual void OnColumnChanged(DataColumnChangeEventArgs e)
{
Debug.Assert(e != null, "e should not be null");
if (_onColumnChangedDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnColumnChanged|INFO> {0}", ObjectID);
_onColumnChangedDelegate(this, e);
}
}
protected virtual void OnPropertyChanging(PropertyChangedEventArgs pcevent)
{
if (_onPropertyChangingDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnPropertyChanging|INFO> {0}", ObjectID);
_onPropertyChangingDelegate(this, pcevent);
}
}
internal void OnRemoveColumnInternal(DataColumn column) => OnRemoveColumn(column);
/// <summary>
/// Notifies the <see cref='System.Data.DataTable'/> that a <see cref='System.Data.DataColumn'/> is
/// being removed.
/// </summary>
protected virtual void OnRemoveColumn(DataColumn column) { }
private DataRowChangeEventArgs? OnRowChanged(DataRowChangeEventArgs? args, DataRow eRow, DataRowAction eAction)
{
if ((null != _onRowChangedDelegate) || IsTypedDataTable)
{
if (null == args)
{
args = new DataRowChangeEventArgs(eRow, eAction);
}
OnRowChanged(args);
}
return args;
}
private DataRowChangeEventArgs? OnRowChanging(DataRowChangeEventArgs? args, DataRow eRow, DataRowAction eAction)
{
if ((null != _onRowChangingDelegate) || IsTypedDataTable)
{
if (null == args)
{
args = new DataRowChangeEventArgs(eRow, eAction);
}
OnRowChanging(args);
}
return args;
}
/// <summary>
/// Raises the <see cref='System.Data.DataTable.RowChanged'/> event.
/// </summary>
protected virtual void OnRowChanged(DataRowChangeEventArgs e)
{
Debug.Assert((null != e) && ((null != _onRowChangedDelegate) || IsTypedDataTable), "OnRowChanged arguments");
if (_onRowChangedDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnRowChanged|INFO> {0}", ObjectID);
_onRowChangedDelegate(this, e);
}
}
/// <summary>
/// Raises the <see cref='System.Data.DataTable.RowChanging'/> event.
/// </summary>
protected virtual void OnRowChanging(DataRowChangeEventArgs e)
{
Debug.Assert((null != e) && ((null != _onRowChangingDelegate) || IsTypedDataTable), "OnRowChanging arguments");
if (_onRowChangingDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnRowChanging|INFO> {0}", ObjectID);
_onRowChangingDelegate(this, e);
}
}
/// <summary>
/// Raises the <see cref='System.Data.DataTable.OnRowDeleting'/> event.
/// </summary>
protected virtual void OnRowDeleting(DataRowChangeEventArgs e)
{
Debug.Assert((null != e) && ((null != _onRowDeletingDelegate) || IsTypedDataTable), "OnRowDeleting arguments");
if (_onRowDeletingDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnRowDeleting|INFO> {0}", ObjectID);
_onRowDeletingDelegate(this, e);
}
}
/// <summary>
/// Raises the <see cref='System.Data.DataTable.OnRowDeleted'/> event.
/// </summary>
protected virtual void OnRowDeleted(DataRowChangeEventArgs e)
{
Debug.Assert((null != e) && ((null != _onRowDeletedDelegate) || IsTypedDataTable), "OnRowDeleted arguments");
if (_onRowDeletedDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnRowDeleted|INFO> {0}", ObjectID);
_onRowDeletedDelegate(this, e);
}
}
protected virtual void OnTableCleared(DataTableClearEventArgs e)
{
if (_onTableClearedDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnTableCleared|INFO> {0}", ObjectID);
_onTableClearedDelegate(this, e);
}
}
protected virtual void OnTableClearing(DataTableClearEventArgs e)
{
if (_onTableClearingDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnTableClearing|INFO> {0}", ObjectID);
_onTableClearingDelegate(this, e);
}
}
protected virtual void OnTableNewRow(DataTableNewRowEventArgs e)
{
if (_onTableNewRowDelegate != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnTableNewRow|INFO> {0}", ObjectID);
_onTableNewRowDelegate(this, e);
}
}
private void OnInitialized()
{
if (_onInitialized != null)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.OnInitialized|INFO> {0}", ObjectID);
_onInitialized(this, EventArgs.Empty);
}
}
internal IndexField[] ParseSortString(string? sortString)
{
IndexField[] indexDesc = Array.Empty<IndexField>();
if ((null != sortString) && (0 < sortString.Length))
{
string[] split = sortString.Split(',', StringSplitOptions.TrimEntries);
indexDesc = new IndexField[split.Length];
for (int i = 0; i < split.Length; i++)
{
string current = split[i];
// handle ASC and DESC.
int length = current.Length;
bool descending = false;
if (length >= 5 && string.Compare(current, length - 4, " ASC", 0, 4, StringComparison.OrdinalIgnoreCase) == 0)
{
current = current.AsSpan(0, length - 4).Trim().ToString();
}
else if (length >= 6 && string.Compare(current, length - 5, " DESC", 0, 5, StringComparison.OrdinalIgnoreCase) == 0)
{
descending = true;
current = current.AsSpan(0, length - 5).Trim().ToString();
}
// handle brackets.
if (current.StartsWith('['))
{
if (current.EndsWith(']'))
{
current = current.Substring(1, current.Length - 2);
}
else
{
throw ExceptionBuilder.InvalidSortString(split[i]);
}
}
// find the column.
DataColumn? column = Columns[current];
if (column == null)
{
throw ExceptionBuilder.ColumnOutOfRange(current);
}
indexDesc[i] = new IndexField(column, descending);
}
}
return indexDesc;
}
internal void RaisePropertyChanging(string name)
{
OnPropertyChanging(new PropertyChangedEventArgs(name));
}
// Notify all indexes that record changed.
// Only called when Error was changed.
internal void RecordChanged(int record)
{
Debug.Assert(record != -1, "Record number must be given");
SetShadowIndexes(); // how about new assert?
try
{
int numIndexes = _shadowIndexes!.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
if (0 < ndx.RefCount)
{
ndx.RecordChanged(record);
}
}
}
finally
{
RestoreShadowIndexes();
}
}
// for each index in liveindexes invok RecordChanged
// oldIndex and newIndex keeps position of record before delete and after insert in each index in order
// LiveIndexes[n-m] will have its information in oldIndex[n-m] and newIndex[n-m]
internal void RecordChanged(int[] oldIndex, int[] newIndex)
{
SetShadowIndexes();
Debug.Assert(oldIndex.Length == newIndex.Length, "Size oldIndexes and newIndexes should be the same");
Debug.Assert(oldIndex.Length == _shadowIndexes!.Count, "Size of OldIndexes should be the same as size of Live indexes");
try
{
int numIndexes = _shadowIndexes.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
if (0 < ndx.RefCount)
{
ndx.RecordChanged(oldIndex[i], newIndex[i]);
}
}
}
finally
{
RestoreShadowIndexes();
}
}
internal void RecordStateChanged(int record, DataViewRowState oldState, DataViewRowState newState)
{
SetShadowIndexes();
try
{
int numIndexes = _shadowIndexes!.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
if (0 < ndx.RefCount)
{
ndx.RecordStateChanged(record, oldState, newState);
}
}
}
finally
{
RestoreShadowIndexes();
}
// System.Data.XML.Store.Store.OnROMChanged(record, oldState, newState);
}
internal void RecordStateChanged(int record1, DataViewRowState oldState1, DataViewRowState newState1,
int record2, DataViewRowState oldState2, DataViewRowState newState2)
{
SetShadowIndexes();
try
{
int numIndexes = _shadowIndexes!.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
if (0 < ndx.RefCount)
{
if (record1 != -1 && record2 != -1)
{
ndx.RecordStateChanged(record1, oldState1, newState1, record2, oldState2, newState2);
}
else if (record1 != -1)
{
ndx.RecordStateChanged(record1, oldState1, newState1);
}
else if (record2 != -1)
{
ndx.RecordStateChanged(record2, oldState2, newState2);
}
}
}
}
finally
{
RestoreShadowIndexes();
}
// System.Data.XML.Store.Store.OnROMChanged(record1, oldState1, newState1, record2, oldState2, newState2);
}
// RemoveRecordFromIndexes removes the given record (using row and version) from all indexes and it stores and returns the position of deleted
// record from each index
// IT SHOULD NOT CAUSE ANY EVENT TO BE FIRED
internal int[] RemoveRecordFromIndexes(DataRow row, DataRowVersion version)
{
int indexCount = LiveIndexes.Count;
int[] positionIndexes = new int[indexCount];
int recordNo = row.GetRecordFromVersion(version);
DataViewRowState states = row.GetRecordState(recordNo);
while (--indexCount >= 0)
{
if (row.HasVersion(version) && ((states & _indexes[indexCount].RecordStates) != DataViewRowState.None))
{
int index = _indexes[indexCount].GetIndex(recordNo);
if (index > -1)
{
positionIndexes[indexCount] = index;
_indexes[indexCount].DeleteRecordFromIndex(index); // this will delete the record from index and MUSt not fire event
}
else
{
positionIndexes[indexCount] = -1; // this means record was not in index
}
}
else
{
positionIndexes[indexCount] = -1; // this means record was not in index
}
}
return positionIndexes;
}
// InsertRecordToIndexes inserts the given record (using row and version) to all indexes and it stores and returns the position of inserted
// record to each index
// IT SHOULD NOT CAUSE ANY EVENT TO BE FIRED
internal int[] InsertRecordToIndexes(DataRow row, DataRowVersion version)
{
int indexCount = LiveIndexes.Count;
int[] positionIndexes = new int[indexCount];
int recordNo = row.GetRecordFromVersion(version);
DataViewRowState states = row.GetRecordState(recordNo);
while (--indexCount >= 0)
{
if (row.HasVersion(version))
{
if ((states & _indexes[indexCount].RecordStates) != DataViewRowState.None)
{
positionIndexes[indexCount] = _indexes[indexCount].InsertRecordToIndex(recordNo);
}
else
{
positionIndexes[indexCount] = -1;
}
}
}
return positionIndexes;
}
internal static void SilentlySetValue(DataRow dr, DataColumn dc, DataRowVersion version, object newValue)
{
// get record for version
int record = dr.GetRecordFromVersion(version);
bool equalValues;
if (DataStorage.IsTypeCustomType(dc.DataType) && newValue != dc[record])
{
// if UDT storage, need to check if reference changed.
equalValues = false;
}
else
{
equalValues = dc.CompareValueToChecked(record, newValue);
}
// if expression has changed
if (!equalValues)
{
int[] oldIndex = dr.Table.RemoveRecordFromIndexes(dr, version); // conditional, if it exists it will try to remove with no event fired
dc.SetValue(record, newValue);
int[] newIndex = dr.Table.InsertRecordToIndexes(dr, version); // conditional, it will insert if it qualifies, no event will be fired
if (dr.HasVersion(version))
{
if (version != DataRowVersion.Original)
{
dr.Table.RecordChanged(oldIndex, newIndex);
}
if (dc._dependentColumns != null)
{
dc.Table!.EvaluateDependentExpressions(dc._dependentColumns, dr, version, null);
}
}
}
dr.ResetLastChangedColumn();
}
/// <summary>
/// Rolls back all changes that have been made to the table
/// since it was loaded, or the last time <see cref='System.Data.DataTable.AcceptChanges'/> was called.
/// </summary>
public void RejectChanges()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.RejectChanges|API> {0}", ObjectID);
try
{
DataRow[] oldRows = new DataRow[Rows.Count];
Rows.CopyTo(oldRows, 0);
for (int i = 0; i < oldRows.Length; i++)
{
RollbackRow(oldRows[i]);
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
internal void RemoveRow(DataRow row, bool check)
{
if (row.rowID == -1)
{
throw ExceptionBuilder.RowAlreadyRemoved();
}
if (check && _dataSet != null)
{
for (ParentForeignKeyConstraintEnumerator constraints = new ParentForeignKeyConstraintEnumerator(_dataSet, this); constraints.GetNext();)
{
constraints.GetForeignKeyConstraint().CheckCanRemoveParentRow(row);
}
}
int oldRecord = row._oldRecord;
int newRecord = row._newRecord;
DataViewRowState oldRecordStatePre = row.GetRecordState(oldRecord);
DataViewRowState newRecordStatePre = row.GetRecordState(newRecord);
row._oldRecord = -1;
row._newRecord = -1;
if (oldRecord == newRecord)
{
oldRecord = -1;
}
RecordStateChanged(oldRecord, oldRecordStatePre, DataViewRowState.None, newRecord, newRecordStatePre, DataViewRowState.None);
FreeRecord(ref oldRecord);
FreeRecord(ref newRecord);
row.rowID = -1;
Rows.ArrayRemove(row);
}
// Resets the table back to its original state.
public virtual void Reset()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Reset|API> {0}", ObjectID);
try
{
Clear();
ResetConstraints();
DataRelationCollection dr = ParentRelations;
int count = dr.Count;
while (count > 0)
{
count--;
dr.RemoveAt(count);
}
dr = ChildRelations;
count = dr.Count;
while (count > 0)
{
count--;
dr.RemoveAt(count);
}
Columns.Clear();
_indexes.Clear();
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
internal void ResetIndexes() => ResetInternalIndexes(null);
internal void ResetInternalIndexes(DataColumn? column)
{
Debug.Assert(null != _indexes, "unexpected null indexes");
SetShadowIndexes();
try
{
// the length of shadowIndexes will not change
// but the array instance may change during
// events during Index.Reset
int numIndexes = _shadowIndexes!.Count;
for (int i = 0; i < numIndexes; i++)
{
Index ndx = _shadowIndexes[i]; // shadowindexes may change, see ShadowIndexCopy()
if (0 < ndx.RefCount)
{
if (null == column)
{
ndx.Reset();
}
else
{
bool found = false;
foreach (IndexField field in ndx._indexFields)
{
if (ReferenceEquals(column, field.Column))
{
found = true;
break;
}
}
if (found)
{
ndx.Reset();
}
}
}
}
}
finally
{
RestoreShadowIndexes();
}
}
internal void RollbackRow(DataRow row)
{
row.CancelEdit();
SetNewRecord(row, row._oldRecord, DataRowAction.Rollback, false, true);
}
private DataRowChangeEventArgs? RaiseRowChanged(DataRowChangeEventArgs? args, DataRow eRow, DataRowAction eAction)
{
try
{
if (UpdatingCurrent(eAction) && (IsTypedDataTable || (null != _onRowChangedDelegate)))
{
args = OnRowChanged(args, eRow, eAction);
}
// check if we deleting good row
else if (DataRowAction.Delete == eAction && eRow._newRecord == -1 && (IsTypedDataTable || (null != _onRowDeletedDelegate)))
{
if (null == args)
{
args = new DataRowChangeEventArgs(eRow, eAction);
}
OnRowDeleted(args);
}
}
catch (Exception f) when (ADP.IsCatchableExceptionType(f))
{
ExceptionBuilder.TraceExceptionWithoutRethrow(f); // ignore the exception
}
return args;
}
private DataRowChangeEventArgs? RaiseRowChanging(DataRowChangeEventArgs? args, DataRow eRow, DataRowAction eAction)
{
if (UpdatingCurrent(eAction) && (IsTypedDataTable || (null != _onRowChangingDelegate)))
{
eRow._inChangingEvent = true;
// don't catch
try
{
args = OnRowChanging(args, eRow, eAction);
}
finally
{
eRow._inChangingEvent = false;
}
}
// check if we deleting good row
else if (DataRowAction.Delete == eAction && eRow._newRecord != -1 && (IsTypedDataTable || (null != _onRowDeletingDelegate)))
{
eRow._inDeletingEvent = true;
// don't catch
try
{
if (null == args)
{
args = new DataRowChangeEventArgs(eRow, eAction);
}
OnRowDeleting(args);
}
finally
{
eRow._inDeletingEvent = false;
}
}
return args;
}
private DataRowChangeEventArgs? RaiseRowChanging(DataRowChangeEventArgs? args, DataRow eRow, DataRowAction eAction, bool fireEvent)
{
// check all constraints
if (EnforceConstraints)
{
int columnCount = _columnCollection.Count;
for (int i = 0; i < columnCount; ++i)
{
DataColumn column = _columnCollection[i];
if (!column.Computed || eAction != DataRowAction.Add)
{
column.CheckColumnConstraint(eRow, eAction);
}
}
int constraintCount = _constraintCollection.Count;
for (int i = 0; i < constraintCount; ++i)
{
_constraintCollection[i].CheckConstraint(eRow, eAction);
}
}
if (fireEvent)
{
args = RaiseRowChanging(args, eRow, eAction);
}
if (!_inDataLoad)
{
// cascade things...
if (!MergingData && eAction != DataRowAction.Nothing && eAction != DataRowAction.ChangeOriginal)
{
CascadeAll(eRow, eAction);
}
}
return args;
}
/// <summary>
/// Returns an array of all <see cref='System.Data.DataRow'/> objects.
/// </summary>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Filter expression is empty therefore this is safe.")]
public DataRow[] Select()
{
DataCommonEventSource.Log.Trace("<ds.DataTable.Select|API> {0}", ObjectID);
return new Select(this, "", "", DataViewRowState.CurrentRows).SelectRows();
}
/// <summary>
/// Returns an array of all <see cref='System.Data.DataRow'/> objects that match the filter criteria in order of
/// primary key (or lacking one, order of addition.)
/// </summary>
[RequiresUnreferencedCode(Data.Select.RequiresUnreferencedCodeMessage)]
public DataRow[] Select(string? filterExpression)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.Select|API> {0}, filterExpression='{1}'", ObjectID, filterExpression);
return new Select(this, filterExpression, "", DataViewRowState.CurrentRows).SelectRows();
}
/// <summary>
/// Returns an array of all <see cref='System.Data.DataRow'/> objects that match the filter criteria, in the
/// specified sort order.
/// </summary>
[RequiresUnreferencedCode(Data.Select.RequiresUnreferencedCodeMessage)]
public DataRow[] Select(string? filterExpression, string? sort)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.Select|API> {0}, filterExpression='{1}', sort='{2}'", ObjectID, filterExpression, sort);
return new Select(this, filterExpression, sort, DataViewRowState.CurrentRows).SelectRows();
}
/// <summary>
/// Returns an array of all <see cref='System.Data.DataRow'/> objects that match the filter in the order of the
/// sort, that match the specified state.
/// </summary>
[RequiresUnreferencedCode(Data.Select.RequiresUnreferencedCodeMessage)]
public DataRow[] Select(string? filterExpression, string? sort, DataViewRowState recordStates)
{
DataCommonEventSource.Log.Trace("<ds.DataTable.Select|API> {0}, filterExpression='{1}', sort='{2}', recordStates={3}", ObjectID, filterExpression, sort, recordStates);
return new Select(this, filterExpression, sort, recordStates).SelectRows();
}
internal void SetNewRecord(DataRow row, int proposedRecord, DataRowAction action = DataRowAction.Change, bool isInMerge = false, bool fireEvent = true, bool suppressEnsurePropertyChanged = false)
{
Exception? deferredException;
SetNewRecordWorker(row, proposedRecord, action, isInMerge, suppressEnsurePropertyChanged, -1, fireEvent, out deferredException); // we are going to call below overload from insert
if (deferredException != null)
{
throw deferredException;
}
}
private void SetNewRecordWorker(DataRow row, int proposedRecord, DataRowAction action, bool isInMerge, bool suppressEnsurePropertyChanged,
int position, bool fireEvent, out Exception? deferredException)
{
// this is the event workhorse... it will throw the changing/changed events
// and update the indexes. Used by change, add, delete, revert.
// order of execution is as follows
//
// 1) set temp record
// 2) Check constraints for non-expression columns
// 3) Raise RowChanging/RowDeleting with temp record
// 4) set the new record in storage
// 5) Update indexes with recordStateChanges - this will fire ListChanged & PropertyChanged events on associated views
// 6) Evaluate all Expressions (exceptions are deferred)- this will fire ListChanged & PropertyChanged events on associated views
// 7) Raise RowChanged/ RowDeleted
// 8) Check constraints for expression columns
Debug.Assert(row != null, "Row can't be null.");
deferredException = null;
if (row._tempRecord != proposedRecord)
{
// $HACK: for performance reasons, EndUpdate calls SetNewRecord with tempRecord == proposedRecord
if (!_inDataLoad)
{
row.CheckInTable();
CheckNotModifying(row);
}
if (proposedRecord == row._newRecord)
{
if (isInMerge)
{
Debug.Assert(fireEvent, "SetNewRecord is called with wrong parameter");
RaiseRowChanged(null, row, action);
}
return;
}
Debug.Assert(!row._inChangingEvent, "How can this row be in an infinite loop?");
row._tempRecord = proposedRecord;
}
DataRowChangeEventArgs? drcevent = null;
try
{
row._action = action;
drcevent = RaiseRowChanging(null, row, action, fireEvent);
}
catch
{
row._tempRecord = -1;
throw;
}
finally
{
row._action = DataRowAction.Nothing;
}
row._tempRecord = -1;
int currentRecord = row._newRecord;
// if we're deleting, then the oldRecord value will change, so need to track that if it's distinct from the newRecord.
int secondRecord = (proposedRecord != -1 ?
proposedRecord :
(row.RowState != DataRowState.Unchanged ?
row._oldRecord :
-1));
if (action == DataRowAction.Add)
{
//if we come here from insert we do insert the row to collection
if (position == -1)
{
Rows.ArrayAdd(row);
}
else
{
Rows.ArrayInsert(row, position);
}
}
List<DataRow>? cachedRows = null;
if ((action == DataRowAction.Delete || action == DataRowAction.Change) &&
_dependentColumns != null && _dependentColumns.Count > 0)
{
// if there are expression columns, need to cache related rows for deletes and updates (key changes)
// before indexes are modified.
cachedRows = new List<DataRow>();
for (int j = 0; j < ParentRelations.Count; j++)
{
DataRelation relation = ParentRelations[j];
if (relation.ChildTable != row.Table)
{
continue;
}
cachedRows.InsertRange(cachedRows.Count, row.GetParentRows(relation));
}
for (int j = 0; j < ChildRelations.Count; j++)
{
DataRelation relation = ChildRelations[j];
if (relation.ParentTable != row.Table)
{
continue;
}
cachedRows.InsertRange(cachedRows.Count, row.GetChildRows(relation));
}
}
// if the newRecord is changing, the propertychanged event should be allowed to triggered for ListChangedType.Changed or .Moved
// unless the specific condition is known that no data has changed, like DataRow.SetModified()
if (!suppressEnsurePropertyChanged && !row.HasPropertyChanged && (row._newRecord != proposedRecord)
&& (-1 != proposedRecord)
&& (-1 != row._newRecord))
{
// DataRow will believe multiple edits occurred and
// DataView.ListChanged event w/ ListChangedType.ItemChanged will raise DataRowView.PropertyChanged event and
// PropertyChangedEventArgs.PropertyName will now be empty string so
// WPF will refresh the entire row
row.LastChangedColumn = null;
row.LastChangedColumn = null;
}
// Check whether we need to update indexes
if (LiveIndexes.Count != 0)
{
if ((-1 == currentRecord) && (-1 != proposedRecord) && (-1 != row._oldRecord) && (proposedRecord != row._oldRecord))
{
// the transition from DataRowState.Deleted -> DataRowState.Modified
// with same original record but new current record
// needs to raise an ItemChanged or ItemMoved instead of ItemAdded in the ListChanged event.
// for indexes/views listening for both DataViewRowState.Deleted | DataViewRowState.ModifiedCurrent
currentRecord = row._oldRecord;
}
DataViewRowState currentRecordStatePre = row.GetRecordState(currentRecord);
DataViewRowState secondRecordStatePre = row.GetRecordState(secondRecord);
row._newRecord = proposedRecord;
if (proposedRecord != -1)
_recordManager[proposedRecord] = row;
DataViewRowState currentRecordStatePost = row.GetRecordState(currentRecord);
DataViewRowState secondRecordStatePost = row.GetRecordState(secondRecord);
// may raise DataView.ListChanged event
RecordStateChanged(currentRecord, currentRecordStatePre, currentRecordStatePost,
secondRecord, secondRecordStatePre, secondRecordStatePost);
}
else
{
row._newRecord = proposedRecord;
if (proposedRecord != -1)
_recordManager[proposedRecord] = row;
}
// reset the last changed column here, after all
// DataViews have raised their DataRowView.PropertyChanged event
row.ResetLastChangedColumn();
// free the 'currentRecord' only after all the indexes have been updated.
// Corruption! { if (currentRecord != row.oldRecord) { FreeRecord(ref currentRecord); } }
// RecordStateChanged raises ListChanged event at which time user may do work
if (-1 != currentRecord)
{
if (currentRecord != row._oldRecord)
{
if ((currentRecord != row._tempRecord) && // Delete, AcceptChanges, BeginEdit
(currentRecord != row._newRecord) && // RejectChanges & SetAdded
(row == _recordManager[currentRecord])) // AcceptChanges, NewRow
{
FreeRecord(ref currentRecord);
}
}
}
if (row.RowState == DataRowState.Detached && row.rowID != -1)
{
RemoveRow(row, false);
}
if (_dependentColumns != null && _dependentColumns.Count > 0)
{
try
{
EvaluateExpressions(row, action, cachedRows);
}
catch (Exception exc)
{
// For DataRows being added, throwing of exception from expression evaluation is
// deferred until after the row has been completely added.
if (action != DataRowAction.Add)
{
throw;
}
else
{
deferredException = exc;
}
}
}
try
{
if (fireEvent)
{
RaiseRowChanged(drcevent, row, action);
}
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ExceptionBuilder.TraceExceptionWithoutRethrow(e); // ignore the exception
}
}
// this is the event workhorse... it will throw the changing/changed events
// and update the indexes.
internal void SetOldRecord(DataRow row, int proposedRecord)
{
if (!_inDataLoad)
{
row.CheckInTable();
CheckNotModifying(row);
}
if (proposedRecord == row._oldRecord)
{
return;
}
int originalRecord = row._oldRecord; // cache old record after potential RowChanging event
try
{
// Check whether we need to update indexes
if (LiveIndexes.Count != 0)
{
if ((-1 == originalRecord) && (-1 != proposedRecord) && (-1 != row._newRecord) && (proposedRecord != row._newRecord))
{
// the transition from DataRowState.Added -> DataRowState.Modified
// with same current record but new original record
// needs to raise an ItemChanged or ItemMoved instead of ItemAdded in the ListChanged event.
// for indexes/views listening for both DataViewRowState.Added | DataViewRowState.ModifiedOriginal
originalRecord = row._newRecord;
}
DataViewRowState originalRecordStatePre = row.GetRecordState(originalRecord);
DataViewRowState proposedRecordStatePre = row.GetRecordState(proposedRecord);
row._oldRecord = proposedRecord;
if (proposedRecord != -1)
{
_recordManager[proposedRecord] = row;
}
DataViewRowState originalRecordStatePost = row.GetRecordState(originalRecord);
DataViewRowState proposedRecordStatePost = row.GetRecordState(proposedRecord);
RecordStateChanged(originalRecord, originalRecordStatePre, originalRecordStatePost,
proposedRecord, proposedRecordStatePre, proposedRecordStatePost);
}
else
{
row._oldRecord = proposedRecord;
if (proposedRecord != -1)
{
_recordManager[proposedRecord] = row;
}
}
}
finally
{
if ((originalRecord != -1) && (originalRecord != row._tempRecord) &&
(originalRecord != row._oldRecord) && (originalRecord != row._newRecord))
{
FreeRecord(ref originalRecord);
}
// else during an event 'row.AcceptChanges(); row.BeginEdit(); row.EndEdit();'
if (row.RowState == DataRowState.Detached && row.rowID != -1)
{
RemoveRow(row, false);
}
}
}
private void RestoreShadowIndexes()
{
Debug.Assert(1 <= _shadowCount, "unexpected negative shadow count");
_shadowCount--;
if (0 == _shadowCount)
{
_shadowIndexes = null;
}
}
private void SetShadowIndexes()
{
if (null == _shadowIndexes)
{
Debug.Assert(0 == _shadowCount, "unexpected count");
_shadowIndexes = LiveIndexes;
_shadowCount = 1;
}
else
{
Debug.Assert(1 <= _shadowCount, "unexpected negative shadow count");
_shadowCount++;
}
}
internal void ShadowIndexCopy()
{
if (_shadowIndexes == _indexes)
{
Debug.Assert(0 < _indexes.Count, "unexpected");
_shadowIndexes = new List<Index>(_indexes);
}
}
/// <summary>
/// Returns the <see cref='System.Data.DataTable.TableName'/> and <see cref='System.Data.DataTable.DisplayExpression'/>, if there is one as a concatenated string.
/// </summary>
public override string ToString() => _displayExpression == null ?
TableName :
TableName + " + " + DisplayExpressionInternal;
public void BeginLoadData()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.BeginLoadData|API> {0}", ObjectID);
try
{
if (_inDataLoad)
{
return;
}
_inDataLoad = true;
Debug.Assert(null == _loadIndex, "loadIndex should already be null");
_loadIndex = null;
// LoadDataRow may have been called before BeginLoadData and already
// initialized loadIndexwithOriginalAdded & loadIndexwithCurrentDeleted
_initialLoad = (Rows.Count == 0);
if (_initialLoad)
{
SuspendIndexEvents();
}
else
{
if (_primaryKey != null)
{
_loadIndex = _primaryKey.Key.GetSortIndex(DataViewRowState.OriginalRows);
}
_loadIndex?.AddRef();
}
if (DataSet != null)
{
_savedEnforceConstraints = DataSet.EnforceConstraints;
DataSet.EnforceConstraints = false;
}
else
{
EnforceConstraints = false;
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
public void EndLoadData()
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.EndLoadData|API> {0}", ObjectID);
try
{
if (!_inDataLoad)
{
return;
}
_loadIndex?.RemoveRef();
_loadIndexwithOriginalAdded?.RemoveRef();
_loadIndexwithCurrentDeleted?.RemoveRef();
_loadIndex = null;
_loadIndexwithOriginalAdded = null;
_loadIndexwithCurrentDeleted = null;
_inDataLoad = false;
RestoreIndexEvents(false);
if (DataSet != null)
{
DataSet.EnforceConstraints = _savedEnforceConstraints;
}
else
{
EnforceConstraints = true;
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
/// <summary>
/// Finds and updates a specific row. If no matching
/// row is found, a new row is created using the given values.
/// </summary>
public DataRow LoadDataRow(object?[] values, bool fAcceptChanges)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.LoadDataRow|API> {0}, fAcceptChanges={1}", ObjectID, fAcceptChanges);
try
{
DataRow row;
if (_inDataLoad)
{
int record = NewRecordFromArray(values);
if (_loadIndex != null)
{
// not expecting LiveIndexes to clear the index we use between calls to LoadDataRow
Debug.Assert(2 <= _loadIndex.RefCount, "bad loadIndex.RefCount");
int result = _loadIndex.FindRecord(record);
if (result != -1)
{
int resultRecord = _loadIndex.GetRecord(result);
row = _recordManager[resultRecord]!;
Debug.Assert(row != null, "Row can't be null for index record");
row.CancelEdit();
if (row.RowState == DataRowState.Deleted)
{
SetNewRecord(row, row._oldRecord, DataRowAction.Rollback, false, true);
}
SetNewRecord(row, record, DataRowAction.Change, false, true);
if (fAcceptChanges)
{
row.AcceptChanges();
}
return row;
}
}
row = NewRow(record);
AddRow(row);
if (fAcceptChanges)
{
row.AcceptChanges();
}
return row;
}
else
{
// In case, BeginDataLoad is not called yet
row = UpdatingAdd(values);
if (fAcceptChanges)
{
row.AcceptChanges();
}
return row;
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
/// <summary>
/// Finds and updates a specific row. If no matching row is found, a new row is created using the given values.
/// </summary>
public DataRow LoadDataRow(object?[] values, LoadOption loadOption)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.LoadDataRow|API> {0}, loadOption={1}", ObjectID, loadOption);
try
{
Index? indextoUse = null;
if (_primaryKey != null)
{
if (loadOption == LoadOption.Upsert)
{
// CurrentVersion, and Deleted
if (_loadIndexwithCurrentDeleted == null)
{
_loadIndexwithCurrentDeleted = _primaryKey.Key.GetSortIndex(DataViewRowState.CurrentRows | DataViewRowState.Deleted);
Debug.Assert(_loadIndexwithCurrentDeleted != null, "loadIndexwithCurrentDeleted should not be null");
_loadIndexwithCurrentDeleted?.AddRef();
}
indextoUse = _loadIndexwithCurrentDeleted;
}
else
{
// CurrentVersion, and Deleted : OverwriteRow, PreserveCurrentValues
if (_loadIndexwithOriginalAdded == null)
{
_loadIndexwithOriginalAdded = _primaryKey.Key.GetSortIndex(DataViewRowState.OriginalRows | DataViewRowState.Added);
Debug.Assert(_loadIndexwithOriginalAdded != null, "loadIndexwithOriginalAdded should not be null");
_loadIndexwithOriginalAdded?.AddRef();
}
indextoUse = _loadIndexwithOriginalAdded;
}
// not expecting LiveIndexes to clear the index we use between calls to LoadDataRow
Debug.Assert(2 <= indextoUse!.RefCount, "bad indextoUse.RefCount");
}
if (_inDataLoad && !AreIndexEventsSuspended)
{
// we do not want to fire any listchanged in new Load/Fill
SuspendIndexEvents(); // so suspend events here(not suspended == table already has some rows initially)
}
DataRow dataRow = LoadRow(values, loadOption, indextoUse); // if indextoUse == null, it means we dont have PK,
// so LoadRow will take care of just adding the row to end
return dataRow;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
internal DataRow UpdatingAdd(object?[] values)
{
Index? index = null;
if (_primaryKey != null)
{
index = _primaryKey.Key.GetSortIndex(DataViewRowState.OriginalRows);
}
if (index != null)
{
int record = NewRecordFromArray(values);
int result = index.FindRecord(record);
if (result != -1)
{
int resultRecord = index.GetRecord(result);
DataRow row = _recordManager[resultRecord]!;
Debug.Assert(row != null, "Row can't be null for index record");
row.RejectChanges();
SetNewRecord(row, record);
return row;
}
DataRow row2 = NewRow(record);
Rows.Add(row2);
return row2;
}
return Rows.Add(values);
}
internal static bool UpdatingCurrent(DataRowAction action)
{
return (action == DataRowAction.Add || action == DataRowAction.Change ||
action == DataRowAction.Rollback || action == DataRowAction.ChangeOriginal ||
action == DataRowAction.ChangeCurrentAndOriginal);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "DataColumn with null expression and int data type is safe.")]
internal DataColumn AddUniqueKey(int position)
{
if (_colUnique != null)
return _colUnique;
// check to see if we can use already existent PrimaryKey
DataColumn[] pkey = PrimaryKey;
if (pkey.Length == 1)
{
// We have one-column primary key, so we can use it in our hierarchical relation
return pkey[0];
}
// add Unique, but not primaryKey to the table
string keyName = XMLSchema.GenUniqueColumnName(TableName + "_Id", this);
DataColumn key = new DataColumn(keyName, typeof(int), null, MappingType.Hidden);
key.Prefix = _tablePrefix;
key.AutoIncrement = true;
key.AllowDBNull = false;
key.Unique = true;
if (position == -1)
{
Columns.Add(key);
}
else
{ // we do have a problem and Imy idea is it is bug. Ask Enzo while Code review. Why we do not set ordinal when we call AddAt?
for (int i = Columns.Count - 1; i >= position; i--)
{
Columns[i].SetOrdinalInternal(i + 1);
}
Columns.AddAt(position, key);
key.SetOrdinalInternal(position);
}
if (pkey.Length == 0)
{
PrimaryKey = new DataColumn[] { key };
}
_colUnique = key;
return _colUnique;
}
internal DataColumn AddUniqueKey() => AddUniqueKey(-1);
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Expression is null and potential problem with data type has already been reported when constructing parentKey")]
internal DataColumn AddForeignKey(DataColumn parentKey)
{
Debug.Assert(parentKey != null, "AddForeignKey: Invalid parameter.. related primary key is null");
string keyName = XMLSchema.GenUniqueColumnName(parentKey.ColumnName, this);
DataColumn foreignKey = new DataColumn(keyName, parentKey.DataType, null, MappingType.Hidden);
Columns.Add(foreignKey);
return foreignKey;
}
internal void UpdatePropertyDescriptorCollectionCache()
{
_propertyDescriptorCollectionCache = null;
}
/// <summary>
/// Retrieves an array of properties that the given component instance
/// provides. This may differ from the set of properties the class
/// provides. If the component is sited, the site may add or remove
/// additional properties. The returned array of properties will be
/// filtered by the given set of attributes.
/// </summary>
internal PropertyDescriptorCollection GetPropertyDescriptorCollection()
{
if (_propertyDescriptorCollectionCache == null)
{
int columnsCount = Columns.Count;
int relationsCount = ChildRelations.Count;
PropertyDescriptor[] props = new PropertyDescriptor[columnsCount + relationsCount];
{
for (int i = 0; i < columnsCount; i++)
{
props[i] = new DataColumnPropertyDescriptor(Columns[i]);
}
for (int i = 0; i < relationsCount; i++)
{
props[columnsCount + i] = new DataRelationPropertyDescriptor(ChildRelations[i]);
}
}
_propertyDescriptorCollectionCache = new PropertyDescriptorCollection(props);
}
return _propertyDescriptorCollectionCache;
}
internal XmlQualifiedName TypeName
{
get { return ((_typeName == null) ? XmlQualifiedName.Empty : (XmlQualifiedName)_typeName); }
set { _typeName = value; }
}
public void Merge(DataTable table) =>
Merge(table, false, MissingSchemaAction.Add);
public void Merge(DataTable table, bool preserveChanges) =>
Merge(table, preserveChanges, MissingSchemaAction.Add);
public void Merge(DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Merge|API> {0}, table={1}, preserveChanges={2}, missingSchemaAction={3}", ObjectID, (table != null) ? table.ObjectID : 0, preserveChanges, missingSchemaAction);
try
{
if (table == null)
{
throw ExceptionBuilder.ArgumentNull(nameof(table));
}
switch (missingSchemaAction)
{
case MissingSchemaAction.Add:
case MissingSchemaAction.Ignore:
case MissingSchemaAction.Error:
case MissingSchemaAction.AddWithKey:
Merger merger = new Merger(this, preserveChanges, missingSchemaAction);
merger.MergeTable(table);
break;
default:
throw ADP.InvalidMissingSchemaAction(missingSchemaAction);
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode("Members from types used in the expression column to be trimmed if not referenced directly.")]
public void Load(IDataReader reader) => Load(reader, LoadOption.PreserveChanges, null);
[RequiresUnreferencedCode("Using LoadOption may cause members from types used in the expression column to be trimmed if not referenced directly.")]
public void Load(IDataReader reader, LoadOption loadOption) => Load(reader, loadOption, null);
[RequiresUnreferencedCode("Using LoadOption may cause members from types used in the expression column to be trimmed if not referenced directly.")]
public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler? errorHandler)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.Load|API> {0}, loadOption={1}", ObjectID, loadOption);
try
{
if (PrimaryKey.Length == 0)
{
DataTableReader? dtReader = reader as DataTableReader;
if (dtReader != null && dtReader.CurrentDataTable == this)
{
return; // if not return, it will go to infinite loop
}
}
Common.LoadAdapter adapter = new Common.LoadAdapter();
adapter.FillLoadOption = loadOption;
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
if (null != errorHandler)
{
adapter.FillError += errorHandler;
}
adapter.FillFromReader(new DataTable[] { this }, reader, 0, 0);
if (!reader.IsClosed && !reader.NextResult())
{
reader.Close();
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
private DataRow LoadRow(object?[] values, LoadOption loadOption, Index? searchIndex)
{
int recordNo;
DataRow? dataRow = null;
if (searchIndex != null)
{
int[] primaryKeyIndex = Array.Empty<int>();
if (_primaryKey != null)
{
// we do check above for PK, but in case if someone else gives us some index unrelated to PK
primaryKeyIndex = new int[_primaryKey.ColumnsReference.Length];
for (int i = 0; i < _primaryKey.ColumnsReference.Length; i++)
{
primaryKeyIndex[i] = _primaryKey.ColumnsReference[i].Ordinal;
}
}
object[] keys = new object[primaryKeyIndex.Length];
for (int i = 0; i < primaryKeyIndex.Length; i++)
{
// TODO: There may be null values in the key
keys[i] = values[primaryKeyIndex[i]]!;
}
Range result = searchIndex.FindRecords(keys);
if (!result.IsNull)
{
Debug.Assert(result.Max >= result.Min);
int deletedRowUpsertCount = 0;
for (int i = result.Min; i <= result.Max; i++)
{
int resultRecord = searchIndex.GetRecord(i);
dataRow = _recordManager[resultRecord]!;
recordNo = NewRecordFromArray(values);
// values array is being reused by DataAdapter, do not modify the values array
for (int count = 0; count < values.Length; count++)
{
if (null == values[count])
{
_columnCollection[count].Copy(resultRecord, recordNo);
}
}
for (int count = values.Length; count < _columnCollection.Count; count++)
{
_columnCollection[count].Copy(resultRecord, recordNo); // if there are missing values
}
if (loadOption != LoadOption.Upsert || dataRow.RowState != DataRowState.Deleted)
{
SetDataRowWithLoadOption(dataRow, recordNo, loadOption, true);
}
else
{
deletedRowUpsertCount++;
}
}
if (0 == deletedRowUpsertCount)
{
return dataRow!;
}
}
}
recordNo = NewRecordFromArray(values);
dataRow = NewRow(recordNo);
// fire rowChanging event here
DataRowAction action;
DataRowChangeEventArgs? drcevent = null;
switch (loadOption)
{
case LoadOption.OverwriteChanges:
case LoadOption.PreserveChanges:
action = DataRowAction.ChangeCurrentAndOriginal;
break;
case LoadOption.Upsert:
action = DataRowAction.Add;
break;
default:
throw ExceptionBuilder.ArgumentOutOfRange(nameof(LoadOption));
}
drcevent = RaiseRowChanging(null, dataRow, action);
InsertRow(dataRow, -1, -1, false);
switch (loadOption)
{
case LoadOption.OverwriteChanges:
case LoadOption.PreserveChanges:
SetOldRecord(dataRow, recordNo);
break;
case LoadOption.Upsert:
break;
default:
throw ExceptionBuilder.ArgumentOutOfRange(nameof(LoadOption));
}
RaiseRowChanged(drcevent, dataRow, action);
return dataRow;
}
private void SetDataRowWithLoadOption(DataRow dataRow, int recordNo, LoadOption loadOption, bool checkReadOnly)
{
bool hasError = false;
if (checkReadOnly)
{
foreach (DataColumn dc in Columns)
{
if (dc.ReadOnly && !dc.Computed)
{
switch (loadOption)
{
case LoadOption.OverwriteChanges:
if ((dataRow[dc, DataRowVersion.Current] != dc[recordNo]) || (dataRow[dc, DataRowVersion.Original] != dc[recordNo]))
hasError = true;
break;
case LoadOption.Upsert:
if (dataRow[dc, DataRowVersion.Current] != dc[recordNo])
hasError = true;
break;
case LoadOption.PreserveChanges:
if (dataRow[dc, DataRowVersion.Original] != dc[recordNo])
hasError = true;
break;
}
}
}
} // No Event should be fired in SenNewRecord and SetOldRecord
// fire rowChanging event here
DataRowChangeEventArgs? drcevent = null;
DataRowAction action = DataRowAction.Nothing;
int cacheTempRecord = dataRow._tempRecord;
dataRow._tempRecord = recordNo;
switch (loadOption)
{
case LoadOption.OverwriteChanges:
action = DataRowAction.ChangeCurrentAndOriginal;
break;
case LoadOption.Upsert:
switch (dataRow.RowState)
{
case DataRowState.Unchanged:
// let see if the incoming value has the same values as existing row, so compare records
foreach (DataColumn dc in dataRow.Table.Columns)
{
if (0 != dc.Compare(dataRow._newRecord, recordNo))
{
action = DataRowAction.Change;
break;
}
}
break;
case DataRowState.Deleted:
Debug.Fail("LoadOption.Upsert with deleted row, should not be here");
break;
default:
action = DataRowAction.Change;
break;
}
break;
case LoadOption.PreserveChanges:
action = dataRow.RowState switch
{
DataRowState.Unchanged => DataRowAction.ChangeCurrentAndOriginal,
_ => DataRowAction.ChangeOriginal,
};
break;
default:
throw ExceptionBuilder.ArgumentOutOfRange(nameof(LoadOption));
}
try
{
drcevent = RaiseRowChanging(null, dataRow, action);
if (action == DataRowAction.Nothing)
{ // RaiseRowChanging does not fire for DataRowAction.Nothing
dataRow._inChangingEvent = true;
try
{
drcevent = OnRowChanging(drcevent, dataRow, action);
}
finally
{
dataRow._inChangingEvent = false;
}
}
}
finally
{
Debug.Assert(dataRow._tempRecord == recordNo, "tempRecord has been changed in event handler");
if (DataRowState.Detached == dataRow.RowState)
{
// 'row.Table.Remove(row);'
if (-1 != cacheTempRecord)
{
FreeRecord(ref cacheTempRecord);
}
}
else
{
if (dataRow._tempRecord != recordNo)
{
// 'row.EndEdit(); row.BeginEdit(); '
if (-1 != cacheTempRecord)
{
FreeRecord(ref cacheTempRecord);
}
if (-1 != recordNo)
{
FreeRecord(ref recordNo);
}
recordNo = dataRow._tempRecord;
}
else
{
dataRow._tempRecord = cacheTempRecord;
}
}
}
if (dataRow._tempRecord != -1)
{
dataRow.CancelEdit();
}
switch (loadOption)
{
case LoadOption.OverwriteChanges:
SetNewRecord(dataRow, recordNo, DataRowAction.Change, false, false);
SetOldRecord(dataRow, recordNo);
break;
case LoadOption.Upsert:
if (dataRow.RowState == DataRowState.Unchanged)
{
SetNewRecord(dataRow, recordNo, DataRowAction.Change, false, false);
if (!dataRow.HasChanges())
{
SetOldRecord(dataRow, recordNo);
}
}
else
{
if (dataRow.RowState == DataRowState.Deleted)
dataRow.RejectChanges();
SetNewRecord(dataRow, recordNo, DataRowAction.Change, false, false);
}
break;
case LoadOption.PreserveChanges:
if (dataRow.RowState == DataRowState.Unchanged)
{
// if ListChanged event deletes dataRow
SetOldRecord(dataRow, recordNo); // do not fire event
SetNewRecord(dataRow, recordNo, DataRowAction.Change, false, false);
}
else
{
// if modified/ added / deleted we want this operation to fire event (just for LoadOption.PreserveCurrentValues)
SetOldRecord(dataRow, recordNo);
}
break;
default:
throw ExceptionBuilder.ArgumentOutOfRange(nameof(LoadOption));
}
if (hasError)
{
string error = SR.Load_ReadOnlyDataModified;
if (dataRow.RowError.Length == 0)
{
dataRow.RowError = error;
}
else
{
dataRow.RowError += " ]:[ " + error;
}
foreach (DataColumn dc in Columns)
{
if (dc.ReadOnly && !dc.Computed)
{
dataRow.SetColumnError(dc, error);
}
}
}
drcevent = RaiseRowChanged(drcevent, dataRow, action);
if (action == DataRowAction.Nothing)
{
// RaiseRowChanged does not fire for DataRowAction.Nothing
dataRow._inChangingEvent = true;
try
{
OnRowChanged(drcevent, dataRow, action);
}
finally
{
dataRow._inChangingEvent = false;
}
}
}
public DataTableReader CreateDataReader() => new DataTableReader(this);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(Stream? stream) => WriteXml(stream, XmlWriteMode.IgnoreSchema, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(Stream? stream, bool writeHierarchy) => WriteXml(stream, XmlWriteMode.IgnoreSchema, writeHierarchy);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(TextWriter? writer) => WriteXml(writer, XmlWriteMode.IgnoreSchema, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(TextWriter? writer, bool writeHierarchy) => WriteXml(writer, XmlWriteMode.IgnoreSchema, writeHierarchy);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(XmlWriter? writer) => WriteXml(writer, XmlWriteMode.IgnoreSchema, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(XmlWriter? writer, bool writeHierarchy) => WriteXml(writer, XmlWriteMode.IgnoreSchema, writeHierarchy);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(string fileName) => WriteXml(fileName, XmlWriteMode.IgnoreSchema, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(string fileName, bool writeHierarchy) => WriteXml(fileName, XmlWriteMode.IgnoreSchema, writeHierarchy);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(Stream? stream, XmlWriteMode mode) => WriteXml(stream, mode, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(Stream? stream, XmlWriteMode mode, bool writeHierarchy)
{
if (stream != null)
{
XmlTextWriter w = new XmlTextWriter(stream, null);
w.Formatting = Formatting.Indented;
WriteXml(w, mode, writeHierarchy);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(TextWriter? writer, XmlWriteMode mode)
{
WriteXml(writer, mode, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(TextWriter? writer, XmlWriteMode mode, bool writeHierarchy)
{
if (writer != null)
{
XmlTextWriter w = new XmlTextWriter(writer);
w.Formatting = Formatting.Indented;
WriteXml(w, mode, writeHierarchy);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(XmlWriter? writer, XmlWriteMode mode)
{
WriteXml(writer, mode, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(XmlWriter? writer, XmlWriteMode mode, bool writeHierarchy)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.WriteXml|API> {0}, mode={1}", ObjectID, mode);
try
{
if (_tableName.Length == 0)
{
throw ExceptionBuilder.CanNotSerializeDataTableWithEmptyName();
}
// Generate SchemaTree and write it out
if (writer != null)
{
if (mode == XmlWriteMode.DiffGram)
{
// Create and save the updates
new NewDiffgramGen(this, writeHierarchy).Save(writer, this);
}
else
{
// Create and save xml data
if (mode == XmlWriteMode.WriteSchema)
{
DataSet? ds = null;
string? tablenamespace = _tableNamespace;
if (null == DataSet)
{
ds = new DataSet();
// if user set values on DataTable, it isn't necessary
// to set them on the DataSet because they won't be inherited
// but it is simpler to set them in both places
// if user did not set values on DataTable, it is required
// to set them on the DataSet so the table will inherit
// the value already on the Datatable
ds.SetLocaleValue(_culture, _cultureUserSet);
ds.CaseSensitive = CaseSensitive;
ds.Namespace = Namespace;
ds.RemotingFormat = RemotingFormat;
ds.Tables.Add(this);
}
if (writer != null)
{
XmlDataTreeWriter xmldataWriter = new XmlDataTreeWriter(this, writeHierarchy);
xmldataWriter.Save(writer, /*mode == XmlWriteMode.WriteSchema*/true);
}
if (null != ds)
{
ds.Tables.Remove(this);
_tableNamespace = tablenamespace;
}
}
else
{
XmlDataTreeWriter xmldataWriter = new XmlDataTreeWriter(this, writeHierarchy);
xmldataWriter.Save(writer, /*mode == XmlWriteMode.WriteSchema*/ false);
}
}
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(string fileName, XmlWriteMode mode) => WriteXml(fileName, mode, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXml(string fileName, XmlWriteMode mode, bool writeHierarchy)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.WriteXml|API> {0}, fileName='{1}', mode={2}", ObjectID, fileName, mode);
try
{
using (XmlTextWriter xw = new XmlTextWriter(fileName, null))
{
xw.Formatting = Formatting.Indented;
xw.WriteStartDocument(true);
WriteXml(xw, mode, writeHierarchy);
xw.WriteEndDocument();
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(Stream? stream) => WriteXmlSchema(stream, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(Stream? stream, bool writeHierarchy)
{
if (stream == null)
{
return;
}
XmlTextWriter w = new XmlTextWriter(stream, null);
w.Formatting = Formatting.Indented;
WriteXmlSchema(w, writeHierarchy);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(TextWriter? writer) => WriteXmlSchema(writer, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(TextWriter? writer, bool writeHierarchy)
{
if (writer == null)
{
return;
}
XmlTextWriter w = new XmlTextWriter(writer);
w.Formatting = Formatting.Indented;
WriteXmlSchema(w, writeHierarchy);
}
private static bool CheckForClosureOnExpressions(DataTable dt, bool writeHierarchy)
{
List<DataTable> tableList = new List<DataTable>();
tableList.Add(dt);
if (writeHierarchy)
{
CreateTableList(dt, tableList);
}
return CheckForClosureOnExpressionTables(tableList);
}
private static bool CheckForClosureOnExpressionTables(List<DataTable> tableList)
{
Debug.Assert(tableList != null, "tableList shouldnot be null");
foreach (DataTable datatable in tableList)
{
foreach (DataColumn dc in datatable.Columns)
{
if (dc.Expression.Length != 0)
{
DataColumn[] dependency = dc.DataExpression!.GetDependency();
for (int j = 0; j < dependency.Length; j++)
{
if (!(tableList.Contains(dependency[j].Table!)))
{
return false;
}
}
}
}
}
return true;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(XmlWriter? writer) => WriteXmlSchema(writer, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(XmlWriter? writer, bool writeHierarchy)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.WriteXmlSchema|API> {0}", ObjectID);
try
{
if (_tableName.Length == 0)
{
throw ExceptionBuilder.CanNotSerializeDataTableWithEmptyName();
}
if (!CheckForClosureOnExpressions(this, writeHierarchy))
{
throw ExceptionBuilder.CanNotSerializeDataTableHierarchy();
}
DataSet? ds = null;
string? tablenamespace = _tableNamespace; //SQL BU Defect Tracking 286968
// Generate SchemaTree and write it out
if (null == DataSet)
{
ds = new DataSet();
// if user set values on DataTable, it isn't necessary
// to set them on the DataSet because they won't be inherited
// but it is simpler to set them in both places
// if user did not set values on DataTable, it is required
// to set them on the DataSet so the table will inherit
// the value already on the Datatable
ds.SetLocaleValue(_culture, _cultureUserSet);
ds.CaseSensitive = CaseSensitive;
ds.Namespace = Namespace;
ds.RemotingFormat = RemotingFormat;
ds.Tables.Add(this);
}
if (writer != null)
{
XmlTreeGen treeGen = new XmlTreeGen(SchemaFormat.Public);
treeGen.Save(null, this, writer, writeHierarchy);
}
if (null != ds)
{
ds.Tables.Remove(this);
_tableNamespace = tablenamespace;
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(string fileName) => WriteXmlSchema(fileName, false);
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void WriteXmlSchema(string fileName, bool writeHierarchy)
{
XmlTextWriter xw = new XmlTextWriter(fileName, null);
try
{
xw.Formatting = Formatting.Indented;
xw.WriteStartDocument(true);
WriteXmlSchema(xw, writeHierarchy);
xw.WriteEndDocument();
}
finally
{
xw.Close();
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public XmlReadMode ReadXml(Stream? stream)
{
if (stream == null)
{
return XmlReadMode.Auto;
}
XmlTextReader xr = new XmlTextReader(stream);
// Prevent Dtd entity in DataTable
xr.XmlResolver = null;
return ReadXml(xr, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public XmlReadMode ReadXml(TextReader? reader)
{
if (reader == null)
{
return XmlReadMode.Auto;
}
XmlTextReader xr = new XmlTextReader(reader);
// Prevent Dtd entity in DataTable
xr.XmlResolver = null;
return ReadXml(xr, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public XmlReadMode ReadXml(string fileName)
{
XmlTextReader xr = new XmlTextReader(fileName);
// Prevent Dtd entity in DataTable
xr.XmlResolver = null;
try
{
return ReadXml(xr, false);
}
finally
{
xr.Close();
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public XmlReadMode ReadXml(XmlReader? reader) => ReadXml(reader, false);
private void RestoreConstraint(bool originalEnforceConstraint)
{
if (DataSet != null)
{
DataSet.EnforceConstraints = originalEnforceConstraint;
}
else
{
EnforceConstraints = originalEnforceConstraint;
}
}
private bool IsEmptyXml(XmlReader reader)
{
if (reader.IsEmptyElement)
{
if (reader.AttributeCount == 0 || (reader.LocalName == Keywords.DIFFGRAM && reader.NamespaceURI == Keywords.DFFNS))
{
return true;
}
if (reader.AttributeCount == 1)
{
reader.MoveToAttribute(0);
if ((Namespace == reader.Value) &&
(Prefix == reader.LocalName) &&
(reader.Prefix == Keywords.XMLNS) &&
(reader.NamespaceURI == Keywords.XSD_XMLNS_NS))
{
return true;
}
}
}
return false;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
internal XmlReadMode ReadXml(XmlReader? reader, bool denyResolving)
{
IDisposable? restrictedScope = null;
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.ReadXml|INFO> {0}, denyResolving={1}", ObjectID, denyResolving);
try
{
restrictedScope = TypeLimiter.EnterRestrictedScope(this);
RowDiffIdUsageSection rowDiffIdUsage = default;
try
{
bool fDataFound = false;
bool fSchemaFound = false;
bool fDiffsFound = false;
bool fIsXdr = false;
int iCurrentDepth = -1;
XmlReadMode ret = XmlReadMode.Auto;
// clear the hashtable to avoid conflicts between diffgrams, SqlHotFix 782
rowDiffIdUsage.Prepare(this);
if (reader == null)
{
return ret;
}
bool originalEnforceConstraint = false;
if (DataSet != null)
{
originalEnforceConstraint = DataSet.EnforceConstraints;
DataSet.EnforceConstraints = false;
}
else
{
originalEnforceConstraint = EnforceConstraints;
EnforceConstraints = false;
}
if (reader is XmlTextReader)
{
((XmlTextReader)reader).WhitespaceHandling = WhitespaceHandling.Significant;
}
XmlDocument xdoc = new XmlDocument(); // we may need this to infer the schema
XmlDataLoader? xmlload = null;
reader.MoveToContent();
if (Columns.Count == 0)
{
if (IsEmptyXml(reader))
{
reader.Read();
return ret;
}
}
if (reader.NodeType == XmlNodeType.Element)
{
iCurrentDepth = reader.Depth;
if ((reader.LocalName == Keywords.DIFFGRAM) && (reader.NamespaceURI == Keywords.DFFNS))
{
if (Columns.Count == 0)
{
if (reader.IsEmptyElement)
{
reader.Read();
return XmlReadMode.DiffGram;
}
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
ReadXmlDiffgram(reader);
// read the closing tag of the current element
ReadEndElement(reader);
RestoreConstraint(originalEnforceConstraint);
return XmlReadMode.DiffGram;
}
// if reader points to the schema load it
if (reader.LocalName == Keywords.XDR_SCHEMA && reader.NamespaceURI == Keywords.XDRNS)
{
// load XDR schema and exit
ReadXDRSchema(reader);
RestoreConstraint(originalEnforceConstraint);
return XmlReadMode.ReadSchema; //since the top level element is a schema return
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI == Keywords.XSDNS)
{
// load XSD schema and exit
ReadXmlSchema(reader, denyResolving);
RestoreConstraint(originalEnforceConstraint);
return XmlReadMode.ReadSchema; //since the top level element is a schema return
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI.StartsWith(Keywords.XSD_NS_START, StringComparison.Ordinal))
{
if (DataSet != null)
{ // we should not throw for constraint, we already will throw for unsupported schema, so restore enforce cost, but not via property
DataSet.RestoreEnforceConstraints(originalEnforceConstraint);
}
else
{
_enforceConstraints = originalEnforceConstraint;
}
throw ExceptionBuilder.DataSetUnsupportedSchema(Keywords.XSDNS);
}
// now either the top level node is a table and we load it through dataReader...
// ... or backup the top node and all its attributes because we may need to InferSchema
XmlElement topNode = xdoc.CreateElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
if (reader.HasAttributes)
{
int attrCount = reader.AttributeCount;
for (int i = 0; i < attrCount; i++)
{
reader.MoveToAttribute(i);
if (reader.NamespaceURI.Equals(Keywords.XSD_XMLNS_NS))
{
topNode.SetAttribute(reader.Name, reader.GetAttribute(i));
}
else
{
XmlAttribute attr = topNode.SetAttributeNode(reader.LocalName, reader.NamespaceURI);
attr.Prefix = reader.Prefix;
attr.Value = reader.GetAttribute(i);
}
}
}
reader.Read();
while (MoveToElement(reader, iCurrentDepth))
{
if ((reader.LocalName == Keywords.DIFFGRAM) && (reader.NamespaceURI == Keywords.DFFNS))
{
ReadXmlDiffgram(reader);
// read the closing tag of the current element
ReadEndElement(reader);
RestoreConstraint(originalEnforceConstraint);
return XmlReadMode.DiffGram;
}
// if reader points to the schema load it...
if (!fSchemaFound && !fDataFound && reader.LocalName == Keywords.XDR_SCHEMA && reader.NamespaceURI == Keywords.XDRNS)
{
// load XDR schema and exit
ReadXDRSchema(reader);
fSchemaFound = true;
fIsXdr = true;
continue;
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI == Keywords.XSDNS)
{
// load XSD schema and exit
ReadXmlSchema(reader, denyResolving);
fSchemaFound = true;
continue;
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI.StartsWith(Keywords.XSD_NS_START, StringComparison.Ordinal))
{
if (DataSet != null)
{
// we should not throw for constraint, we already will throw for unsupported schema, so restore enforce cost, but not via property
DataSet.RestoreEnforceConstraints(originalEnforceConstraint);
}
else
{
_enforceConstraints = originalEnforceConstraint;
}
throw ExceptionBuilder.DataSetUnsupportedSchema(Keywords.XSDNS);
}
if ((reader.LocalName == Keywords.DIFFGRAM) && (reader.NamespaceURI == Keywords.DFFNS))
{
ReadXmlDiffgram(reader);
fDiffsFound = true;
ret = XmlReadMode.DiffGram;
}
else
{
// we found data here
fDataFound = true;
if (!fSchemaFound && Columns.Count == 0)
{
XmlNode node = xdoc.ReadNode(reader)!;
topNode.AppendChild(node);
}
else
{
xmlload ??= new XmlDataLoader(this, fIsXdr, topNode, false);
xmlload.LoadData(reader);
ret = fSchemaFound ? XmlReadMode.ReadSchema : XmlReadMode.IgnoreSchema;
}
}
}
// read the closing tag of the current element
ReadEndElement(reader);
// now top node contains the data part
xdoc.AppendChild(topNode);
if (!fSchemaFound && Columns.Count == 0)
{
if (IsEmptyXml(reader))
{
reader.Read();
return ret;
}
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
xmlload ??= new XmlDataLoader(this, fIsXdr, false);
// so we InferSchema
if (!fDiffsFound)
{
// we need to add support for inference here
}
}
RestoreConstraint(originalEnforceConstraint);
return ret;
}
finally
{
rowDiffIdUsage.Cleanup();
}
}
finally
{
restrictedScope?.Dispose();
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
internal XmlReadMode ReadXml(XmlReader? reader, XmlReadMode mode, bool denyResolving)
{
IDisposable? restrictedScope = null;
RowDiffIdUsageSection rowDiffIdUsage = default;
try
{
restrictedScope = TypeLimiter.EnterRestrictedScope(this);
bool fSchemaFound = false;
bool fDataFound = false;
bool fIsXdr = false;
int iCurrentDepth = -1;
XmlReadMode ret = mode;
// prepare and cleanup rowDiffId hashtable
rowDiffIdUsage.Prepare(this);
if (reader == null)
{
return ret;
}
bool originalEnforceConstraint = false;
if (DataSet != null)
{
originalEnforceConstraint = DataSet.EnforceConstraints;
DataSet.EnforceConstraints = false;
}
else
{
originalEnforceConstraint = EnforceConstraints;
EnforceConstraints = false;
}
if (reader is XmlTextReader)
((XmlTextReader)reader).WhitespaceHandling = WhitespaceHandling.Significant;
XmlDocument xdoc = new XmlDocument(); // we may need this to infer the schema
if ((mode != XmlReadMode.Fragment) && (reader.NodeType == XmlNodeType.Element))
{
iCurrentDepth = reader.Depth;
}
reader.MoveToContent();
if (Columns.Count == 0)
{
if (IsEmptyXml(reader))
{
reader.Read();
return ret;
}
}
XmlDataLoader? xmlload = null;
if (reader.NodeType == XmlNodeType.Element)
{
XmlElement? topNode = null;
if (mode == XmlReadMode.Fragment)
{
xdoc.AppendChild(xdoc.CreateElement("ds_sqlXmlWraPPeR"));
topNode = xdoc.DocumentElement!;
}
else
{
//handle the top node
if ((reader.LocalName == Keywords.DIFFGRAM) && (reader.NamespaceURI == Keywords.DFFNS))
{
if ((mode == XmlReadMode.DiffGram) || (mode == XmlReadMode.IgnoreSchema))
{
if (Columns.Count == 0)
{
if (reader.IsEmptyElement)
{
reader.Read();
return XmlReadMode.DiffGram;
}
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
ReadXmlDiffgram(reader);
// read the closing tag of the current element
ReadEndElement(reader);
}
else
{
reader.Skip();
}
RestoreConstraint(originalEnforceConstraint);
return ret;
}
if (reader.LocalName == Keywords.XDR_SCHEMA && reader.NamespaceURI == Keywords.XDRNS)
{
// load XDR schema and exit
if ((mode != XmlReadMode.IgnoreSchema) && (mode != XmlReadMode.InferSchema))
{
ReadXDRSchema(reader);
}
else
{
reader.Skip();
}
RestoreConstraint(originalEnforceConstraint);
return ret; //since the top level element is a schema return
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI == Keywords.XSDNS)
{
// load XSD schema and exit
if ((mode != XmlReadMode.IgnoreSchema) && (mode != XmlReadMode.InferSchema))
{
ReadXmlSchema(reader, denyResolving);
}
else
{
reader.Skip();
}
RestoreConstraint(originalEnforceConstraint);
return ret; //since the top level element is a schema return
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI.StartsWith(Keywords.XSD_NS_START, StringComparison.Ordinal))
{
if (DataSet != null)
{
// we should not throw for constraint, we already will throw for unsupported schema, so restore enforce cost, but not via property
DataSet.RestoreEnforceConstraints(originalEnforceConstraint);
}
else
{
_enforceConstraints = originalEnforceConstraint;
}
throw ExceptionBuilder.DataSetUnsupportedSchema(Keywords.XSDNS);
}
// now either the top level node is a table and we load it through dataReader...
// ... or backup the top node and all its attributes
topNode = xdoc.CreateElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
if (reader.HasAttributes)
{
int attrCount = reader.AttributeCount;
for (int i = 0; i < attrCount; i++)
{
reader.MoveToAttribute(i);
if (reader.NamespaceURI.Equals(Keywords.XSD_XMLNS_NS))
{
topNode.SetAttribute(reader.Name, reader.GetAttribute(i));
}
else
{
XmlAttribute attr = topNode.SetAttributeNode(reader.LocalName, reader.NamespaceURI);
attr.Prefix = reader.Prefix;
attr.Value = reader.GetAttribute(i);
}
}
}
reader.Read();
}
while (MoveToElement(reader, iCurrentDepth))
{
if (reader.LocalName == Keywords.XDR_SCHEMA && reader.NamespaceURI == Keywords.XDRNS)
{
// load XDR schema
if (!fSchemaFound && !fDataFound && (mode != XmlReadMode.IgnoreSchema) && (mode != XmlReadMode.InferSchema))
{
ReadXDRSchema(reader);
fSchemaFound = true;
fIsXdr = true;
}
else
{
reader.Skip();
}
continue;
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI == Keywords.XSDNS)
{
// load XSD schema and exit
if ((mode != XmlReadMode.IgnoreSchema) && (mode != XmlReadMode.InferSchema))
{
ReadXmlSchema(reader, denyResolving);
fSchemaFound = true;
}
else
{
reader.Skip();
}
continue;
}
if ((reader.LocalName == Keywords.DIFFGRAM) && (reader.NamespaceURI == Keywords.DFFNS))
{
if ((mode == XmlReadMode.DiffGram) || (mode == XmlReadMode.IgnoreSchema))
{
if (Columns.Count == 0)
{
if (reader.IsEmptyElement)
{
reader.Read();
return XmlReadMode.DiffGram;
}
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
ReadXmlDiffgram(reader);
ret = XmlReadMode.DiffGram;
}
else
{
reader.Skip();
}
continue;
}
if (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI.StartsWith(Keywords.XSD_NS_START, StringComparison.Ordinal))
{
if (DataSet != null)
{
// we should not throw for constraint, we already will throw for unsupported schema, so restore enforce cost, but not via property
DataSet.RestoreEnforceConstraints(originalEnforceConstraint);
}
else
{
_enforceConstraints = originalEnforceConstraint;
}
throw ExceptionBuilder.DataSetUnsupportedSchema(Keywords.XSDNS);
}
if (mode == XmlReadMode.DiffGram)
{
reader.Skip();
continue; // we do not read data in diffgram mode
}
// if we are here we found some data
fDataFound = true;
if (mode == XmlReadMode.InferSchema)
{
//save the node in DOM until the end;
XmlNode node = xdoc.ReadNode(reader)!;
topNode.AppendChild(node);
}
else
{
if (Columns.Count == 0)
{
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
xmlload ??= new XmlDataLoader(this, fIsXdr, topNode, mode == XmlReadMode.IgnoreSchema);
xmlload.LoadData(reader);
}
} //end of the while
// read the closing tag of the current element
ReadEndElement(reader);
// now top node contains the data part
xdoc.AppendChild(topNode);
xmlload ??= new XmlDataLoader(this, fIsXdr, mode == XmlReadMode.IgnoreSchema);
if (mode == XmlReadMode.DiffGram)
{
// we already got the diffs through XmlReader interface
RestoreConstraint(originalEnforceConstraint);
return ret;
}
// Load Data
if (mode == XmlReadMode.InferSchema)
{
if (Columns.Count == 0)
{
throw ExceptionBuilder.DataTableInferenceNotSupported();
}
}
}
RestoreConstraint(originalEnforceConstraint);
return ret;
}
finally
{
restrictedScope?.Dispose();
// prepare and cleanup rowDiffId hashtable
rowDiffIdUsage.Cleanup();
}
}
internal static void ReadEndElement(XmlReader reader)
{
while (reader.NodeType == XmlNodeType.Whitespace)
{
reader.Skip();
}
if (reader.NodeType == XmlNodeType.None)
{
reader.Skip();
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
internal static void ReadXDRSchema(XmlReader reader)
{
XmlDocument xdoc = new XmlDocument(); // we may need this to infer the schema
xdoc.ReadNode(reader);
//consume and ignore it - No support
}
internal static bool MoveToElement(XmlReader reader, int depth)
{
while (!reader.EOF && reader.NodeType != XmlNodeType.EndElement && reader.NodeType != XmlNodeType.Element && reader.Depth > depth)
{
reader.Read();
}
return (reader.NodeType == XmlNodeType.Element);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
private void ReadXmlDiffgram(XmlReader reader)
{
// fill correctly
int d = reader.Depth;
bool fEnforce = EnforceConstraints;
EnforceConstraints = false;
DataTable newDt;
bool isEmpty;
if (Rows.Count == 0)
{
isEmpty = true;
newDt = this;
}
else
{
isEmpty = false;
newDt = Clone();
newDt.EnforceConstraints = false;
}
newDt.Rows._nullInList = 0;
reader.MoveToContent();
if ((reader.LocalName != Keywords.DIFFGRAM) && (reader.NamespaceURI != Keywords.DFFNS))
{
return;
}
reader.Read();
if (reader.NodeType == XmlNodeType.Whitespace)
{
MoveToElement(reader, reader.Depth - 1 /*iCurrentDepth*/); // skip over whitespace.
}
newDt._fInLoadDiffgram = true;
if (reader.Depth > d)
{
if ((reader.NamespaceURI != Keywords.DFFNS) && (reader.NamespaceURI != Keywords.MSDNS))
{
//we should be inside the dataset part
XmlDocument xdoc = new XmlDocument();
XmlElement node = xdoc.CreateElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
reader.Read();
if (reader.Depth - 1 > d)
{
XmlDataLoader xmlload = new XmlDataLoader(newDt, false, node, false);
xmlload._isDiffgram = true; // turn on the special processing
xmlload.LoadData(reader);
}
ReadEndElement(reader);
}
if (((reader.LocalName == Keywords.SQL_BEFORE) && (reader.NamespaceURI == Keywords.DFFNS)) ||
((reader.LocalName == Keywords.MSD_ERRORS) && (reader.NamespaceURI == Keywords.DFFNS)))
{
//this will consume the changes and the errors part
XMLDiffLoader diffLoader = new XMLDiffLoader();
diffLoader.LoadDiffGram(newDt, reader);
}
// get to the closing diff tag
while (reader.Depth > d)
{
reader.Read();
}
// read the closing tag
ReadEndElement(reader);
}
if (newDt.Rows._nullInList > 0)
{
throw ExceptionBuilder.RowInsertMissing(newDt.TableName);
}
newDt._fInLoadDiffgram = false;
List<DataTable> tableList = new List<DataTable>();
tableList.Add(this);
CreateTableList(this, tableList);
// this is terrible, optimize it
for (int i = 0; i < tableList.Count; i++)
{
DataRelation[] relations = tableList[i].NestedParentRelations;
foreach (DataRelation rel in relations)
{
if (rel != null && rel.ParentTable == tableList[i])
{
foreach (DataRow r in tableList[i].Rows)
{
foreach (DataRelation rel2 in relations)
{
r.CheckForLoops(rel2);
}
}
}
}
}
if (!isEmpty)
{
Merge(newDt);
}
EnforceConstraints = fEnforce;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
internal void ReadXSDSchema(XmlReader reader)
{
XmlSchemaSet sSet = new XmlSchemaSet();
while (reader.LocalName == Keywords.XSD_SCHEMA && reader.NamespaceURI == Keywords.XSDNS)
{
XmlSchema s = XmlSchema.Read(reader, null)!;
sSet.Add(s);
//read the end tag
ReadEndElement(reader);
}
sSet.Compile();
XSDSchema schema = new XSDSchema();
schema.LoadSchema(sSet, this);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void ReadXmlSchema(Stream? stream)
{
if (stream == null)
{
return;
}
ReadXmlSchema(new XmlTextReader(stream), false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void ReadXmlSchema(TextReader? reader)
{
if (reader == null)
{
return;
}
ReadXmlSchema(new XmlTextReader(reader), false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void ReadXmlSchema(string fileName)
{
XmlTextReader xr = new XmlTextReader(fileName);
try
{
ReadXmlSchema(xr, false);
}
finally
{
xr.Close();
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
public void ReadXmlSchema(XmlReader? reader)
{
ReadXmlSchema(reader, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
internal void ReadXmlSchema(XmlReader? reader, bool denyResolving)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataTable.ReadXmlSchema|INFO> {0}, denyResolving={1}", ObjectID, denyResolving);
try
{
DataSet ds = new DataSet();
SerializationFormat cachedRemotingFormat = RemotingFormat;
// fxcop: ReadXmlSchema will provide the CaseSensitive, Locale, Namespace information
ds.ReadXmlSchema(reader, denyResolving);
string CurrentTableFullName = ds.MainTableName;
if (string.IsNullOrEmpty(_tableName) && string.IsNullOrEmpty(CurrentTableFullName))
{
return;
}
DataTable? currentTable = null;
if (!string.IsNullOrEmpty(_tableName))
{
if (!string.IsNullOrEmpty(Namespace))
{
currentTable = ds.Tables[_tableName, Namespace];
}
else
{
int tableIndex = ds.Tables.InternalIndexOf(_tableName);
if (tableIndex > -1)
{
currentTable = ds.Tables[tableIndex];
}
}
}
else
{
string CurrentTableNamespace = string.Empty;
int nsSeparator = CurrentTableFullName.IndexOf(':');
if (nsSeparator > -1)
{
CurrentTableNamespace = CurrentTableFullName.Substring(0, nsSeparator);
}
string CurrentTableName = CurrentTableFullName.Substring(nsSeparator + 1);
currentTable = ds.Tables[CurrentTableName, CurrentTableNamespace];
}
if (currentTable == null)
{
string qTableName = string.Empty;
if (!string.IsNullOrEmpty(_tableName))
{
qTableName = (Namespace.Length > 0) ? (Namespace + ":" + _tableName) : _tableName;
}
else
{
qTableName = CurrentTableFullName;
}
throw ExceptionBuilder.TableNotFound(qTableName);
}
currentTable._remotingFormat = cachedRemotingFormat;
List<DataTable> tableList = new List<DataTable>();
tableList.Add(currentTable);
CreateTableList(currentTable, tableList);
List<DataRelation> relationList = new List<DataRelation>();
CreateRelationList(tableList, relationList);
if (relationList.Count == 0)
{
if (Columns.Count == 0)
{
DataTable tempTable = currentTable;
tempTable.CloneTo(this, null, false); // we may have issue Amir
if (DataSet == null && _tableNamespace == null)
{
// for standalone table, clone won't get these correctly, since they may come with inheritance
_tableNamespace = tempTable.Namespace;
}
}
return;
}
else
{
if (string.IsNullOrEmpty(TableName))
{
TableName = currentTable.TableName;
if (!string.IsNullOrEmpty(currentTable.Namespace))
{
Namespace = currentTable.Namespace;
}
}
if (DataSet == null)
{
DataSet dataset = new DataSet(ds.DataSetName);
// if user set values on DataTable, it isn't necessary
// to set them on the DataSet because they won't be inherited
// but it is simpler to set them in both places
// if user did not set values on DataTable, it is required
// to set them on the DataSet so the table will inherit
// the value already on the Datatable
dataset.SetLocaleValue(ds.Locale, ds.ShouldSerializeLocale());
dataset.CaseSensitive = ds.CaseSensitive;
dataset.Namespace = ds.Namespace;
dataset._mainTableName = ds._mainTableName;
dataset.RemotingFormat = ds.RemotingFormat;
dataset.Tables.Add(this);
}
Debug.Assert(DataSet != null);
CloneHierarchy(currentTable, DataSet, null);
foreach (DataTable tempTable in tableList)
{
DataTable destinationTable = DataSet.Tables[tempTable._tableName, tempTable.Namespace]!;
DataTable sourceTable = ds.Tables[tempTable._tableName, tempTable.Namespace]!;
foreach (Constraint tempConstrain in sourceTable.Constraints)
{
ForeignKeyConstraint? fkc = tempConstrain as ForeignKeyConstraint; // we have already cloned the UKC when cloning the datatable
if (fkc != null)
{
if (fkc.Table != fkc.RelatedTable)
{
if (tableList.Contains(fkc.Table!) && tableList.Contains(fkc.RelatedTable))
{
ForeignKeyConstraint newFKC = (ForeignKeyConstraint)fkc.Clone(destinationTable.DataSet!)!;
if (!destinationTable.Constraints.Contains(newFKC.ConstraintName))
{
destinationTable.Constraints.Add(newFKC); // we know that the dest table is already in the table
}
}
}
}
}
}
foreach (DataRelation rel in relationList)
{
if (!DataSet.Relations.Contains(rel.RelationName))
{
DataSet.Relations.Add(rel.Clone(DataSet));
}
}
bool hasExternaldependency = false;
foreach (DataTable tempTable in tableList)
{
foreach (DataColumn dc in tempTable.Columns)
{
hasExternaldependency = false;
if (dc.Expression.Length != 0)
{
DataColumn[] dependency = dc.DataExpression!.GetDependency();
for (int j = 0; j < dependency.Length; j++)
{
if (!tableList.Contains(dependency[j].Table!))
{
hasExternaldependency = true;
break;
}
}
}
if (!hasExternaldependency)
{
DataSet.Tables[tempTable.TableName, tempTable.Namespace]!.Columns[dc.ColumnName]!.Expression = dc.Expression;
}
}
hasExternaldependency = false;
}
}
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
private static void CreateTableList(DataTable currentTable, List<DataTable> tableList)
{
foreach (DataRelation r in currentTable.ChildRelations)
{
if (!tableList.Contains(r.ChildTable))
{
tableList.Add(r.ChildTable);
CreateTableList(r.ChildTable, tableList);
}
}
}
private static void CreateRelationList(List<DataTable> tableList, List<DataRelation> relationList)
{
foreach (DataTable table in tableList)
{
foreach (DataRelation r in table.ChildRelations)
{
if (tableList.Contains(r.ChildTable) && tableList.Contains(r.ParentTable))
{
relationList.Add(r);
}
}
}
}
public static XmlSchemaComplexType GetDataTableSchema(XmlSchemaSet? schemaSet)
{
XmlSchemaComplexType type = new XmlSchemaComplexType();
XmlSchemaSequence sequence = new XmlSchemaSequence();
XmlSchemaAny any = new XmlSchemaAny();
any.Namespace = XmlSchema.Namespace;
any.MinOccurs = 0;
any.MaxOccurs = decimal.MaxValue;
any.ProcessContents = XmlSchemaContentProcessing.Lax;
sequence.Items.Add(any);
any = new XmlSchemaAny();
any.Namespace = Keywords.DFFNS;
any.MinOccurs = 1; // when recognizing WSDL - MinOccurs="0" denotes DataSet, a MinOccurs="1" for DataTable
any.ProcessContents = XmlSchemaContentProcessing.Lax;
sequence.Items.Add(any);
type.Particle = sequence;
return type;
}
XmlSchema? IXmlSerializable.GetSchema()
{
if (!DataSet.XmlSerializationIsSupported)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}
return GetXmlSchema();
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2046:UnrecognizedReflectionPattern",
Justification = "https://github.com/mono/linker/issues/1187 Trimmer thinks this implements IXmlSerializable.GetSchema() and warns about not matching attributes.")]
protected virtual XmlSchema? GetSchema()
{
if (GetType() == typeof(DataTable))
{
return null;
}
MemoryStream stream = new MemoryStream();
XmlWriter writer = new XmlTextWriter(stream, null);
new XmlTreeGen(SchemaFormat.WebService).Save(this, writer);
stream.Position = 0;
return XmlSchema.Read(new XmlTextReader(stream), null);
}
[RequiresUnreferencedCode("DataTable.GetSchema uses TypeDescriptor and XmlSerialization underneath which are not trimming safe. Members from serialized types may be trimmed if not referenced directly.")]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
private XmlSchema? GetXmlSchema()
{
return GetSchema();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
if (!DataSet.XmlSerializationIsSupported)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}
IXmlTextParser? textReader = reader as IXmlTextParser;
bool fNormalization = true;
if (textReader != null)
{
fNormalization = textReader.Normalized;
textReader.Normalized = false;
}
ReadXmlSerializableInternal(reader);
if (textReader != null)
{
textReader.Normalized = fNormalization;
}
}
[RequiresUnreferencedCode("DataTable.ReadXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
private void ReadXmlSerializableInternal(XmlReader reader)
{
ReadXmlSerializable(reader);
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
if (!DataSet.XmlSerializationIsSupported)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}
WriteXmlInternal(writer);
}
[RequiresUnreferencedCode("DataTable.WriteXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
private void WriteXmlInternal(XmlWriter writer)
{
WriteXmlSchema(writer, false);
WriteXml(writer, XmlWriteMode.DiffGram, false);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(DataSet.RequiresDynamicCodeMessage)]
protected virtual void ReadXmlSerializable(XmlReader? reader) => ReadXml(reader, XmlReadMode.DiffGram, true);
// RowDiffIdUsageSection & DSRowDiffIdUsageSection Usage:
//
// DataTable.[DS]RowDiffIdUsageSection rowDiffIdUsage = new DataTable.[DS]RowDiffIdUsageSection();
// try {
// rowDiffIdUsage.Prepare(DataTable or DataSet, depending on type);
//
// // code that requires RowDiffId usage
//
// }
// finally {
// rowDiffIdUsage.Cleanup();
// }
//
// Nested calls are allowed on different tables. For example, user can register to row change events and trigger
// ReadXml on different table/ds). But, if user code will try to call ReadXml on table that is already in the scope,
// this code will assert since nested calls on same table are unsupported.
internal struct RowDiffIdUsageSection
{
#if DEBUG
// This list contains tables currently used in diffgram processing, not including new tables that might be added later during.
// if diffgram processing is not started, this value must be null. when it starts, relevant method should call Prepare.
// Notes:
// * in case of ReadXml on empty DataSet, this list can be initialized as empty (so empty list != null).
// * only one scope is allowed on single thread, either for datatable or dataset
// * assert is triggered if same table is added to this list twice
//
// do not allocate TLS data in RETAIL bits!
[ThreadStatic]
internal static List<DataTable>? t_usedTables;
#endif //DEBUG
private DataTable _targetTable;
internal void Prepare(DataTable table)
{
Debug.Assert(_targetTable == null, "do not reuse this section");
Debug.Assert(table != null);
Debug.Assert(table._rowDiffId == null, "rowDiffId wasn't previously cleared");
#if DEBUG
Debug.Assert(t_usedTables == null || !t_usedTables.Contains(table),
"Nested call with same table can cause data corruption!");
#endif
#if DEBUG
(t_usedTables ??= new List<DataTable>()).Add(table);
#endif
_targetTable = table;
table._rowDiffId = null;
}
[Conditional("DEBUG")]
internal void Cleanup()
{
// cannot assume target table was set
if (_targetTable != null)
{
#if DEBUG
Debug.Assert(t_usedTables != null && t_usedTables.Contains(_targetTable), "missing Prepare before Cleanup");
if (t_usedTables != null)
{
t_usedTables.Remove(_targetTable);
if (t_usedTables.Count == 0)
t_usedTables = null;
}
#endif
_targetTable._rowDiffId = null;
}
}
[Conditional("DEBUG")]
internal static void Assert(string message)
{
#if DEBUG
// this code asserts scope was created, but it does not assert that the table was included in it
// note that in case of DataSet, new tables might be added to the list in which case they won't appear in t_usedTables.
Debug.Assert(t_usedTables != null, message);
#endif
}
}
internal struct DSRowDiffIdUsageSection
{
private DataSet _targetDS;
internal void Prepare(DataSet ds)
{
Debug.Assert(_targetDS == null, "do not reuse this section");
Debug.Assert(ds != null);
_targetDS = ds;
#if DEBUG
// initialize list of tables out of current tables
// note: it might remain empty (still initialization is needed for assert to operate)
RowDiffIdUsageSection.t_usedTables ??= new List<DataTable>();
#endif
for (int tableIndex = 0; tableIndex < ds.Tables.Count; ++tableIndex)
{
DataTable table = ds.Tables[tableIndex];
#if DEBUG
Debug.Assert(!RowDiffIdUsageSection.t_usedTables.Contains(table), "Nested call with same table can cause data corruption!");
RowDiffIdUsageSection.t_usedTables.Add(table);
#endif
Debug.Assert(table._rowDiffId == null, "rowDiffId wasn't previously cleared");
table._rowDiffId = null;
}
}
[Conditional("DEBUG")]
internal void Cleanup()
{
// cannot assume target was set
if (_targetDS != null)
{
#if DEBUG
Debug.Assert(RowDiffIdUsageSection.t_usedTables != null, "missing Prepare before Cleanup");
#endif
for (int tableIndex = 0; tableIndex < _targetDS.Tables.Count; ++tableIndex)
{
DataTable table = _targetDS.Tables[tableIndex];
#if DEBUG
// cannot assert that table exists in the usedTables - new tables might be
// created during diffgram processing in DataSet.ReadXml.
RowDiffIdUsageSection.t_usedTables?.Remove(table);
#endif
table._rowDiffId = null;
}
#if DEBUG
if (RowDiffIdUsageSection.t_usedTables != null && RowDiffIdUsageSection.t_usedTables.Count == 0)
RowDiffIdUsageSection.t_usedTables = null; // out-of-scope
#endif
}
}
}
internal Hashtable RowDiffId
{
get
{
// assert scope has been created either with RowDiffIdUsageSection.Prepare or DSRowDiffIdUsageSection.Prepare
RowDiffIdUsageSection.Assert("missing call to RowDiffIdUsageSection.Prepare or DSRowDiffIdUsageSection.Prepare");
return _rowDiffId ??= new Hashtable();
}
}
internal int ObjectID => _objectID;
internal void AddDependentColumn(DataColumn expressionColumn)
{
_dependentColumns ??= new List<DataColumn>();
if (!_dependentColumns.Contains(expressionColumn))
{
// only remember unique columns but expect non-unique columns to be added
_dependentColumns.Add(expressionColumn);
}
}
internal void RemoveDependentColumn(DataColumn expressionColumn)
{
if (_dependentColumns != null && _dependentColumns.Contains(expressionColumn))
{
_dependentColumns.Remove(expressionColumn);
}
}
internal void EvaluateExpressions()
{
// evaluates all expressions for all rows in table
// this improves performance by only computing expressions when they are present
// and iterating over the rows instead of computing their position multiple times
if ((null != _dependentColumns) && (0 < _dependentColumns.Count))
{
foreach (DataRow row in Rows)
{
// only evaluate original values if different from current.
if (row._oldRecord != -1 && row._oldRecord != row._newRecord)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Original, null);
}
if (row._newRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Current, null);
}
if (row._tempRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Proposed, null);
}
}
}
}
internal void EvaluateExpressions(DataRow row, DataRowAction action, List<DataRow>? cachedRows)
{
// evaluate all expressions for specified row
if (action == DataRowAction.Add ||
action == DataRowAction.Change ||
(action == DataRowAction.Rollback && (row._oldRecord != -1 || row._newRecord != -1)))
{
// only evaluate original values if different from current.
if (row._oldRecord != -1 && row._oldRecord != row._newRecord)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Original, cachedRows);
}
if (row._newRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Current, cachedRows);
}
if (row._tempRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, row, DataRowVersion.Proposed, cachedRows);
}
return;
}
else if ((action == DataRowAction.Delete || (action == DataRowAction.Rollback && row._oldRecord == -1 && row._newRecord == -1)) && _dependentColumns != null)
{
foreach (DataColumn col in _dependentColumns)
{
if (col.DataExpression != null && col.DataExpression.HasLocalAggregate() && col.Table == this)
{
for (int j = 0; j < Rows.Count; j++)
{
DataRow tableRow = Rows[j];
if (tableRow._oldRecord != -1 && tableRow._oldRecord != tableRow._newRecord)
{
EvaluateDependentExpressions(_dependentColumns, tableRow, DataRowVersion.Original, null);
}
}
for (int j = 0; j < Rows.Count; j++)
{
DataRow tableRow = Rows[j];
if (tableRow._tempRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, tableRow, DataRowVersion.Proposed, null);
}
}
// Order is important here - we need to update proposed before current
// Oherwise rows that are in edit state will get ListChanged/PropertyChanged event before default value is changed
// It is also the reason why we are not doping it in the single loop: EvaluateDependentExpression can update the
// whole table, if it happens, current for all but first row is updated before proposed value
for (int j = 0; j < Rows.Count; j++)
{
DataRow tableRow = Rows[j];
if (tableRow._newRecord != -1)
{
EvaluateDependentExpressions(_dependentColumns, tableRow, DataRowVersion.Current, null);
}
}
break;
}
}
if (cachedRows != null)
{
foreach (DataRow relatedRow in cachedRows)
{
if (relatedRow._oldRecord != -1 && relatedRow._oldRecord != relatedRow._newRecord)
{
relatedRow.Table.EvaluateDependentExpressions(relatedRow.Table._dependentColumns, relatedRow, DataRowVersion.Original, null);
}
if (relatedRow._newRecord != -1)
{
relatedRow.Table.EvaluateDependentExpressions(relatedRow.Table._dependentColumns, relatedRow, DataRowVersion.Current, null);
}
if (relatedRow._tempRecord != -1)
{
relatedRow.Table.EvaluateDependentExpressions(relatedRow.Table._dependentColumns, relatedRow, DataRowVersion.Proposed, null);
}
}
}
}
}
internal void EvaluateExpressions(DataColumn column)
{
Debug.Assert(column._table != null);
Debug.Assert(column.Computed, "Only computed columns should be re-evaluated.");
// evaluates all rows for expression from specified column
int count = column._table.Rows.Count;
if (column.DataExpression!.IsTableAggregate() && count > 0)
{
// this value is a constant across the table.
object aggCurrent = column.DataExpression!.Evaluate();
for (int j = 0; j < count; j++)
{
DataRow row = column._table.Rows[j];
// only evaluate original values if different from current.
if (row._oldRecord != -1 && row._oldRecord != row._newRecord)
{
column[row._oldRecord] = aggCurrent;
}
if (row._newRecord != -1)
{
column[row._newRecord] = aggCurrent;
}
if (row._tempRecord != -1)
{
column[row._tempRecord] = aggCurrent;
}
}
}
else
{
for (int j = 0; j < count; j++)
{
DataRow row = column._table.Rows[j];
if (row._oldRecord != -1 && row._oldRecord != row._newRecord)
{
column[row._oldRecord] = column.DataExpression.Evaluate(row, DataRowVersion.Original);
}
if (row._newRecord != -1)
{
column[row._newRecord] = column.DataExpression.Evaluate(row, DataRowVersion.Current);
}
if (row._tempRecord != -1)
{
column[row._tempRecord] = column.DataExpression.Evaluate(row, DataRowVersion.Proposed);
}
}
}
column._table.ResetInternalIndexes(column);
EvaluateDependentExpressions(column);
}
internal void EvaluateDependentExpressions(DataColumn column)
{
// DataTable.Clear(), DataRowCollection.Clear() & DataColumn.set_Expression
if (column._dependentColumns != null)
{
foreach (DataColumn dc in column._dependentColumns)
{
if ((dc._table != null) && !ReferenceEquals(column, dc))
{
EvaluateExpressions(dc);
}
}
}
}
internal void EvaluateDependentExpressions(List<DataColumn>? columns, DataRow row, DataRowVersion version, List<DataRow>? cachedRows)
{
if (columns == null)
{
return;
}
//Expression evaluation is done first over same table expressions.
int count = columns.Count;
for (int i = 0; i < count; i++)
{
if (columns[i].Table == this)
{
// if this column is in my table
DataColumn dc = columns[i];
if (dc.DataExpression != null && dc.DataExpression.HasLocalAggregate())
{
// if column expression references a local Table aggregate we need to recalc it for the each row in the local table
DataRowVersion expressionVersion = (version == DataRowVersion.Proposed) ? DataRowVersion.Default : version;
bool isConst = dc.DataExpression.IsTableAggregate(); //is expression constant for entire table?
object? newValue = null;
if (isConst)
{
//if new value, just compute once
newValue = dc.DataExpression.Evaluate(row, expressionVersion);
}
for (int j = 0; j < Rows.Count; j++)
{
//evaluate for all rows in the table
DataRow dr = Rows[j];
if (dr.RowState == DataRowState.Deleted)
{
continue;
}
else if (expressionVersion == DataRowVersion.Original && (dr._oldRecord == -1 || dr._oldRecord == dr._newRecord))
{
continue;
}
if (!isConst)
{
newValue = dc.DataExpression.Evaluate(dr, expressionVersion);
}
SilentlySetValue(dr, dc, expressionVersion, newValue!);
}
}
else
{
if (row.RowState == DataRowState.Deleted)
{
continue;
}
else if (version == DataRowVersion.Original && (row._oldRecord == -1 || row._oldRecord == row._newRecord))
{
continue;
}
SilentlySetValue(row, dc, version, dc.DataExpression == null ? dc.DefaultValue : dc.DataExpression.Evaluate(row, version));
}
}
}
// now do expression evaluation for expression columns other tables.
count = columns.Count;
for (int i = 0; i < count; i++)
{
DataColumn dc = columns[i];
// if this column is NOT in my table or it is in the table and is not a local aggregate (self refs)
if (dc.Table != this || (dc.DataExpression != null && !dc.DataExpression.HasLocalAggregate()))
{
DataRowVersion foreignVer = (version == DataRowVersion.Proposed) ? DataRowVersion.Default : version;
// first - evaluate expressions for cachedRows (deletes & updates)
if (cachedRows != null)
{
foreach (DataRow cachedRow in cachedRows)
{
if (cachedRow.Table != dc.Table)
{
continue;
}
// don't update original version if child row doesn't have an oldRecord.
if (foreignVer == DataRowVersion.Original && cachedRow._newRecord == cachedRow._oldRecord)
{
continue;
}
if (cachedRow != null && ((cachedRow.RowState != DataRowState.Deleted) && (version != DataRowVersion.Original || cachedRow._oldRecord != -1)))
{
// if deleted GetRecordFromVersion will throw
// TODO: Possible bug, dc.DataExpression may be null
object newValue = dc.DataExpression!.Evaluate(cachedRow, foreignVer);
SilentlySetValue(cachedRow, dc, foreignVer, newValue);
}
}
}
// next check parent relations
for (int j = 0; j < ParentRelations.Count; j++)
{
DataRelation relation = ParentRelations[j];
if (relation.ParentTable != dc.Table)
{
continue;
}
foreach (DataRow parentRow in row.GetParentRows(relation, version))
{
if (cachedRows != null && cachedRows.Contains(parentRow))
{
continue;
}
// don't update original version if child row doesn't have an oldRecord.
if (foreignVer == DataRowVersion.Original && parentRow._newRecord == parentRow._oldRecord)
{
continue;
}
if (parentRow != null && ((parentRow.RowState != DataRowState.Deleted) && (version != DataRowVersion.Original || parentRow._oldRecord != -1)))
{
// if deleted GetRecordFromVersion will throw
// TODO: Possible bug, dc.DataExpression may be null
object newValue = dc.DataExpression!.Evaluate(parentRow, foreignVer);
SilentlySetValue(parentRow, dc, foreignVer, newValue);
}
}
}
// next check child relations
for (int j = 0; j < ChildRelations.Count; j++)
{
DataRelation relation = ChildRelations[j];
if (relation.ChildTable != dc.Table)
{
continue;
}
foreach (DataRow childRow in row.GetChildRows(relation, version))
{
// don't update original version if child row doesn't have an oldRecord.
if (cachedRows != null && cachedRows.Contains(childRow))
{
continue;
}
if (foreignVer == DataRowVersion.Original && childRow._newRecord == childRow._oldRecord)
{
continue;
}
if (childRow != null && ((childRow.RowState != DataRowState.Deleted) && (version != DataRowVersion.Original || childRow._oldRecord != -1)))
{
// if deleted GetRecordFromVersion will throw
// TODO: Possible bug, dc.DataExpression may be null
object newValue = dc.DataExpression!.Evaluate(childRow, foreignVer);
SilentlySetValue(childRow, dc, foreignVer, newValue);
}
}
}
}
}
}
}
}
|