UniqueConstraint.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. //
  2. // System.Data.UniqueConstraint.cs
  3. //
  4. // Author:
  5. // Franklin Wise <[email protected]>
  6. // Daniel Morgan <[email protected]>
  7. // Tim Coleman ([email protected])
  8. //
  9. // (C) 2002 Franklin Wise
  10. // (C) 2002 Daniel Morgan
  11. // Copyright (C) Tim Coleman, 2002
  12. //
  13. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  14. //
  15. // Permission is hereby granted, free of charge, to any person obtaining
  16. // a copy of this software and associated documentation files (the
  17. // "Software"), to deal in the Software without restriction, including
  18. // without limitation the rights to use, copy, modify, merge, publish,
  19. // distribute, sublicense, and/or sell copies of the Software, and to
  20. // permit persons to whom the Software is furnished to do so, subject to
  21. // the following conditions:
  22. //
  23. // The above copyright notice and this permission notice shall be
  24. // included in all copies or substantial portions of the Software.
  25. //
  26. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  27. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  28. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  29. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  30. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  31. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  32. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  33. //
  34. using System;
  35. using System.Collections;
  36. using System.ComponentModel;
  37. using System.Runtime.InteropServices;
  38. using System.Data.Common;
  39. namespace System.Data {
  40. [Editor]
  41. [DefaultProperty ("ConstraintName")]
  42. [Serializable]
  43. public class UniqueConstraint : Constraint
  44. {
  45. private bool _isPrimaryKey = false;
  46. private bool _belongsToCollection = false;
  47. private DataTable _dataTable; //set by ctor except when unique case
  48. //FIXME: create a class which will wrap this collection
  49. private DataColumn [] _dataColumns;
  50. //TODO:provide helpers for this case
  51. private string [] _dataColumnNames; //unique case
  52. private bool _dataColsNotValidated;
  53. #region Constructors
  54. public UniqueConstraint (DataColumn column)
  55. {
  56. _uniqueConstraint ("", column, false);
  57. }
  58. public UniqueConstraint (DataColumn[] columns)
  59. {
  60. _uniqueConstraint ("", columns, false);
  61. }
  62. public UniqueConstraint (DataColumn column, bool isPrimaryKey)
  63. {
  64. _uniqueConstraint ("", column, isPrimaryKey);
  65. }
  66. public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
  67. {
  68. _uniqueConstraint ("", columns, isPrimaryKey);
  69. }
  70. public UniqueConstraint (string name, DataColumn column)
  71. {
  72. _uniqueConstraint (name, column, false);
  73. }
  74. public UniqueConstraint (string name, DataColumn[] columns)
  75. {
  76. _uniqueConstraint (name, columns, false);
  77. }
  78. public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
  79. {
  80. _uniqueConstraint (name, column, isPrimaryKey);
  81. }
  82. public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
  83. {
  84. _uniqueConstraint (name, columns, isPrimaryKey);
  85. }
  86. //Special case. Can only be added to the Collection with AddRange
  87. [Browsable (false)]
  88. public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
  89. {
  90. _dataColsNotValidated = true;
  91. //keep list of names to resolve later
  92. _dataColumnNames = columnNames;
  93. base.ConstraintName = name;
  94. _isPrimaryKey = isPrimaryKey;
  95. }
  96. //helper ctor
  97. private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
  98. {
  99. _dataColsNotValidated = false;
  100. //validate
  101. _validateColumn (column);
  102. //Set Constraint Name
  103. base.ConstraintName = name;
  104. _isPrimaryKey = isPrimaryKey;
  105. //keep reference
  106. _dataColumns = new DataColumn [] {column};
  107. //Get table reference
  108. _dataTable = column.Table;
  109. }
  110. //helpter ctor
  111. private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
  112. {
  113. _dataColsNotValidated = false;
  114. //validate
  115. _validateColumns (columns, out _dataTable);
  116. //Set Constraint Name
  117. base.ConstraintName = name;
  118. //keep reference
  119. _dataColumns = columns;
  120. //PK?
  121. _isPrimaryKey = isPrimaryKey;
  122. }
  123. #endregion // Constructors
  124. #region Helpers
  125. private void _validateColumns(DataColumn [] columns)
  126. {
  127. DataTable table;
  128. _validateColumns(columns, out table);
  129. }
  130. //Validates a collection of columns with the ctor rules
  131. private void _validateColumns(DataColumn [] columns, out DataTable table) {
  132. table = null;
  133. //not null
  134. if (null == columns) throw new ArgumentNullException();
  135. //check that there is at least one column
  136. //LAMESPEC: not in spec
  137. if (columns.Length < 1)
  138. throw new InvalidConstraintException("Must be at least one column.");
  139. DataTable compareTable = columns[0].Table;
  140. //foreach
  141. foreach (DataColumn col in columns){
  142. //check individual column rules
  143. _validateColumn (col);
  144. //check that columns are all from the same table??
  145. //LAMESPEC: not in spec
  146. if (compareTable != col.Table)
  147. throw new InvalidConstraintException("Columns must be from the same table.");
  148. }
  149. table = compareTable;
  150. }
  151. //validates a column with the ctor rules
  152. private void _validateColumn(DataColumn column) {
  153. //not null
  154. if (null == column) // FIXME: This is little weird, but here it goes...
  155. throw new NullReferenceException("Object reference not set to an instance of an object.");
  156. //column must belong to a table
  157. //LAMESPEC: not in spec
  158. if (null == column.Table)
  159. throw new ArgumentException ("Column must belong to a table.");
  160. }
  161. internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
  162. {
  163. //not null
  164. if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
  165. //make sure newPrimaryKey belongs to the collection parm unless it is null
  166. if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
  167. throw new ArgumentException("newPrimaryKey must belong to collection.");
  168. //Get existing pk
  169. UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
  170. //clear existing
  171. if (null != uc) uc._isPrimaryKey = false;
  172. //set new key
  173. if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
  174. }
  175. internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
  176. {
  177. if (null == collection) throw new ArgumentNullException("Collection can't be null.");
  178. UniqueConstraint uc;
  179. IEnumerator enumer = collection.GetEnumerator();
  180. while (enumer.MoveNext())
  181. {
  182. uc = enumer.Current as UniqueConstraint;
  183. if (null == uc) continue;
  184. if (uc.IsPrimaryKey) return uc;
  185. }
  186. //if we got here there was no pk
  187. return null;
  188. }
  189. internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
  190. DataColumn[] columns)
  191. {
  192. if (null == collection) throw new ArgumentNullException("Collection can't be null.");
  193. if (null == columns ) return null;
  194. foreach(Constraint constraint in collection) {
  195. if (constraint is UniqueConstraint) {
  196. UniqueConstraint uc = constraint as UniqueConstraint;
  197. if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
  198. return uc;
  199. }
  200. }
  201. }
  202. return null;
  203. }
  204. internal bool DataColsNotValidated
  205. {
  206. get {
  207. return (_dataColsNotValidated);
  208. }
  209. }
  210. // Helper Special Ctor
  211. // Set the _dataTable property to the table to which this instance is bound when AddRange()
  212. // is called with the special constructor.
  213. // Validate whether the named columns exist in the _dataTable
  214. internal void PostAddRange( DataTable _setTable )
  215. {
  216. _dataTable = _setTable;
  217. DataColumn []cols = new DataColumn [_dataColumnNames.Length];
  218. int i = 0;
  219. foreach ( string _columnName in _dataColumnNames ) {
  220. if ( _setTable.Columns.Contains (_columnName) ) {
  221. cols [i] = _setTable.Columns [_columnName];
  222. i++;
  223. continue;
  224. }
  225. throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
  226. }
  227. _dataColumns = cols;
  228. }
  229. #endregion //Helpers
  230. #region Properties
  231. [DataCategory ("Data")]
  232. [DataSysDescription ("Indicates the columns of this constraint.")]
  233. [ReadOnly (true)]
  234. public virtual DataColumn[] Columns {
  235. get { return _dataColumns; }
  236. }
  237. [DataCategory ("Data")]
  238. [DataSysDescription ("Indicates if this constraint is a primary key.")]
  239. public bool IsPrimaryKey {
  240. get {
  241. if (Table == null || (!_belongsToCollection)) {
  242. return false;
  243. }
  244. return _isPrimaryKey;
  245. }
  246. }
  247. [DataCategory ("Data")]
  248. [DataSysDescription ("Indicates the table of this constraint.")]
  249. [ReadOnly (true)]
  250. public override DataTable Table {
  251. get { return _dataTable; }
  252. }
  253. #endregion // Properties
  254. #region Methods
  255. public override bool Equals(object key2) {
  256. UniqueConstraint cst = key2 as UniqueConstraint;
  257. if (null == cst) return false;
  258. //according to spec if the cols are equal
  259. //then two UniqueConstraints are equal
  260. return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
  261. }
  262. public override int GetHashCode()
  263. {
  264. //initialize hash with default value
  265. int hash = 42;
  266. int i;
  267. //derive the hash code from the columns that way
  268. //Equals and GetHashCode return Equal objects to be the
  269. //same
  270. //Get the first column hash
  271. if (this.Columns.Length > 0)
  272. hash ^= this.Columns[0].GetHashCode();
  273. //get the rest of the column hashes if there any
  274. for (i = 1; i < this.Columns.Length; i++)
  275. {
  276. hash ^= this.Columns[1].GetHashCode();
  277. }
  278. return hash ;
  279. }
  280. internal override void AddToConstraintCollectionSetup(
  281. ConstraintCollection collection)
  282. {
  283. for (int i = 0; i < Columns.Length; i++)
  284. if (Columns[i].Table != collection.Table)
  285. throw new ArgumentException("These columns don't point to this table.");
  286. //run Ctor rules again
  287. _validateColumns(_dataColumns);
  288. //make sure a unique constraint doesn't already exists for these columns
  289. UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
  290. if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
  291. " columns. Existing ConstraintName is " + uc.ConstraintName);
  292. //Allow only one primary key
  293. if (this.IsPrimaryKey) {
  294. uc = GetPrimaryKeyConstraint(collection);
  295. if (null != uc) uc._isPrimaryKey = false;
  296. }
  297. // if constraint is based on one column only
  298. // this column becomes unique
  299. if (_dataColumns.Length == 1) {
  300. _dataColumns[0].SetUnique();
  301. }
  302. //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
  303. //this method, so that it is executed twice. Need to investigate which
  304. // call to remove as that migth affect other parts of the classes.
  305. //AssertConstraint();
  306. if (IsConstraintViolated())
  307. throw new ArgumentException("These columns don't currently have unique values.");
  308. _belongsToCollection = true;
  309. }
  310. internal override void RemoveFromConstraintCollectionCleanup(
  311. ConstraintCollection collection)
  312. {
  313. _belongsToCollection = false;
  314. Index index = Index;
  315. Index = null;
  316. }
  317. protected override bool IsConstraintViolated()
  318. {
  319. if (Index == null) {
  320. Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
  321. }
  322. if (Index.HasDuplicates) {
  323. int[] dups = Index.Duplicates;
  324. for (int i = 0; i < dups.Length; i++){
  325. DataRow row = Table.RecordCache[dups[i]];
  326. ArrayList columns = new ArrayList();
  327. ArrayList values = new ArrayList();
  328. foreach (DataColumn col in Columns){
  329. columns.Add(col.ColumnName);
  330. values.Add(row[col].ToString());
  331. }
  332. string columnNames = String.Join(",", (string[])columns.ToArray(typeof(string)));
  333. string columnValues = String.Join(",", (string[])values.ToArray(typeof(string)));
  334. row.RowError = String.Format("Column(s) '{0}' are constrained to be unique. Value(s) '{1}' are already present", columnNames, columnValues);
  335. }
  336. // FIXME : check the exception to be thrown here
  337. // throw new ConstraintException("These columns don't currently have unique values");
  338. //throw new ConstraintException ("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
  339. return true;
  340. }
  341. return false;
  342. }
  343. internal override void AssertConstraint(DataRow row)
  344. {
  345. if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
  346. for (int i = 0; i < Columns.Length; i++) {
  347. if (row.IsNull(Columns[i])) {
  348. throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
  349. }
  350. }
  351. }
  352. if (Index == null) {
  353. Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
  354. }
  355. if (Index.HasDuplicates) {
  356. throw new ConstraintException(GetErrorMessage(row));
  357. }
  358. }
  359. internal override bool IsColumnContained(DataColumn column)
  360. {
  361. for (int i = 0; i < _dataColumns.Length; i++)
  362. if (column == _dataColumns[i])
  363. return true;
  364. return false;
  365. }
  366. internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
  367. if (Equals(col.Table.PrimaryKey)){
  368. if (shouldThrow)
  369. throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
  370. return false;
  371. }
  372. if (Table.DataSet != null){
  373. foreach (DataTable table in Table.DataSet.Tables){
  374. foreach (Constraint constraint in table.Constraints){
  375. if (constraint is ForeignKeyConstraint)
  376. if (((ForeignKeyConstraint)constraint).RelatedTable == Table){
  377. if (shouldThrow)
  378. throw new ArgumentException(
  379. String.Format("Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first.",
  380. ConstraintName, constraint.ConstraintName)
  381. );
  382. return false;
  383. }
  384. }
  385. }
  386. }
  387. return true;
  388. }
  389. private string GetErrorMessage(DataRow row)
  390. {
  391. int i;
  392. System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
  393. for (i = 1; i < _dataColumns.Length; i++) {
  394. sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
  395. }
  396. string valStr = sb.ToString();
  397. sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
  398. for (i = 1; i < _dataColumns.Length; i++) {
  399. sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
  400. }
  401. string colStr = sb.ToString();
  402. return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
  403. }
  404. #endregion // Methods
  405. }
  406. }