aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs625
1 files changed, 625 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs
new file mode 100644
index 00000000..21e8543e
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs
@@ -0,0 +1,625 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.WindowsInstaller
4{
5 using System;
6 using System.Text;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.Diagnostics.CodeAnalysis;
10
11 /// <summary>
12 /// A View represents a result set obtained when processing a query using the
13 /// <see cref="WixToolset.Dtf.WindowsInstaller.Database.OpenView"/> method of a
14 /// <see cref="Database"/>. Before any data can be transferred,
15 /// the query must be executed using the <see cref="Execute(Record)"/> method, passing to
16 /// it all replaceable parameters designated within the SQL query string.
17 /// </summary>
18 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
19 public class View : InstallerHandle, IEnumerable<Record>
20 {
21 private Database database;
22 private string sql;
23 private IList<TableInfo> tables;
24 private ColumnCollection columns;
25
26 internal View(IntPtr handle, string sql, Database database)
27 : base(handle, true)
28 {
29 this.sql = sql;
30 this.database = database;
31 }
32
33 /// <summary>
34 /// Gets the Database on which this View was opened.
35 /// </summary>
36 public Database Database
37 {
38 get { return this.database; }
39 }
40
41 /// <summary>
42 /// Gets the SQL query string used to open this View.
43 /// </summary>
44 public string QueryString
45 {
46 get { return this.sql; }
47 }
48
49 /// <summary>
50 /// Gets the set of tables that were included in the SQL query for this View.
51 /// </summary>
52 public IList<TableInfo> Tables
53 {
54 get
55 {
56 if (this.tables == null)
57 {
58 if (this.sql == null)
59 {
60 return null;
61 }
62
63 // Parse the table names out of the SQL query string by looking
64 // for tokens that can come before or after the list of tables.
65
66 string parseSql = this.sql.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ');
67 string upperSql = parseSql.ToUpper(CultureInfo.InvariantCulture);
68
69 string[] prefixes = new string[] { " FROM ", " INTO ", " TABLE " };
70 string[] suffixes = new string[] { " WHERE ", " ORDER ", " SET ", " (", " ADD " };
71
72 int index;
73
74 for (int i = 0; i < prefixes.Length; i++)
75 {
76 if ((index = upperSql.IndexOf(prefixes[i], StringComparison.Ordinal)) > 0)
77 {
78 parseSql = parseSql.Substring(index + prefixes[i].Length);
79 upperSql = upperSql.Substring(index + prefixes[i].Length);
80 }
81 }
82
83 if (upperSql.StartsWith("UPDATE ", StringComparison.Ordinal))
84 {
85 parseSql = parseSql.Substring(7);
86 upperSql = upperSql.Substring(7);
87 }
88
89 for (int i = 0; i < suffixes.Length; i++)
90 {
91 if ((index = upperSql.IndexOf(suffixes[i], StringComparison.Ordinal)) > 0)
92 {
93 parseSql = parseSql.Substring(0, index);
94 upperSql = upperSql.Substring(0, index);
95 }
96 }
97
98 if (upperSql.EndsWith(" HOLD", StringComparison.Ordinal) ||
99 upperSql.EndsWith(" FREE", StringComparison.Ordinal))
100 {
101 parseSql = parseSql.Substring(0, parseSql.Length - 5);
102 upperSql = upperSql.Substring(0, upperSql.Length - 5);
103 }
104
105 // At this point we should be left with a comma-separated list of table names,
106 // optionally quoted with grave accent marks (`).
107
108 string[] tableNames = parseSql.Split(',');
109 IList<TableInfo> tableList = new List<TableInfo>(tableNames.Length);
110 for (int i = 0; i < tableNames.Length; i++)
111 {
112 string tableName = tableNames[i].Trim(' ', '`');
113 tableList.Add(new TableInfo(this.database, tableName));
114 }
115 this.tables = tableList;
116 }
117 return new List<TableInfo>(this.tables);
118 }
119 }
120
121 /// <summary>
122 /// Gets the set of columns that were included in the query for this View,
123 /// or null if this view is not a SELECT query.
124 /// </summary>
125 /// <exception cref="InstallerException">the View is not in an active state</exception>
126 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
127 /// <remarks><p>
128 /// Win32 MSI API:
129 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgetcolumninfo.asp">MsiViewGetColumnInfo</a>
130 /// </p></remarks>
131 public ColumnCollection Columns
132 {
133 get
134 {
135 if (this.columns == null)
136 {
137 this.columns = new ColumnCollection(this);
138 }
139 return this.columns;
140 }
141 }
142
143 /// <summary>
144 /// Executes a SQL View query and supplies any required parameters. The query uses the
145 /// question mark token to represent parameters as described in SQL Syntax. The values of
146 /// these parameters are passed in as the corresponding fields of a parameter record.
147 /// </summary>
148 /// <param name="executeParams">Optional Record that supplies the parameters. This
149 /// Record contains values to replace the parameter tokens in the SQL query.</param>
150 /// <exception cref="InstallerException">the View could not be executed</exception>
151 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
152 /// <remarks><p>
153 /// Win32 MSI API:
154 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
155 /// </p></remarks>
156 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Params"), SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
157 public void Execute(Record executeParams)
158 {
159 uint ret = RemotableNativeMethods.MsiViewExecute(
160 (int) this.Handle,
161 (executeParams != null ? (int) executeParams.Handle : 0));
162 if (ret == (uint) NativeMethods.Error.BAD_QUERY_SYNTAX)
163 {
164 throw new BadQuerySyntaxException(this.sql);
165 }
166 else if (ret != 0)
167 {
168 throw InstallerException.ExceptionFromReturnCode(ret);
169 }
170 }
171
172 /// <summary>
173 /// Executes a SQL View query.
174 /// </summary>
175 /// <exception cref="InstallerException">the View could not be executed</exception>
176 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
177 /// <remarks><p>
178 /// Win32 MSI API:
179 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
180 /// </p></remarks>
181 public void Execute() { this.Execute(null); }
182
183 /// <summary>
184 /// Fetches the next sequential record from the view, or null if there are no more records.
185 /// </summary>
186 /// <exception cref="InstallerException">the View is not in an active state</exception>
187 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
188 /// <remarks><p>
189 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
190 /// It is best that the handle be closed manually as soon as it is no longer
191 /// needed, as leaving lots of unused handles open can degrade performance.
192 /// </p><p>
193 /// Win32 MSI API:
194 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
195 /// </p></remarks>
196 public Record Fetch()
197 {
198 int recordHandle;
199 uint ret = RemotableNativeMethods.MsiViewFetch((int) this.Handle, out recordHandle);
200 if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
201 {
202 return null;
203 }
204 else if (ret != 0)
205 {
206 throw InstallerException.ExceptionFromReturnCode(ret);
207 }
208
209 Record r = new Record((IntPtr) recordHandle, true, this);
210 r.IsFormatStringInvalid = true;
211 return r;
212 }
213
214 /// <summary>
215 /// Updates a fetched Record.
216 /// </summary>
217 /// <param name="mode">specifies the modify mode</param>
218 /// <param name="record">the Record to modify</param>
219 /// <exception cref="InstallerException">the modification failed,
220 /// or a validation was requested and the data did not pass</exception>
221 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
222 /// <remarks><p>
223 /// You can update or delete a record immediately after inserting, or seeking provided you
224 /// have NOT modified the 0th field of the inserted or sought record.
225 /// </p><p>
226 /// To execute any SQL statement, a View must be created. However, a View that does not
227 /// create a result set, such as CREATE TABLE, or INSERT INTO, cannot be used with any of
228 /// the Modify methods to update tables though the view.
229 /// </p><p>
230 /// You cannot fetch a record containing binary data from one database and then use
231 /// that record to insert the data into another database. To move binary data from one database
232 /// to another, you should export the data to a file and then import it into the new database
233 /// using a query and the <see cref="Record.SetStream(int,string)"/>. This ensures that each database has
234 /// its own copy of the binary data.
235 /// </p><p>
236 /// Note that custom actions can only add, modify, or remove temporary rows, columns,
237 /// or tables from a database. Custom actions cannot modify persistent data in a database,
238 /// such as data that is a part of the database stored on disk.
239 /// </p><p>
240 /// Win32 MSI API:
241 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
242 /// </p></remarks>
243 /// <seealso cref="Refresh"/>
244 /// <seealso cref="Insert"/>
245 /// <seealso cref="Update"/>
246 /// <seealso cref="Assign"/>
247 /// <seealso cref="Replace"/>
248 /// <seealso cref="Delete"/>
249 /// <seealso cref="InsertTemporary"/>
250 /// <seealso cref="Seek"/>
251 /// <seealso cref="Merge"/>
252 /// <seealso cref="Validate"/>
253 /// <seealso cref="ValidateNew"/>
254 /// <seealso cref="ValidateFields"/>
255 /// <seealso cref="ValidateDelete"/>
256 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
257 public void Modify(ViewModifyMode mode, Record record)
258 {
259 if (record == null)
260 {
261 throw new ArgumentNullException("record");
262 }
263
264 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
265 if (mode == ViewModifyMode.Insert || mode == ViewModifyMode.InsertTemporary)
266 {
267 record.IsFormatStringInvalid = true;
268 }
269 if (ret != 0)
270 {
271 throw InstallerException.ExceptionFromReturnCode(ret);
272 }
273 }
274
275 /// <summary>
276 /// Refreshes the data in a Record.
277 /// </summary>
278 /// <param name="record">the Record to be refreshed</param>
279 /// <exception cref="InstallerException">the refresh failed</exception>
280 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
281 /// <remarks><p>
282 /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails with
283 /// a deleted Record. Works only with read-write Records.
284 /// </p><p>
285 /// See <see cref="Modify"/> for more remarks.
286 /// </p><p>
287 /// Win32 MSI API:
288 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
289 /// </p></remarks>
290 public void Refresh(Record record) { this.Modify(ViewModifyMode.Refresh, record); }
291
292 /// <summary>
293 /// Inserts a Record into the view.
294 /// </summary>
295 /// <param name="record">the Record to be inserted</param>
296 /// <exception cref="InstallerException">the insertion failed</exception>
297 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
298 /// <remarks><p>
299 /// Fails if a row with the same primary keys exists. Fails with a read-only database.
300 /// This method cannot be used with a View containing joins.
301 /// </p><p>
302 /// See <see cref="Modify"/> for more remarks.
303 /// </p><p>
304 /// Win32 MSI API:
305 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
306 /// </p></remarks>
307 public void Insert(Record record) { this.Modify(ViewModifyMode.Insert, record); }
308
309 /// <summary>
310 /// Updates the View with new data from the Record.
311 /// </summary>
312 /// <param name="record">the new data</param>
313 /// <exception cref="InstallerException">the update failed</exception>
314 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
315 /// <remarks><p>
316 /// Only non-primary keys can be updated. The Record must have been obtained by calling
317 /// <see cref="Fetch"/>. Fails with a deleted Record. Works only with read-write Records.
318 /// </p><p>
319 /// See <see cref="Modify"/> for more remarks.
320 /// </p><p>
321 /// Win32 MSI API:
322 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
323 /// </p></remarks>
324 public void Update(Record record) { this.Modify(ViewModifyMode.Update, record); }
325
326 /// <summary>
327 /// Updates or inserts a Record into the View.
328 /// </summary>
329 /// <param name="record">the Record to be assigned</param>
330 /// <exception cref="InstallerException">the assignment failed</exception>
331 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
332 /// <remarks><p>
333 /// Updates record if the primary keys match an existing row and inserts if they do not match.
334 /// Fails with a read-only database. This method cannot be used with a View containing joins.
335 /// </p><p>
336 /// See <see cref="Modify"/> for more remarks.
337 /// </p><p>
338 /// Win32 MSI API:
339 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
340 /// </p></remarks>
341 public void Assign(Record record) { this.Modify(ViewModifyMode.Assign, record); }
342
343 /// <summary>
344 /// Updates or deletes and inserts a Record into the View.
345 /// </summary>
346 /// <param name="record">the Record to be replaced</param>
347 /// <exception cref="InstallerException">the replacement failed</exception>
348 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
349 /// <remarks><p>
350 /// The Record must have been obtained by calling <see cref="Fetch"/>. Updates record if the
351 /// primary keys are unchanged. Deletes old row and inserts new if primary keys have changed.
352 /// Fails with a read-only database. This method cannot be used with a View containing joins.
353 /// </p><p>
354 /// See <see cref="Modify"/> for more remarks.
355 /// </p><p>
356 /// Win32 MSI API:
357 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
358 /// </p></remarks>
359 public void Replace(Record record) { this.Modify(ViewModifyMode.Replace, record); }
360
361 /// <summary>
362 /// Deletes a Record from the View.
363 /// </summary>
364 /// <param name="record">the Record to be deleted</param>
365 /// <exception cref="InstallerException">the deletion failed</exception>
366 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
367 /// <remarks><p>
368 /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails if the row has been
369 /// deleted. Works only with read-write records. This method cannot be used with a View containing joins.
370 /// </p><p>
371 /// See <see cref="Modify"/> for more remarks.
372 /// </p><p>
373 /// Win32 MSI API:
374 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
375 /// </p></remarks>
376 public void Delete(Record record) { this.Modify(ViewModifyMode.Delete, record); }
377
378 /// <summary>
379 /// Inserts a Record into the View. The inserted data is not persistent.
380 /// </summary>
381 /// <param name="record">the Record to be inserted</param>
382 /// <exception cref="InstallerException">the insertion failed</exception>
383 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
384 /// <remarks><p>
385 /// Fails if a row with the same primary key exists. Works only with read-write records.
386 /// This method cannot be used with a View containing joins.
387 /// </p><p>
388 /// See <see cref="Modify"/> for more remarks.
389 /// </p><p>
390 /// Win32 MSI API:
391 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
392 /// </p></remarks>
393 public void InsertTemporary(Record record) { this.Modify(ViewModifyMode.InsertTemporary, record); }
394
395 /// <summary>
396 /// Refreshes the information in the supplied record without changing the position
397 /// in the result set and without affecting subsequent fetch operations.
398 /// </summary>
399 /// <param name="record">the Record to be filled with the result of the seek</param>
400 /// <exception cref="InstallerException">the seek failed</exception>
401 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
402 /// <remarks><p>
403 /// After seeking, the Record may then be used for subsequent Update, Delete, and Refresh
404 /// operations. All primary key columns of the table must be in the query and the Record must
405 /// have at least as many fields as the query. Seek cannot be used with multi-table queries.
406 /// This method cannot be used with a View containing joins.
407 /// </p><p>
408 /// See <see cref="Modify"/> for more remarks.
409 /// </p><p>
410 /// Win32 MSI API:
411 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
412 /// </p></remarks>
413 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
414 public bool Seek(Record record)
415 {
416 if (record == null)
417 {
418 throw new ArgumentNullException("record");
419 }
420
421 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Seek, (int) record.Handle);
422 record.IsFormatStringInvalid = true;
423 if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
424 {
425 return false;
426 }
427 else if (ret != 0)
428 {
429 throw InstallerException.ExceptionFromReturnCode(ret);
430 }
431
432 return true;
433 }
434
435 /// <summary>
436 /// Inserts or validates a record.
437 /// </summary>
438 /// <param name="record">the Record to be merged</param>
439 /// <returns>true if the record was inserted or validated, false if there is an existing
440 /// record with the same primary keys that is not identical</returns>
441 /// <exception cref="InstallerException">the merge failed (for a reason other than invalid data)</exception>
442 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
443 /// <remarks><p>
444 /// Works only with read-write records. This method cannot be used with a
445 /// View containing joins.
446 /// </p><p>
447 /// See <see cref="Modify"/> for more remarks.
448 /// </p><p>
449 /// Win32 MSI API:
450 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
451 /// </p></remarks>
452 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
453 public bool Merge(Record record)
454 {
455 if (record == null)
456 {
457 throw new ArgumentNullException("record");
458 }
459
460 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Merge, (int) record.Handle);
461 if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
462 {
463 return false;
464 }
465 else if (ret != 0)
466 {
467 throw InstallerException.ExceptionFromReturnCode(ret);
468 }
469 return true;
470 }
471
472 /// <summary>
473 /// Validates a record, returning information about any errors.
474 /// </summary>
475 /// <param name="record">the Record to be validated</param>
476 /// <returns>null if the record was validated; if there is an existing record with
477 /// the same primary keys that has conflicting data then error information is returned</returns>
478 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
479 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
480 /// <remarks><p>
481 /// The Record must have been obtained by calling <see cref="Fetch"/>.
482 /// Works with read-write and read-only records. This method cannot be used
483 /// with a View containing joins.
484 /// </p><p>
485 /// See <see cref="Modify"/> for more remarks.
486 /// </p><p>
487 /// Win32 MSI APIs:
488 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
489 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
490 /// </p></remarks>
491 public ICollection<ValidationErrorInfo> Validate(Record record) { return this.InternalValidate(ViewModifyMode.Validate, record); }
492
493 /// <summary>
494 /// Validates a new record, returning information about any errors.
495 /// </summary>
496 /// <param name="record">the Record to be validated</param>
497 /// <returns>null if the record was validated; if there is an existing
498 /// record with the same primary keys then error information is returned</returns>
499 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
500 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
501 /// <remarks><p>
502 /// Checks for duplicate keys. The Record must have been obtained by
503 /// calling <see cref="Fetch"/>. Works with read-write and read-only records.
504 /// This method cannot be used with a View containing joins.
505 /// </p><p>
506 /// See <see cref="Modify"/> for more remarks.
507 /// </p><p>
508 /// Win32 MSI APIs:
509 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
510 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
511 /// </p></remarks>
512 [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
513 public ICollection<ValidationErrorInfo> ValidateNew(Record record) { return this.InternalValidate(ViewModifyMode.ValidateNew, record); }
514
515 /// <summary>
516 /// Validates fields of a fetched or new record, returning information about any errors.
517 /// Can validate one or more fields of an incomplete record.
518 /// </summary>
519 /// <param name="record">the Record to be validated</param>
520 /// <returns>null if the record was validated; if there is an existing record with
521 /// the same primary keys that has conflicting data then error information is returned</returns>
522 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
523 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
524 /// <remarks><p>
525 /// Works with read-write and read-only records. This method cannot be used with
526 /// a View containing joins.
527 /// </p><p>
528 /// See <see cref="Modify"/> for more remarks.
529 /// </p><p>
530 /// Win32 MSI APIs:
531 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
532 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
533 /// </p></remarks>
534 public ICollection<ValidationErrorInfo> ValidateFields(Record record) { return this.InternalValidate(ViewModifyMode.ValidateField, record); }
535
536 /// <summary>
537 /// Validates a record that will be deleted later, returning information about any errors.
538 /// </summary>
539 /// <param name="record">the Record to be validated</param>
540 /// <returns>null if the record is safe to delete; if another row refers to
541 /// the primary keys of this row then error information is returned</returns>
542 /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
543 /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
544 /// <remarks><p>
545 /// Validation does not check for the existence of the primary keys of this row in properties
546 /// or strings. Does not check if a column is a foreign key to multiple tables. Works with
547 /// read-write and read-only records. This method cannot be used with a View containing joins.
548 /// </p><p>
549 /// See <see cref="Modify"/> for more remarks.
550 /// </p><p>
551 /// Win32 MSI APIs:
552 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
553 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
554 /// </p></remarks>
555 public ICollection<ValidationErrorInfo> ValidateDelete(Record record) { return this.InternalValidate(ViewModifyMode.ValidateDelete, record); }
556
557 /// <summary>
558 /// Enumerates over the Records retrieved by the View.
559 /// </summary>
560 /// <returns>An enumerator of Record objects.</returns>
561 /// <exception cref="InstallerException">The View was not <see cref="Execute(Record)"/>d before attempting the enumeration.</exception>
562 /// <remarks><p>
563 /// Each Record object should be <see cref="InstallerHandle.Close"/>d after use.
564 /// It is best that the handle be closed manually as soon as it is no longer
565 /// needed, as leaving lots of unused handles open can degrade performance.
566 /// However, note that it is not necessary to complete the enumeration just
567 /// for the purpose of closing handles, because Records are fetched lazily
568 /// on each step of the enumeration.
569 /// </p><p>
570 /// Win32 MSI API:
571 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
572 /// </p></remarks>
573 public IEnumerator<Record> GetEnumerator()
574 {
575 Record rec;
576 while ((rec = this.Fetch()) != null)
577 {
578 yield return rec;
579 }
580 }
581
582 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
583 {
584 return this.GetEnumerator();
585 }
586
587 private ICollection<ValidationErrorInfo> InternalValidate(ViewModifyMode mode, Record record)
588 {
589 uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
590 if (ret == (uint) NativeMethods.Error.INVALID_DATA)
591 {
592 ICollection<ValidationErrorInfo> errorInfo = new List<ValidationErrorInfo>();
593 while (true)
594 {
595 uint bufSize = 40;
596 StringBuilder column = new StringBuilder("", (int) bufSize);
597 int error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
598 if (error == -2 /*MSIDBERROR_MOREDATA*/)
599 {
600 column.Capacity = (int) ++bufSize;
601 error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
602 }
603
604 if (error == -3 /*MSIDBERROR_INVALIDARG*/)
605 {
606 throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.INVALID_PARAMETER);
607 }
608 else if (error == 0 /*MSIDBERROR_NOERROR*/)
609 {
610 break;
611 }
612
613 errorInfo.Add(new ValidationErrorInfo((ValidationError) error, column.ToString()));
614 }
615
616 return errorInfo;
617 }
618 else if (ret != 0)
619 {
620 throw InstallerException.ExceptionFromReturnCode(ret);
621 }
622 return null;
623 }
624 }
625}