File: System\Data\DataRelation.cs
Web Access
Project: src\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
/*****************************************************************************************************
Rules for Multiple Nested Parent, enforce following constraints
 
1) At all times, only 1(ONE) FK can be NON-Null in a row.
2) NULL FK values are not associated with PARENT(x), even if PK is NULL in Parent
3) Enforce <rule 1> when
        a) Any FK value is changed
        b) A relation created that result in Multiple Nested Child
 
WriteXml
 
1) WriteXml will throw if <rule 1> is violated
2) if NON-Null FK has parentRow (boolean check) print as Nested, else it will get written as normal row
 
additional notes:
We decided to enforce the rule 1 just if Xml being persisted
******************************************************************************************************/
 
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading;
 
namespace System.Data
{
    [DefaultProperty(nameof(RelationName))]
    [Editor("Microsoft.VSDesigner.Data.Design.DataRelationEditor, 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(RelationshipConverter))]
    public class DataRelation
    {
        // properties
        private DataSet? _dataSet;
        internal PropertyCollection? _extendedProperties;
        internal string _relationName = string.Empty;
 
        // state
        private DataKey _childKey;
        private DataKey _parentKey;
        private UniqueConstraint? _parentKeyConstraint;
        private ForeignKeyConstraint? _childKeyConstraint;
 
        // Design time serialization
        internal string[]? _parentColumnNames;
        internal string[]? _childColumnNames;
        internal string? _parentTableName;
        internal string? _childTableName;
        internal string? _parentTableNamespace;
        internal string? _childTableNamespace;
 
        /// <summary>
        /// This stores whether the  child element appears beneath the parent in the XML persisted files.
        /// </summary>
        internal bool _nested;
 
        /// <summary>
        /// This stores whether the relationship should make sure that KeyConstraints and ForeignKeyConstraints
        /// exist when added to the ConstraintsCollections of the table.
        /// </summary>
        internal bool _createConstraints;
 
        private bool _checkMultipleNested = true;
 
        private static int s_objectTypeCount; // Bid counter
        private readonly int _objectID = Interlocked.Increment(ref s_objectTypeCount);
 
        /// <summary>
        /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name,
        /// parent, and child columns.
        /// </summary>
        public DataRelation(string? relationName, DataColumn parentColumn, DataColumn childColumn) :
            this(relationName, parentColumn, childColumn, true)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name, parent, and child columns, and
        /// value to create constraints.
        /// </summary>
        public DataRelation(string? relationName, DataColumn parentColumn, DataColumn childColumn, bool createConstraints)
        {
            DataCommonEventSource.Log.Trace("<ds.DataRelation.DataRelation|API> {0}, relationName='{1}', parentColumn={2}, childColumn={3}, createConstraints={4}",
                            ObjectID, relationName, (parentColumn != null) ? parentColumn.ObjectID : 0, (childColumn != null) ? childColumn.ObjectID : 0,
                            createConstraints);
 
            DataColumn[] parentColumns = new DataColumn[1];
            parentColumns[0] = parentColumn!;
            DataColumn[] childColumns = new DataColumn[1];
            childColumns[0] = childColumn!;
            Create(relationName, parentColumns, childColumns, createConstraints);
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name
        /// and matched arrays of parent and child columns.
        /// </summary>
        public DataRelation(string? relationName, DataColumn[] parentColumns, DataColumn[] childColumns) :
            this(relationName, parentColumns, childColumns, true)
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name, matched arrays of parent
        /// and child columns, and value to create constraints.
        /// </summary>
        public DataRelation(string? relationName, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints)
        {
            Create(relationName, parentColumns, childColumns, createConstraints);
        }
 
        [Browsable(false)] // design-time ctor
        public DataRelation(string relationName, string? parentTableName, string? childTableName, string[]? parentColumnNames, string[]? childColumnNames, bool nested)
        {
            _relationName = relationName;
            _parentColumnNames = parentColumnNames;
            _childColumnNames = childColumnNames;
            _parentTableName = parentTableName;
            _childTableName = childTableName;
            _nested = nested;
        }
 
        [Browsable(false)] // design-time ctor
        public DataRelation(string relationName, string? parentTableName, string? parentTableNamespace, string? childTableName, string? childTableNamespace, string[]? parentColumnNames, string[]? childColumnNames, bool nested)
        {
            _relationName = relationName;
            _parentColumnNames = parentColumnNames;
            _childColumnNames = childColumnNames;
            _parentTableName = parentTableName;
            _childTableName = childTableName;
            _parentTableNamespace = parentTableNamespace;
            _childTableNamespace = childTableNamespace;
            _nested = nested;
        }
 
        /// <summary>
        /// Gets the child columns of this relation.
        /// </summary>
        public virtual DataColumn[] ChildColumns
        {
            get
            {
                CheckStateForProperty();
                return _childKey.ToArray();
            }
        }
 
        internal DataColumn[] ChildColumnsReference
        {
            get
            {
                CheckStateForProperty();
                return _childKey.ColumnsReference;
            }
        }
 
        /// <summary>
        /// The internal Key object for the child table.
        /// </summary>
        internal DataKey ChildKey
        {
            get
            {
                CheckStateForProperty();
                return _childKey;
            }
        }
 
        /// <summary>
        /// Gets the child table of this relation.
        /// </summary>
        public virtual DataTable ChildTable
        {
            get
            {
                CheckStateForProperty();
                return _childKey.Table;
            }
        }
 
        /// <summary>
        /// Gets the <see cref='System.Data.DataSet'/> to which the relations' collection belongs to.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
        public virtual DataSet? DataSet
        {
            get
            {
                CheckStateForProperty();
                return _dataSet;
            }
        }
 
        internal string[] ParentColumnNames => _parentKey.GetColumnNames();
 
        internal string[] ChildColumnNames => _childKey.GetColumnNames();
 
        private static bool IsKeyNull(object[] values)
        {
            for (int i = 0; i < values.Length; i++)
            {
                if (!DataStorage.IsObjectNull(values[i]))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        /// <summary>
        /// Gets the child rows for the parent row across the relation using the version given
        /// </summary>
        internal static DataRow[] GetChildRows(DataKey parentKey, DataKey childKey, DataRow parentRow, DataRowVersion version)
        {
            object[] values = parentRow.GetKeyValues(parentKey, version);
            if (IsKeyNull(values))
            {
                return childKey.Table.NewRowArray(0);
            }
 
            Index index = childKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
            return index.GetRows(values);
        }
 
        /// <summary>
        /// Gets the parent rows for the given child row across the relation using the version given
        /// </summary>
        internal static DataRow[] GetParentRows(DataKey parentKey, DataKey childKey, DataRow childRow, DataRowVersion version)
        {
            object[] values = childRow.GetKeyValues(childKey, version);
            if (IsKeyNull(values))
            {
                return parentKey.Table.NewRowArray(0);
            }
 
            Index index = parentKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
            return index.GetRows(values);
        }
 
        internal static DataRow? GetParentRow(DataKey parentKey, DataKey childKey, DataRow childRow, DataRowVersion version)
        {
            if (!childRow.HasVersion((version == DataRowVersion.Original) ? DataRowVersion.Original : DataRowVersion.Current))
            {
                if (childRow._tempRecord == -1)
                {
                    return null;
                }
            }
 
            object[] values = childRow.GetKeyValues(childKey, version);
            if (IsKeyNull(values))
            {
                return null;
            }
 
            Index index = parentKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
            Range range = index.FindRecords(values);
            if (range.IsNull)
            {
                return null;
            }
 
            if (range.Count > 1)
            {
                throw ExceptionBuilder.MultipleParents();
            }
 
            return parentKey.Table._recordManager[index.GetRecord(range.Min)];
        }
 
 
        /// <summary>
        /// Internally sets the DataSet pointer.
        /// </summary>
        internal void SetDataSet(DataSet? dataSet)
        {
            if (_dataSet != dataSet)
            {
                _dataSet = dataSet;
            }
        }
 
        internal void SetParentRowRecords(DataRow childRow, DataRow parentRow)
        {
            object[] parentKeyValues = parentRow.GetKeyValues(ParentKey);
            if (childRow._tempRecord != -1)
            {
                RecordManager.SetKeyValues(childRow._tempRecord, ChildKey, parentKeyValues);
            }
            if (childRow._newRecord != -1)
            {
                RecordManager.SetKeyValues(childRow._newRecord, ChildKey, parentKeyValues);
            }
            if (childRow._oldRecord != -1)
            {
                RecordManager.SetKeyValues(childRow._oldRecord, ChildKey, parentKeyValues);
            }
        }
 
        /// <summary>
        /// Gets the parent columns of this relation.
        /// </summary>
        public virtual DataColumn[] ParentColumns
        {
            get
            {
                CheckStateForProperty();
                return _parentKey.ToArray();
            }
        }
 
        internal DataColumn[] ParentColumnsReference => _parentKey.ColumnsReference;
 
        /// <summary>
        /// The internal constraint object for the parent table.
        /// </summary>
        internal DataKey ParentKey
        {
            get
            {
                CheckStateForProperty();
                return _parentKey;
            }
        }
 
        /// <summary>
        /// Gets the parent table of this relation.
        /// </summary>
        public virtual DataTable ParentTable
        {
            get
            {
                CheckStateForProperty();
                return _parentKey.Table;
            }
        }
 
        /// <summary>
        /// Gets or sets the name used to look up this relation in the parent
        /// data set's <see cref='System.Data.DataRelationCollection'/>.
        /// </summary>
        [DefaultValue("")]
        [AllowNull]
        public virtual string RelationName
        {
            get
            {
                CheckStateForProperty();
                return _relationName;
            }
            set
            {
                long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataRelation.set_RelationName|API> {0}, '{1}'", ObjectID, value);
                try
                {
                    value ??= string.Empty;
 
                    CultureInfo locale = (_dataSet != null ? _dataSet.Locale : CultureInfo.CurrentCulture);
                    if (string.Compare(_relationName, value, true, locale) != 0)
                    {
                        if (_dataSet != null)
                        {
                            if (value.Length == 0)
                            {
                                throw ExceptionBuilder.NoRelationName();
                            }
 
                            _dataSet.Relations.RegisterName(value);
                            if (_relationName.Length != 0)
                            {
                                _dataSet.Relations.UnregisterName(_relationName);
                            }
                        }
                        _relationName = value;
                        ((DataRelationCollection.DataTableRelationCollection)(ParentTable.ChildRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
                        ((DataRelationCollection.DataTableRelationCollection)(ChildTable.ParentRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
                    }
                    else if (string.Compare(_relationName, value, false, locale) != 0)
                    {
                        _relationName = value;
                        ((DataRelationCollection.DataTableRelationCollection)(ParentTable.ChildRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
                        ((DataRelationCollection.DataTableRelationCollection)(ChildTable.ParentRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
                    }
                }
                finally
                {
                    DataCommonEventSource.Log.ExitScope(logScopeId);
                }
            }
        }
        internal void CheckNamespaceValidityForNestedRelations(string ns)
        {
            foreach (DataRelation rel in ChildTable.ParentRelations)
            {
                if (rel == this || rel.Nested)
                {
                    if (rel.ParentTable.Namespace != ns)
                    {
                        throw ExceptionBuilder.InValidNestedRelation(ChildTable.TableName);
                    }
                }
            }
        }
 
        internal void CheckNestedRelations()
        {
            DataCommonEventSource.Log.Trace("<ds.DataRelation.CheckNestedRelations|INFO> {0}", ObjectID);
 
            Debug.Assert(DataSet == null || !_nested, "this relation supposed to be not in dataset or not nested");
            // 1. There is no other relation (R) that has this.ChildTable as R.ChildTable
            //  This is not valid for Whidbey anymore so the code has been removed
 
            // 2. There is no loop in nested relations
 
            if (ChildTable == ParentTable)
            {
                if (string.Compare(ChildTable.TableName, ChildTable.DataSet!.DataSetName, true, ChildTable.DataSet.Locale) == 0)
                    throw ExceptionBuilder.SelfnestedDatasetConflictingName(ChildTable.TableName);
                return; //allow self join tables.
            }
 
            List<DataTable> list = new List<DataTable>();
            list.Add(ChildTable);
 
            // We have already checked for nested relaion UP
            for (int i = 0; i < list.Count; ++i)
            {
                DataRelation[] relations = list[i].NestedParentRelations;
                foreach (DataRelation rel in relations)
                {
                    if (rel.ParentTable == ChildTable && rel.ChildTable != ChildTable)
                    {
                        throw ExceptionBuilder.LoopInNestedRelations(ChildTable.TableName);
                    }
                    if (!list.Contains(rel.ParentTable))
                    {
                        // check for self nested
                        list.Add(rel.ParentTable);
                    }
                }
            }
        }
 
        /********************
          The Namespace of a table nested inside multiple parents can be
          1. Explicitly specified
          2. Inherited from Parent Table
          3. Empty (Form = unqualified case)
          However, Schema does not allow (3) to be a global element and multiple nested child has to be a global element.
          Therefore we'll reduce case (3) to (2) if all parents have same namespace else throw.
         ********************/
 
        /// <summary>
        /// Gets or sets a value indicating whether relations are nested.
        /// </summary>
        [DefaultValue(false)]
        public virtual bool Nested
        {
            get
            {
                CheckStateForProperty();
                return _nested;
            }
            set
            {
                long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataRelation.set_Nested|API> {0}, {1}", ObjectID, value);
                try
                {
                    if (_nested != value)
                    {
                        if (_dataSet != null)
                        {
                            if (value)
                            {
                                if (ChildTable.IsNamespaceInherited())
                                { // if not added to collection, don't do this check
                                    CheckNamespaceValidityForNestedRelations(ParentTable.Namespace);
                                }
                                Debug.Assert(ChildTable != null, "On a DataSet, but not on Table. Bad state");
                                ForeignKeyConstraint? constraint = ChildTable.Constraints.FindForeignKeyConstraint(ChildKey.ColumnsReference, ParentKey.ColumnsReference);
                                constraint?.CheckConstraint();
                                ValidateMultipleNestedRelations();
                            }
                        }
                        if (!value && (_parentKey.ColumnsReference[0].ColumnMapping == MappingType.Hidden))
                        {
                            throw ExceptionBuilder.RelationNestedReadOnly();
                        }
 
                        if (value)
                        {
                            ParentTable.Columns.RegisterColumnName(ChildTable.TableName, null);
                        }
                        else
                        {
                            ParentTable.Columns.UnregisterName(ChildTable.TableName);
                        }
                        RaisePropertyChanging(nameof(Nested));
 
                        if (value)
                        {
                            CheckNestedRelations();
                            if (DataSet != null)
                                if (ParentTable == ChildTable)
                                {
                                    foreach (DataRow row in ChildTable.Rows)
                                    {
                                        row.CheckForLoops(this);
                                    }
 
                                    if (ChildTable.DataSet != null && (string.Compare(ChildTable.TableName, ChildTable.DataSet.DataSetName, true, ChildTable.DataSet.Locale) == 0))
                                    {
                                        throw ExceptionBuilder.DatasetConflictingName(DataSet.DataSetName);
                                    }
                                    ChildTable._fNestedInDataset = false;
                                }
                                else
                                {
                                    foreach (DataRow row in ChildTable.Rows)
                                    {
                                        row.GetParentRow(this);
                                    }
                                }
 
                            ParentTable.ElementColumnCount++;
                        }
                        else
                        {
                            ParentTable.ElementColumnCount--;
                        }
 
                        _nested = value;
                        ChildTable.CacheNestedParent();
                        if (value)
                        {
                            if (string.IsNullOrEmpty(ChildTable.Namespace) && ((ChildTable.NestedParentsCount > 1) ||
                                ((ChildTable.NestedParentsCount > 0) && !(ChildTable.DataSet!.Relations.Contains(RelationName)))))
                            {
                                string? parentNs = null;
                                foreach (DataRelation rel in ChildTable.ParentRelations)
                                {
                                    if (rel.Nested)
                                    {
                                        if (null == parentNs)
                                        {
                                            parentNs = rel.ParentTable.Namespace;
                                        }
                                        else
                                        {
                                            if (!string.Equals(parentNs, rel.ParentTable.Namespace, StringComparison.Ordinal))
                                            {
                                                _nested = false;
                                                throw ExceptionBuilder.InvalidParentNamespaceinNestedRelation(ChildTable.TableName);
                                            }
                                        }
                                    }
                                }
                                // if not already in memory , form == unqualified
                                if (CheckMultipleNested && ChildTable._tableNamespace != null && ChildTable._tableNamespace.Length == 0)
                                {
                                    throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
                                }
                                ChildTable._tableNamespace = null; // if we dont throw, then let it inherit the Namespace
                            }
                        }
                    }
                }
                finally
                {
                    DataCommonEventSource.Log.ExitScope(logScopeId);
                }
            }
        }
 
        /// <summary>
        /// Gets the constraint which ensures values in a column are unique.
        /// </summary>
        public virtual UniqueConstraint? ParentKeyConstraint
        {
            get
            {
                CheckStateForProperty();
                return _parentKeyConstraint;
            }
        }
 
        internal void SetParentKeyConstraint(UniqueConstraint? value)
        {
            Debug.Assert(_parentKeyConstraint == null || value == null, "ParentKeyConstraint should not have been set already.");
            _parentKeyConstraint = value;
        }
 
 
        /// <summary>
        /// Gets the <see cref='System.Data.ForeignKeyConstraint'/> for the relation.
        /// </summary>
        public virtual ForeignKeyConstraint? ChildKeyConstraint
        {
            get
            {
                CheckStateForProperty();
                return _childKeyConstraint;
            }
        }
 
        /// <summary>
        /// Gets the collection of custom user information.
        /// </summary>
        [Browsable(false)]
        public PropertyCollection ExtendedProperties => _extendedProperties ??= new PropertyCollection();
 
        internal bool CheckMultipleNested
        {
            get { return _checkMultipleNested; }
            set { _checkMultipleNested = value; }
        }
 
        internal void SetChildKeyConstraint(ForeignKeyConstraint? value)
        {
            Debug.Assert(_childKeyConstraint == null || value == null, "ChildKeyConstraint should not have been set already.");
            _childKeyConstraint = value;
        }
 
        internal event PropertyChangedEventHandler? PropertyChanging;
 
        // If we're not in a dataSet relations collection, we need to verify on every property get that we're
        // still a good relation object.
        internal void CheckState()
        {
            if (_dataSet == null)
            {
                _parentKey.CheckState();
                _childKey.CheckState();
 
                if (_parentKey.Table.DataSet != _childKey.Table.DataSet)
                {
                    throw ExceptionBuilder.RelationDataSetMismatch();
                }
 
                if (_childKey.ColumnsEqual(_parentKey))
                {
                    throw ExceptionBuilder.KeyColumnsIdentical();
                }
 
                for (int i = 0; i < _parentKey.ColumnsReference.Length; i++)
                {
                    if ((_parentKey.ColumnsReference[i].DataType != _childKey.ColumnsReference[i].DataType) ||
                        ((_parentKey.ColumnsReference[i].DataType == typeof(DateTime)) &&
                        (_parentKey.ColumnsReference[i].DateTimeMode != _childKey.ColumnsReference[i].DateTimeMode) &&
                        ((_parentKey.ColumnsReference[i].DateTimeMode & _childKey.ColumnsReference[i].DateTimeMode) != DataSetDateTime.Unspecified)))
                    {
                        // allow unspecified and unspecifiedlocal
                        throw ExceptionBuilder.ColumnsTypeMismatch();
                    }
                }
            }
        }
 
        /// <summary>
        /// Checks to ensure the DataRelation is a valid object, even if it doesn't
        /// belong to a <see cref='System.Data.DataSet'/>.
        /// </summary>
        protected void CheckStateForProperty()
        {
            try
            {
                CheckState();
            }
            catch (Exception e) when (ADP.IsCatchableExceptionType(e))
            {
                throw ExceptionBuilder.BadObjectPropertyAccess(e.Message);
            }
        }
 
        private void Create(string? relationName, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints)
        {
            long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataRelation.Create|INFO> {0}, relationName='{1}', createConstraints={2}", ObjectID, relationName, createConstraints);
            try
            {
                _parentKey = new DataKey(parentColumns, true);
                _childKey = new DataKey(childColumns, true);
 
                if (parentColumns.Length != childColumns.Length)
                {
                    throw ExceptionBuilder.KeyLengthMismatch();
                }
 
                for (int i = 0; i < parentColumns.Length; i++)
                {
                    if ((parentColumns[i].Table!.DataSet == null) || (childColumns[i].Table!.DataSet == null))
                    {
                        throw ExceptionBuilder.ParentOrChildColumnsDoNotHaveDataSet();
                    }
                }
 
                CheckState();
 
                _relationName = relationName ?? "";
                _createConstraints = createConstraints;
            }
            finally
            {
                DataCommonEventSource.Log.ExitScope(logScopeId);
            }
        }
 
        internal DataRelation Clone(DataSet destination)
        {
            DataCommonEventSource.Log.Trace("<ds.DataRelation.Clone|INFO> {0}, destination={1}", ObjectID, (destination != null) ? destination.ObjectID : 0);
            Debug.Assert(destination != null);
 
            DataTable parent = destination.Tables[ParentTable.TableName, ParentTable.Namespace]!;
            DataTable child = destination.Tables[ChildTable.TableName, ChildTable.Namespace]!;
            int keyLength = _parentKey.ColumnsReference.Length;
 
            DataColumn[] parentColumns = new DataColumn[keyLength];
            DataColumn[] childColumns = new DataColumn[keyLength];
 
            for (int i = 0; i < keyLength; i++)
            {
                parentColumns[i] = parent.Columns[ParentKey.ColumnsReference[i].ColumnName]!;
                childColumns[i] = child.Columns[ChildKey.ColumnsReference[i].ColumnName]!;
            }
 
            DataRelation clone = new DataRelation(_relationName, parentColumns, childColumns, false);
 
            clone.CheckMultipleNested = false; // disable the check  in clone as it is already created
            clone.Nested = Nested;
            clone.CheckMultipleNested = true; // enable the check
 
            // ...Extended Properties
            if (_extendedProperties != null)
            {
                foreach (object key in _extendedProperties.Keys)
                {
                    clone.ExtendedProperties[key] = _extendedProperties[key];
                }
            }
            return clone;
        }
 
        protected internal void OnPropertyChanging(PropertyChangedEventArgs pcevent)
        {
            if (PropertyChanging != null)
            {
                DataCommonEventSource.Log.Trace("<ds.DataRelation.OnPropertyChanging|INFO> {0}", ObjectID);
                PropertyChanging(this, pcevent);
            }
        }
 
        protected internal void RaisePropertyChanging(string name)
        {
            OnPropertyChanging(new PropertyChangedEventArgs(name));
        }
 
        /// <summary>
        /// </summary>
        public override string ToString() => RelationName;
 
        internal void ValidateMultipleNestedRelations()
        {
            // find all nested relations that this child table has
            // if this relation is the only relation it has, then fine,
            // otherwise check if all relations are created from XSD, without using Key/KeyRef
            // check all keys to see autogenerated
 
            if (!Nested || !CheckMultipleNested) // no need for this verification
            {
                return;
            }
 
            if (0 < ChildTable.NestedParentRelations.Length)
            {
                DataColumn[] childCols = ChildColumns;
                if (childCols.Length != 1 || !IsAutoGenerated(childCols[0]))
                {
                    throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
                }
 
                if (!XmlTreeGen.AutoGenerated(this))
                {
                    throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
                }
 
                foreach (Constraint cs in ChildTable.Constraints)
                {
                    if (cs is ForeignKeyConstraint fk)
                    {
                        if (!XmlTreeGen.AutoGenerated(fk, true))
                        {
                            throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
                        }
                    }
                    else
                    {
                        UniqueConstraint unique = (UniqueConstraint)cs;
                        if (!XmlTreeGen.AutoGenerated(unique))
                        {
                            throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
                        }
                    }
                }
            }
        }
 
        private bool IsAutoGenerated(DataColumn col)
        {
            if (col.ColumnMapping != MappingType.Hidden)
            {
                return false;
            }
 
            if (col.DataType != typeof(int))
            {
                return false;
            }
 
            string generatedname = col.Table!.TableName + "_Id";
 
            if ((col.ColumnName == generatedname) || (col.ColumnName == generatedname + "_0"))
            {
                return true;
            }
 
            generatedname = ParentColumnsReference[0].Table!.TableName + "_Id";
            if ((col.ColumnName == generatedname) || (col.ColumnName == generatedname + "_0"))
            {
                return true;
            }
 
            return false;
        }
 
        internal int ObjectID => _objectID;
    }
}