|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace System.Data
{
/// <summary>
/// Represents a restriction on a set of columns in which all values must be unique.
/// </summary>
[DefaultProperty("ConstraintName")]
[Editor("Microsoft.VSDesigner.Data.Design.UniqueConstraintEditor, 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")]
public class UniqueConstraint : Constraint
{
private DataKey _key;
private Index? _constraintIndex;
internal bool _bPrimaryKey;
// Design time serialization
internal string? _constraintName;
internal string[]? _columnNames;
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and
/// <see cref='System.Data.DataColumn'/>.
/// </summary>
public UniqueConstraint(string? name, DataColumn column)
{
DataColumn[] columns = new DataColumn[1];
columns[0] = column;
Create(name, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>.
/// </summary>
public UniqueConstraint(DataColumn column)
{
DataColumn[] columns = new DataColumn[1];
columns[0] = column;
Create(null, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array
/// of <see cref='System.Data.DataColumn'/> objects.
/// </summary>
public UniqueConstraint(string? name, DataColumn[] columns)
{
Create(name, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/>
/// objects.
/// </summary>
public UniqueConstraint(DataColumn[] columns)
{
Create(null, columns);
}
// Construct design time object
[Browsable(false)]
public UniqueConstraint(string? name, string[]? columnNames, bool isPrimaryKey)
{
_constraintName = name;
_columnNames = columnNames;
_bPrimaryKey = isPrimaryKey;
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and
/// <see cref='System.Data.DataColumn'/>.
/// </summary>
public UniqueConstraint(string? name, DataColumn column, bool isPrimaryKey)
{
DataColumn[] columns = new DataColumn[1];
columns[0] = column;
_bPrimaryKey = isPrimaryKey;
Create(name, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>.
/// </summary>
public UniqueConstraint(DataColumn column, bool isPrimaryKey)
{
DataColumn[] columns = new DataColumn[1];
columns[0] = column;
_bPrimaryKey = isPrimaryKey;
Create(null, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array
/// of <see cref='System.Data.DataColumn'/> objects.
/// </summary>
public UniqueConstraint(string? name, DataColumn[] columns, bool isPrimaryKey)
{
_bPrimaryKey = isPrimaryKey;
Create(name, columns);
}
/// <summary>
/// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/>
/// objects.
/// </summary>
public UniqueConstraint(DataColumn[] columns, bool isPrimaryKey)
{
_bPrimaryKey = isPrimaryKey;
Create(null, columns);
}
// design time serialization only
internal string[] ColumnNames
{
get
{
return _key.GetColumnNames();
}
}
// Use constraint index only for search operations (and use key.GetSortIndex() when enumeration is needed and/or order is important)
internal Index ConstraintIndex
{
get
{
AssertConstraintAndKeyIndexes();
return _constraintIndex!;
}
}
[Conditional("DEBUG")]
private void AssertConstraintAndKeyIndexes()
{
Debug.Assert(null != _constraintIndex, "null UniqueConstraint index");
// ideally, we would like constraintIndex and key.GetSortIndex to share the same index underneath: Debug.Assert(_constraintIndex == key.GetSortIndex)
// but, there is a scenario where constraint and key indexes are built from the same list of columns but in a different order
DataColumn[] sortIndexColumns = new DataColumn[_constraintIndex._indexFields.Length];
for (int i = 0; i < sortIndexColumns.Length; i++)
{
sortIndexColumns[i] = _constraintIndex._indexFields[i].Column;
}
Debug.Assert(DataKey.ColumnsEqual(_key.ColumnsReference, sortIndexColumns), "UniqueConstraint index columns do not match the key sort index");
}
internal void ConstraintIndexClear()
{
if (null != _constraintIndex)
{
_constraintIndex.RemoveRef();
_constraintIndex = null;
}
}
internal void ConstraintIndexInitialize()
{
if (null == _constraintIndex)
{
_constraintIndex = _key.GetSortIndex();
_constraintIndex.AddRef();
}
AssertConstraintAndKeyIndexes();
}
internal override void CheckState()
{
NonVirtualCheckState();
}
private void NonVirtualCheckState()
{
_key.CheckState();
}
internal override void CheckCanAddToCollection(ConstraintCollection constraints)
{
}
internal override bool CanBeRemovedFromCollection(ConstraintCollection constraints, bool fThrowException)
{
if (Equals(constraints.Table._primaryKey))
{
Debug.Assert(constraints.Table._primaryKey == this, "If the primary key and this are 'Equal', they should also be '=='");
if (!fThrowException)
return false;
else
throw ExceptionBuilder.RemovePrimaryKey(constraints.Table);
}
for (ParentForeignKeyConstraintEnumerator cs = new ParentForeignKeyConstraintEnumerator(Table!.DataSet, Table); cs.GetNext();)
{
ForeignKeyConstraint constraint = cs.GetForeignKeyConstraint();
if (!_key.ColumnsEqual(constraint.ParentKey))
continue;
if (!fThrowException)
return false;
else
throw ExceptionBuilder.NeededForForeignKeyConstraint(this, constraint);
}
return true;
}
internal override bool CanEnableConstraint()
{
if (Table!.EnforceConstraints)
return ConstraintIndex.CheckUnique();
return true;
}
internal override bool IsConstraintViolated()
{
bool result = false;
Index index = ConstraintIndex;
if (index.HasDuplicates)
{
object[] uniqueKeys = index.GetUniqueKeyValues();
for (int i = 0; i < uniqueKeys.Length; i++)
{
Range r = index.FindRecords((object[])uniqueKeys[i]);
if (1 < r.Count)
{
DataRow[] rows = index.GetRows(r);
string error = ExceptionBuilder.UniqueConstraintViolationText(_key.ColumnsReference, (object[])uniqueKeys[i]);
for (int j = 0; j < rows.Length; j++)
{
rows[j].RowError = error;
foreach (DataColumn dataColumn in _key.ColumnsReference)
{
rows[j].SetColumnError(dataColumn, error);
}
}
result = true;
}
}
}
return result;
}
internal override void CheckConstraint(DataRow row, DataRowAction action)
{
if (Table!.EnforceConstraints &&
(action == DataRowAction.Add ||
action == DataRowAction.Change ||
(action == DataRowAction.Rollback && row._tempRecord != -1)))
{
if (row.HaveValuesChanged(ColumnsReference))
{
if (ConstraintIndex.IsKeyRecordInIndex(row.GetDefaultRecord()))
{
object[] values = row.GetColumnValues(ColumnsReference);
throw ExceptionBuilder.ConstraintViolation(ColumnsReference, values);
}
}
}
}
internal override bool ContainsColumn(DataColumn column)
{
return _key.ContainsColumn(column);
}
internal override Constraint? Clone(DataSet destination)
{
return Clone(destination, false);
}
internal override Constraint? Clone(DataSet destination, bool ignorNSforTableLookup)
{
int iDest;
if (ignorNSforTableLookup)
{
iDest = destination.Tables.IndexOf(Table!.TableName);
}
else
{
iDest = destination.Tables.IndexOf(Table!.TableName, Table.Namespace, false); // pass false for last param to be backward compatible
}
if (iDest < 0)
return null;
DataTable table = destination.Tables[iDest];
int keys = ColumnsReference.Length;
DataColumn[] columns = new DataColumn[keys];
for (int i = 0; i < keys; i++)
{
DataColumn src = ColumnsReference[i];
iDest = table.Columns.IndexOf(src.ColumnName);
if (iDest < 0)
return null;
columns[i] = table.Columns[iDest];
}
UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns);
// ...Extended Properties
foreach (object key in ExtendedProperties.Keys)
{
clone.ExtendedProperties[key] = ExtendedProperties[key];
}
return clone;
}
internal UniqueConstraint? Clone(DataTable table)
{
int keys = ColumnsReference.Length;
DataColumn[] columns = new DataColumn[keys];
for (int i = 0; i < keys; i++)
{
DataColumn src = ColumnsReference[i];
int iDest = table.Columns.IndexOf(src.ColumnName);
if (iDest < 0)
return null;
columns[i] = table.Columns[iDest];
}
UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns);
// ...Extended Properties
foreach (object key in ExtendedProperties.Keys)
{
clone.ExtendedProperties[key] = ExtendedProperties[key];
}
return clone;
}
/// <summary>
/// Gets the array of columns that this constraint affects.
/// </summary>
[ReadOnly(true)]
public virtual DataColumn[] Columns
{
get
{
return _key.ToArray();
}
}
internal DataColumn[] ColumnsReference
{
get
{
return _key.ColumnsReference;
}
}
/// <summary>
/// Gets a value indicating whether or not the constraint is on a primary key.
/// </summary>
public bool IsPrimaryKey
{
get
{
if (Table == null)
{
return false;
}
return (this == Table._primaryKey);
}
}
private void Create(string? constraintName, DataColumn[] columns)
{
for (int i = 0; i < columns.Length; i++)
{
if (columns[i].Computed)
{
throw ExceptionBuilder.ExpressionInConstraint(columns[i]);
}
}
_key = new DataKey(columns, true);
ConstraintName = constraintName;
NonVirtualCheckState();
}
/// <summary>
/// Compares this constraint to a second to determine if both are identical.
/// </summary>
public override bool Equals([NotNullWhen(true)] object? key2)
{
if (!(key2 is UniqueConstraint))
return false;
return Key.ColumnsEqual(((UniqueConstraint)key2).Key);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
internal override bool InCollection
{
set
{
base.InCollection = value;
if (_key.ColumnsReference.Length == 1)
{
_key.ColumnsReference[0].InternalUnique(value);
}
}
}
internal DataKey Key
{
get
{
return _key;
}
}
/// <summary>
/// Gets the table to which this constraint belongs.
/// </summary>
[ReadOnly(true)]
public override DataTable? Table
{
get
{
if (_key.HasValue)
{
return _key.Table;
}
return null;
}
}
// misc
}
}
|