diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs | 625 |
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 | |||
3 | namespace 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 | } | ||