1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.ComponentModel;
7 
8 namespace System.Data
9 {
10     /// <summary>
11     /// Represents a restriction on a set of columns in which all values must be unique.
12     /// </summary>
13     [DefaultProperty("ConstraintName")]
14     public class UniqueConstraint : Constraint
15     {
16         private DataKey _key;
17         private Index _constraintIndex;
18         internal bool _bPrimaryKey = false;
19 
20         // Design time serialization
21         internal string _constraintName = null;
22         internal string[] _columnNames = null;
23 
24         /// <summary>
25         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and
26         /// <see cref='System.Data.DataColumn'/>.
27         /// </summary>
UniqueConstraint(string name, DataColumn column)28         public UniqueConstraint(string name, DataColumn column)
29         {
30             DataColumn[] columns = new DataColumn[1];
31             columns[0] = column;
32             Create(name, columns);
33         }
34 
35         /// <summary>
36         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>.
37         /// </summary>
UniqueConstraint(DataColumn column)38         public UniqueConstraint(DataColumn column)
39         {
40             DataColumn[] columns = new DataColumn[1];
41             columns[0] = column;
42             Create(null, columns);
43         }
44 
45         /// <summary>
46         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array
47         ///    of <see cref='System.Data.DataColumn'/> objects.
48         /// </summary>
UniqueConstraint(string name, DataColumn[] columns)49         public UniqueConstraint(string name, DataColumn[] columns)
50         {
51             Create(name, columns);
52         }
53 
54         /// <summary>
55         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/>
56         /// objects.
57         /// </summary>
UniqueConstraint(DataColumn[] columns)58         public UniqueConstraint(DataColumn[] columns)
59         {
60             Create(null, columns);
61         }
62 
63         // Construct design time object
64         [Browsable(false)]
UniqueConstraint(string name, string[] columnNames, bool isPrimaryKey)65         public UniqueConstraint(string name, string[] columnNames, bool isPrimaryKey)
66         {
67             _constraintName = name;
68             _columnNames = columnNames;
69             _bPrimaryKey = isPrimaryKey;
70         }
71 
72         /// <summary>
73         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and
74         /// <see cref='System.Data.DataColumn'/>.
75         /// </summary>
UniqueConstraint(string name, DataColumn column, bool isPrimaryKey)76         public UniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
77         {
78             DataColumn[] columns = new DataColumn[1];
79             columns[0] = column;
80             _bPrimaryKey = isPrimaryKey;
81             Create(name, columns);
82         }
83 
84         /// <summary>
85         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>.
86         /// </summary>
UniqueConstraint(DataColumn column, bool isPrimaryKey)87         public UniqueConstraint(DataColumn column, bool isPrimaryKey)
88         {
89             DataColumn[] columns = new DataColumn[1];
90             columns[0] = column;
91             _bPrimaryKey = isPrimaryKey;
92             Create(null, columns);
93         }
94 
95         /// <summary>
96         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array
97         ///    of <see cref='System.Data.DataColumn'/> objects.
98         /// </summary>
UniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)99         public UniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
100         {
101             _bPrimaryKey = isPrimaryKey;
102             Create(name, columns);
103         }
104 
105         /// <summary>
106         /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/>
107         /// objects.
108         /// </summary>
UniqueConstraint(DataColumn[] columns, bool isPrimaryKey)109         public UniqueConstraint(DataColumn[] columns, bool isPrimaryKey)
110         {
111             _bPrimaryKey = isPrimaryKey;
112             Create(null, columns);
113         }
114 
115         // design time serialization only
116         internal string[] ColumnNames
117         {
118             get
119             {
120                 return _key.GetColumnNames();
121             }
122         }
123 
124         // Use constraint index only for search operations (and use key.GetSortIndex() when enumeration is needed and/or order is important)
125         internal Index ConstraintIndex
126         {
127             get
128             {
129                 AssertConstraintAndKeyIndexes();
130                 return _constraintIndex;
131             }
132         }
133 
134         [Conditional("DEBUG")]
AssertConstraintAndKeyIndexes()135         private void AssertConstraintAndKeyIndexes()
136         {
137             Debug.Assert(null != _constraintIndex, "null UniqueConstraint index");
138 
139             // ideally, we would like constraintIndex and key.GetSortIndex to share the same index underneath: Debug.Assert(_constraintIndex == key.GetSortIndex)
140             // but, there is a scenario where constraint and key indexes are built from the same list of columns but in a different order
141             DataColumn[] sortIndexColumns = new DataColumn[_constraintIndex._indexFields.Length];
142             for (int i = 0; i < sortIndexColumns.Length; i++)
143             {
144                 sortIndexColumns[i] = _constraintIndex._indexFields[i].Column;
145             }
146             Debug.Assert(DataKey.ColumnsEqual(_key.ColumnsReference, sortIndexColumns), "UniqueConstraint index columns do not match the key sort index");
147         }
148 
ConstraintIndexClear()149         internal void ConstraintIndexClear()
150         {
151             if (null != _constraintIndex)
152             {
153                 _constraintIndex.RemoveRef();
154                 _constraintIndex = null;
155             }
156         }
157 
ConstraintIndexInitialize()158         internal void ConstraintIndexInitialize()
159         {
160             if (null == _constraintIndex)
161             {
162                 _constraintIndex = _key.GetSortIndex();
163                 _constraintIndex.AddRef();
164             }
165 
166             AssertConstraintAndKeyIndexes();
167         }
168 
CheckState()169         internal override void CheckState()
170         {
171             NonVirtualCheckState();
172         }
173 
NonVirtualCheckState()174         private void NonVirtualCheckState()
175         {
176             _key.CheckState();
177         }
178 
CheckCanAddToCollection(ConstraintCollection constraints)179         internal override void CheckCanAddToCollection(ConstraintCollection constraints)
180         {
181         }
182 
CanBeRemovedFromCollection(ConstraintCollection constraints, bool fThrowException)183         internal override bool CanBeRemovedFromCollection(ConstraintCollection constraints, bool fThrowException)
184         {
185             if (Equals(constraints.Table._primaryKey))
186             {
187                 Debug.Assert(constraints.Table._primaryKey == this, "If the primary key and this are 'Equal', they should also be '=='");
188                 if (!fThrowException)
189                     return false;
190                 else
191                     throw ExceptionBuilder.RemovePrimaryKey(constraints.Table);
192             }
193             for (ParentForeignKeyConstraintEnumerator cs = new ParentForeignKeyConstraintEnumerator(Table.DataSet, Table); cs.GetNext();)
194             {
195                 ForeignKeyConstraint constraint = cs.GetForeignKeyConstraint();
196                 if (!_key.ColumnsEqual(constraint.ParentKey))
197                     continue;
198 
199                 if (!fThrowException)
200                     return false;
201                 else
202                     throw ExceptionBuilder.NeededForForeignKeyConstraint(this, constraint);
203             }
204 
205             return true;
206         }
207 
CanEnableConstraint()208         internal override bool CanEnableConstraint()
209         {
210             if (Table.EnforceConstraints)
211                 return ConstraintIndex.CheckUnique();
212 
213             return true;
214         }
215 
IsConstraintViolated()216         internal override bool IsConstraintViolated()
217         {
218             bool result = false;
219             Index index = ConstraintIndex;
220             if (index.HasDuplicates)
221             {
222                 object[] uniqueKeys = index.GetUniqueKeyValues();
223 
224                 for (int i = 0; i < uniqueKeys.Length; i++)
225                 {
226                     Range r = index.FindRecords((object[])uniqueKeys[i]);
227                     if (1 < r.Count)
228                     {
229                         DataRow[] rows = index.GetRows(r);
230                         string error = ExceptionBuilder.UniqueConstraintViolationText(_key.ColumnsReference, (object[])uniqueKeys[i]);
231                         for (int j = 0; j < rows.Length; j++)
232                         {
233                             rows[j].RowError = error;
234                             foreach (DataColumn dataColumn in _key.ColumnsReference)
235                             {
236                                 rows[j].SetColumnError(dataColumn, error);
237                             }
238                         }
239                         result = true;
240                     }
241                 }
242             }
243             return result;
244         }
245 
CheckConstraint(DataRow row, DataRowAction action)246         internal override void CheckConstraint(DataRow row, DataRowAction action)
247         {
248             if (Table.EnforceConstraints &&
249                 (action == DataRowAction.Add ||
250                  action == DataRowAction.Change ||
251                  (action == DataRowAction.Rollback && row._tempRecord != -1)))
252             {
253                 if (row.HaveValuesChanged(ColumnsReference))
254                 {
255                     if (ConstraintIndex.IsKeyRecordInIndex(row.GetDefaultRecord()))
256                     {
257                         object[] values = row.GetColumnValues(ColumnsReference);
258                         throw ExceptionBuilder.ConstraintViolation(ColumnsReference, values);
259                     }
260                 }
261             }
262         }
263 
ContainsColumn(DataColumn column)264         internal override bool ContainsColumn(DataColumn column)
265         {
266             return _key.ContainsColumn(column);
267         }
268 
Clone(DataSet destination)269         internal override Constraint Clone(DataSet destination)
270         {
271             return Clone(destination, false);
272         }
273 
Clone(DataSet destination, bool ignorNSforTableLookup)274         internal override Constraint Clone(DataSet destination, bool ignorNSforTableLookup)
275         {
276             int iDest;
277             if (ignorNSforTableLookup)
278             {
279                 iDest = destination.Tables.IndexOf(Table.TableName);
280             }
281             else
282             {
283                 iDest = destination.Tables.IndexOf(Table.TableName, Table.Namespace, false);// pass false for last param to be backward compatable
284             }
285 
286             if (iDest < 0)
287                 return null;
288             DataTable table = destination.Tables[iDest];
289 
290             int keys = ColumnsReference.Length;
291             DataColumn[] columns = new DataColumn[keys];
292 
293             for (int i = 0; i < keys; i++)
294             {
295                 DataColumn src = ColumnsReference[i];
296                 iDest = table.Columns.IndexOf(src.ColumnName);
297                 if (iDest < 0)
298                     return null;
299                 columns[i] = table.Columns[iDest];
300             }
301 
302             UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns);
303 
304             // ...Extended Properties
305             foreach (object key in ExtendedProperties.Keys)
306             {
307                 clone.ExtendedProperties[key] = ExtendedProperties[key];
308             }
309 
310             return clone;
311         }
312 
Clone(DataTable table)313         internal UniqueConstraint Clone(DataTable table)
314         {
315             int keys = ColumnsReference.Length;
316             DataColumn[] columns = new DataColumn[keys];
317 
318             for (int i = 0; i < keys; i++)
319             {
320                 DataColumn src = ColumnsReference[i];
321                 int iDest = table.Columns.IndexOf(src.ColumnName);
322                 if (iDest < 0)
323                     return null;
324                 columns[i] = table.Columns[iDest];
325             }
326 
327             UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns);
328 
329             // ...Extended Properties
330             foreach (object key in ExtendedProperties.Keys)
331             {
332                 clone.ExtendedProperties[key] = ExtendedProperties[key];
333             }
334 
335             return clone;
336         }
337 
338         /// <summary>
339         /// Gets the array of columns that this constraint affects.
340         /// </summary>
341         [ReadOnly(true)]
342         public virtual DataColumn[] Columns
343         {
344             get
345             {
346                 return _key.ToArray();
347             }
348         }
349 
350         internal DataColumn[] ColumnsReference
351         {
352             get
353             {
354                 return _key.ColumnsReference;
355             }
356         }
357 
358         /// <summary>
359         /// Gets a value indicating whether or not the constraint is on a primary key.
360         /// </summary>
361         public bool IsPrimaryKey
362         {
363             get
364             {
365                 if (Table == null)
366                 {
367                     return false;
368                 }
369                 return (this == Table._primaryKey);
370             }
371         }
372 
Create(string constraintName, DataColumn[] columns)373         private void Create(string constraintName, DataColumn[] columns)
374         {
375             for (int i = 0; i < columns.Length; i++)
376             {
377                 if (columns[i].Computed)
378                 {
379                     throw ExceptionBuilder.ExpressionInConstraint(columns[i]);
380                 }
381             }
382             _key = new DataKey(columns, true);
383             ConstraintName = constraintName;
384             NonVirtualCheckState();
385         }
386 
387         /// <summary>
388         /// Compares this constraint to a second to determine if both are identical.
389         /// </summary>
Equals(object key2)390         public override bool Equals(object key2)
391         {
392             if (!(key2 is UniqueConstraint))
393                 return false;
394 
395             return Key.ColumnsEqual(((UniqueConstraint)key2).Key);
396         }
397 
GetHashCode()398         public override int GetHashCode()
399         {
400             return base.GetHashCode();
401         }
402 
403         internal override bool InCollection
404         {
405             set
406             {
407                 base.InCollection = value;
408                 if (_key.ColumnsReference.Length == 1)
409                 {
410                     _key.ColumnsReference[0].InternalUnique(value);
411                 }
412             }
413         }
414 
415         internal DataKey Key
416         {
417             get
418             {
419                 return _key;
420             }
421         }
422 
423         /// <summary>
424         /// Gets the table to which this constraint belongs.
425         /// </summary>
426         [ReadOnly(true)]
427         public override DataTable Table
428         {
429             get
430             {
431                 if (_key.HasValue)
432                 {
433                     return _key.Table;
434                 }
435                 return null;
436             }
437         }
438 
439         // misc
440     }
441 }
442