diff options
Diffstat (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs new file mode 100644 index 00000000..2d31fa75 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs | |||
@@ -0,0 +1,1048 @@ | |||
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.IO; | ||
7 | using System.Text; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Runtime.InteropServices; | ||
10 | using System.Diagnostics.CodeAnalysis; | ||
11 | |||
12 | /// <summary> | ||
13 | /// The Record object is a container for holding and transferring a variable number of values. | ||
14 | /// Fields within the record are numerically indexed and can contain strings, integers, streams, | ||
15 | /// and null values. Record fields are indexed starting with 1. Field 0 is a special format field. | ||
16 | /// </summary> | ||
17 | /// <remarks><p> | ||
18 | /// Most methods on the Record class have overloads that allow using either a number | ||
19 | /// or a name to designate a field. However note that field names only exist when the | ||
20 | /// Record is directly returned from a query on a database. For other records, attempting | ||
21 | /// to access a field by name will result in an InvalidOperationException. | ||
22 | /// </p></remarks> | ||
23 | public class Record : InstallerHandle | ||
24 | { | ||
25 | private View view; | ||
26 | private bool isFormatStringInvalid; | ||
27 | |||
28 | /// <summary> | ||
29 | /// IsFormatStringInvalid is set from several View methods that invalidate the FormatString | ||
30 | /// and used to determine behavior during Record.ToString(). | ||
31 | /// </summary> | ||
32 | internal protected bool IsFormatStringInvalid | ||
33 | { | ||
34 | set { this.isFormatStringInvalid = value; } | ||
35 | |||
36 | get { return this.isFormatStringInvalid; } | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Creates a new record object with the requested number of fields. | ||
41 | /// </summary> | ||
42 | /// <param name="fieldCount">Required number of fields, which may be 0. | ||
43 | /// The maximum number of fields in a record is limited to 65535.</param> | ||
44 | /// <remarks><p> | ||
45 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
46 | /// It is best that the handle be closed manually as soon as it is no longer | ||
47 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
48 | /// </p><p> | ||
49 | /// Win32 MSI API: | ||
50 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a> | ||
51 | /// </p></remarks> | ||
52 | public Record(int fieldCount) | ||
53 | : this((IntPtr) RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, 0), true, (View) null) | ||
54 | { | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Creates a new record object, providing values for an arbitrary number of fields. | ||
59 | /// </summary> | ||
60 | /// <param name="fields">The values of the record fields. The parameters should be of type Int16, Int32 or String</param> | ||
61 | /// <remarks><p> | ||
62 | /// The Record object should be <see cref="InstallerHandle.Close"/>d after use. | ||
63 | /// It is best that the handle be closed manually as soon as it is no longer | ||
64 | /// needed, as leaving lots of unused handles open can degrade performance. | ||
65 | /// </p><p> | ||
66 | /// Win32 MSI API: | ||
67 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a> | ||
68 | /// </p></remarks> | ||
69 | public Record(params object[] fields) | ||
70 | : this(fields.Length) | ||
71 | { | ||
72 | for (int i = 0; i < fields.Length; i++) | ||
73 | { | ||
74 | this[i + 1] = fields[i]; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | internal Record(IntPtr handle, bool ownsHandle, View view) | ||
79 | : base(handle, ownsHandle) | ||
80 | { | ||
81 | this.view = view; | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Gets the number of fields in a record. | ||
86 | /// </summary> | ||
87 | /// <remarks><p> | ||
88 | /// Win32 MSI API: | ||
89 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetfieldcount.asp">MsiRecordGetFieldCount</a> | ||
90 | /// </p></remarks> | ||
91 | public int FieldCount | ||
92 | { | ||
93 | get | ||
94 | { | ||
95 | return (int) RemotableNativeMethods.MsiRecordGetFieldCount((int) this.Handle); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Gets or sets field 0 of the Record, which is the format string. | ||
101 | /// </summary> | ||
102 | public string FormatString | ||
103 | { | ||
104 | get { return this.GetString(0); } | ||
105 | set { this.SetString(0, value); } | ||
106 | } | ||
107 | |||
108 | /// <summary> | ||
109 | /// Gets or sets a record field value. | ||
110 | /// </summary> | ||
111 | /// <param name="fieldName">Specifies the name of the field of the Record to get or set.</param> | ||
112 | /// <exception cref="ArgumentOutOfRangeException">The name does not match any known field of the Record.</exception> | ||
113 | /// <remarks><p> | ||
114 | /// When getting a field, the type of the object returned depends on the type of the Record field. | ||
115 | /// The object will be one of: Int16, Int32, String, Stream, or null. | ||
116 | /// </p><p> | ||
117 | /// When setting a field, the type of the object provided will be converted to match the View | ||
118 | /// query that returned the record, or if Record was not returned from a view then the type of | ||
119 | /// the object provided will determine the type of the Record field. The object should be one of: | ||
120 | /// Int16, Int32, String, Stream, or null. | ||
121 | /// </p></remarks> | ||
122 | public object this[string fieldName] | ||
123 | { | ||
124 | get | ||
125 | { | ||
126 | int field = this.FindColumn(fieldName); | ||
127 | return this[field]; | ||
128 | } | ||
129 | |||
130 | set | ||
131 | { | ||
132 | int field = this.FindColumn(fieldName); | ||
133 | this[field] = value; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Gets or sets a record field value. | ||
139 | /// </summary> | ||
140 | /// <param name="field">Specifies the field of the Record to get or set.</param> | ||
141 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
142 | /// number of fields in the Record.</exception> | ||
143 | /// <remarks><p> | ||
144 | /// Record fields are indexed starting with 1. Field 0 is a special format field. | ||
145 | /// </p><p> | ||
146 | /// When getting a field, the type of the object returned depends on the type of the Record field. | ||
147 | /// The object will be one of: Int16, Int32, String, Stream, or null. If the Record was returned | ||
148 | /// from a View, the type will match that of the field from the View query. Otherwise, the type | ||
149 | /// will match the type of the last value set for the field. | ||
150 | /// </p><p> | ||
151 | /// When setting a field, the type of the object provided will be converted to match the View | ||
152 | /// query that returned the Record, or if Record was not returned from a View then the type of | ||
153 | /// the object provided will determine the type of the Record field. The object should be one of: | ||
154 | /// Int16, Int32, String, Stream, or null. | ||
155 | /// </p><p> | ||
156 | /// The type-specific getters and setters are slightly more efficient than this property, since | ||
157 | /// they don't have to do the extra work to infer the value's type every time. | ||
158 | /// </p><p> | ||
159 | /// Win32 MSI APIs: | ||
160 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>, | ||
161 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>, | ||
162 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>, | ||
163 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a> | ||
164 | /// </p></remarks> | ||
165 | public object this[int field] | ||
166 | { | ||
167 | get | ||
168 | { | ||
169 | if (field == 0) | ||
170 | { | ||
171 | return this.GetString(0); | ||
172 | } | ||
173 | else | ||
174 | { | ||
175 | Type valueType = null; | ||
176 | if (this.view != null) | ||
177 | { | ||
178 | this.CheckRange(field); | ||
179 | |||
180 | valueType = this.view.Columns[field - 1].Type; | ||
181 | } | ||
182 | |||
183 | if (valueType == null || valueType == typeof(String)) | ||
184 | { | ||
185 | return this.GetString(field); | ||
186 | } | ||
187 | else if (valueType == typeof(Stream)) | ||
188 | { | ||
189 | return this.IsNull(field) ? null : new RecordStream(this, field); | ||
190 | } | ||
191 | else | ||
192 | { | ||
193 | int? value = this.GetNullableInteger(field); | ||
194 | return value.HasValue ? (object) value.Value : null; | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | set | ||
200 | { | ||
201 | if (field == 0) | ||
202 | { | ||
203 | if (value == null) | ||
204 | { | ||
205 | value = String.Empty; | ||
206 | } | ||
207 | |||
208 | this.SetString(0, value.ToString()); | ||
209 | } | ||
210 | else if (value == null) | ||
211 | { | ||
212 | this.SetNullableInteger(field, null); | ||
213 | } | ||
214 | else | ||
215 | { | ||
216 | Type valueType = value.GetType(); | ||
217 | if (valueType == typeof(Int32) || valueType == typeof(Int16)) | ||
218 | { | ||
219 | this.SetInteger(field, (int) value); | ||
220 | } | ||
221 | else if (valueType.IsSubclassOf(typeof(Stream))) | ||
222 | { | ||
223 | this.SetStream(field, (Stream) value); | ||
224 | } | ||
225 | else | ||
226 | { | ||
227 | this.SetString(field, value.ToString()); | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | } | ||
232 | |||
233 | /// <summary> | ||
234 | /// Creates a new Record object from an integer record handle. | ||
235 | /// </summary> | ||
236 | /// <remarks><p> | ||
237 | /// This method is only provided for interop purposes. A Record object | ||
238 | /// should normally be obtained by calling <see cref="WixToolset.Dtf.WindowsInstaller.View.Fetch"/> | ||
239 | /// other methods. | ||
240 | /// <p>The handle will be closed when this object is disposed or finalized.</p> | ||
241 | /// </p></remarks> | ||
242 | /// <param name="handle">Integer record handle</param> | ||
243 | /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param> | ||
244 | public static Record FromHandle(IntPtr handle, bool ownsHandle) | ||
245 | { | ||
246 | return new Record(handle, ownsHandle, (View) null); | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Sets all fields in a record to null. | ||
251 | /// </summary> | ||
252 | /// <remarks><p> | ||
253 | /// Win32 MSI API: | ||
254 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordcleardata.asp">MsiRecordClearData</a> | ||
255 | /// </p></remarks> | ||
256 | public void Clear() | ||
257 | { | ||
258 | uint ret = RemotableNativeMethods.MsiRecordClearData((int) this.Handle); | ||
259 | if (ret != 0) | ||
260 | { | ||
261 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /// <summary> | ||
266 | /// Reports whether a record field is null. | ||
267 | /// </summary> | ||
268 | /// <param name="field">Specifies the field to check.</param> | ||
269 | /// <returns>True if the field is null, false otherwise.</returns> | ||
270 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
271 | /// number of fields in the Record.</exception> | ||
272 | /// <remarks><p> | ||
273 | /// Win32 MSI API: | ||
274 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordisnull.asp">MsiRecordIsNull</a> | ||
275 | /// </p></remarks> | ||
276 | public bool IsNull(int field) | ||
277 | { | ||
278 | this.CheckRange(field); | ||
279 | return RemotableNativeMethods.MsiRecordIsNull((int) this.Handle, (uint) field); | ||
280 | } | ||
281 | |||
282 | /// <summary> | ||
283 | /// Reports whether a record field is null. | ||
284 | /// </summary> | ||
285 | /// <param name="fieldName">Specifies the field to check.</param> | ||
286 | /// <returns>True if the field is null, false otherwise.</returns> | ||
287 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
288 | /// of the named fields in the Record.</exception> | ||
289 | public bool IsNull(string fieldName) | ||
290 | { | ||
291 | int field = this.FindColumn(fieldName); | ||
292 | return this.IsNull(field); | ||
293 | } | ||
294 | |||
295 | /// <summary> | ||
296 | /// Gets the length of a record field. The count does not include the terminating null. | ||
297 | /// </summary> | ||
298 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
299 | /// number of fields in the Record.</exception> | ||
300 | /// <remarks><p> | ||
301 | /// The returned data size is 0 if the field is null, non-existent, | ||
302 | /// or an internal object pointer. The method also returns 0 if the handle is not a valid | ||
303 | /// Record handle. | ||
304 | /// </p><p> | ||
305 | /// If the data is in integer format, the property returns 2 or 4. | ||
306 | /// </p><p> | ||
307 | /// If the data is in string format, the property returns the character count | ||
308 | /// (not including the NULL terminator). | ||
309 | /// </p><p> | ||
310 | /// If the data is in stream format, the property returns the byte count. | ||
311 | /// </p><p> | ||
312 | /// Win32 MSI API: | ||
313 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecorddatasize.asp">MsiRecordDataSize</a> | ||
314 | /// </p></remarks> | ||
315 | public int GetDataSize(int field) | ||
316 | { | ||
317 | this.CheckRange(field); | ||
318 | return (int) RemotableNativeMethods.MsiRecordDataSize((int) this.Handle, (uint) field); | ||
319 | } | ||
320 | |||
321 | /// <summary> | ||
322 | /// Gets the length of a record field. The count does not include the terminating null. | ||
323 | /// </summary> | ||
324 | /// <param name="fieldName">Specifies the field to check.</param> | ||
325 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
326 | /// of the named fields in the Record.</exception> | ||
327 | /// <remarks><p>The returned data size is 0 if the field is null, non-existent, | ||
328 | /// or an internal object pointer. The method also returns 0 if the handle is not a valid | ||
329 | /// Record handle. | ||
330 | /// </p><p> | ||
331 | /// If the data is in integer format, the property returns 2 or 4. | ||
332 | /// </p><p> | ||
333 | /// If the data is in string format, the property returns the character count | ||
334 | /// (not including the NULL terminator). | ||
335 | /// </p><p> | ||
336 | /// If the data is in stream format, the property returns the byte count. | ||
337 | /// </p></remarks> | ||
338 | public int GetDataSize(string fieldName) | ||
339 | { | ||
340 | int field = this.FindColumn(fieldName); | ||
341 | return this.GetDataSize(field); | ||
342 | } | ||
343 | |||
344 | /// <summary> | ||
345 | /// Gets a field value as an integer. | ||
346 | /// </summary> | ||
347 | /// <param name="field">Specifies the field to retrieve.</param> | ||
348 | /// <returns>Integer value of the field, or 0 if the field is null.</returns> | ||
349 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
350 | /// number of fields in the Record.</exception> | ||
351 | /// <remarks><p> | ||
352 | /// Win32 MSI API: | ||
353 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a> | ||
354 | /// </p></remarks> | ||
355 | /// <seealso cref="GetNullableInteger(int)"/> | ||
356 | [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")] | ||
357 | public int GetInteger(int field) | ||
358 | { | ||
359 | this.CheckRange(field); | ||
360 | |||
361 | int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field); | ||
362 | if (value == Int32.MinValue) // MSI_NULL_INTEGER | ||
363 | { | ||
364 | return 0; | ||
365 | } | ||
366 | return value; | ||
367 | } | ||
368 | |||
369 | /// <summary> | ||
370 | /// Gets a field value as an integer. | ||
371 | /// </summary> | ||
372 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
373 | /// <returns>Integer value of the field, or 0 if the field is null.</returns> | ||
374 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
375 | /// of the named fields in the Record.</exception> | ||
376 | /// <seealso cref="GetNullableInteger(string)"/> | ||
377 | [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")] | ||
378 | public int GetInteger(string fieldName) | ||
379 | { | ||
380 | int field = this.FindColumn(fieldName); | ||
381 | return this.GetInteger(field); | ||
382 | } | ||
383 | |||
384 | /// <summary> | ||
385 | /// Gets a field value as an integer. | ||
386 | /// </summary> | ||
387 | /// <param name="field">Specifies the field to retrieve.</param> | ||
388 | /// <returns>Integer value of the field, or null if the field is null.</returns> | ||
389 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
390 | /// number of fields in the Record.</exception> | ||
391 | /// <remarks><p> | ||
392 | /// Win32 MSI API: | ||
393 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a> | ||
394 | /// </p></remarks> | ||
395 | /// <seealso cref="GetInteger(int)"/> | ||
396 | public int? GetNullableInteger(int field) | ||
397 | { | ||
398 | this.CheckRange(field); | ||
399 | |||
400 | int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field); | ||
401 | if (value == Int32.MinValue) // MSI_NULL_INTEGER | ||
402 | { | ||
403 | return null; | ||
404 | } | ||
405 | return value; | ||
406 | } | ||
407 | |||
408 | /// <summary> | ||
409 | /// Gets a field value as an integer. | ||
410 | /// </summary> | ||
411 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
412 | /// <returns>Integer value of the field, or null if the field is null.</returns> | ||
413 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
414 | /// of the named fields in the Record.</exception> | ||
415 | /// <seealso cref="GetInteger(string)"/> | ||
416 | public int? GetNullableInteger(string fieldName) | ||
417 | { | ||
418 | int field = this.FindColumn(fieldName); | ||
419 | return this.GetInteger(field); | ||
420 | } | ||
421 | |||
422 | /// <summary> | ||
423 | /// Sets the value of a field to an integer. | ||
424 | /// </summary> | ||
425 | /// <param name="field">Specifies the field to set.</param> | ||
426 | /// <param name="value">new value of the field</param> | ||
427 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
428 | /// number of fields in the Record.</exception> | ||
429 | /// <remarks><p> | ||
430 | /// Win32 MSI API: | ||
431 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a> | ||
432 | /// </p></remarks> | ||
433 | /// <seealso cref="SetNullableInteger(int,int?)"/> | ||
434 | public void SetInteger(int field, int value) | ||
435 | { | ||
436 | this.CheckRange(field); | ||
437 | |||
438 | uint ret = RemotableNativeMethods.MsiRecordSetInteger((int) this.Handle, (uint) field, value); | ||
439 | if (ret != 0) | ||
440 | { | ||
441 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | /// <summary> | ||
446 | /// Sets the value of a field to an integer. | ||
447 | /// </summary> | ||
448 | /// <param name="fieldName">Specifies the field to set.</param> | ||
449 | /// <param name="value">new value of the field</param> | ||
450 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
451 | /// of the named fields in the Record.</exception> | ||
452 | /// <seealso cref="SetNullableInteger(string,int?)"/> | ||
453 | public void SetInteger(string fieldName, int value) | ||
454 | { | ||
455 | int field = this.FindColumn(fieldName); | ||
456 | this.SetInteger(field, value); | ||
457 | } | ||
458 | |||
459 | /// <summary> | ||
460 | /// Sets the value of a field to a nullable integer. | ||
461 | /// </summary> | ||
462 | /// <param name="field">Specifies the field to set.</param> | ||
463 | /// <param name="value">new value of the field</param> | ||
464 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
465 | /// number of fields in the Record.</exception> | ||
466 | /// <remarks><p> | ||
467 | /// Win32 MSI API: | ||
468 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a> | ||
469 | /// </p></remarks> | ||
470 | /// <seealso cref="SetInteger(int,int)"/> | ||
471 | public void SetNullableInteger(int field, int? value) | ||
472 | { | ||
473 | this.CheckRange(field); | ||
474 | |||
475 | uint ret = RemotableNativeMethods.MsiRecordSetInteger( | ||
476 | (int) this.Handle, | ||
477 | (uint) field, | ||
478 | value.HasValue ? (int) value : Int32.MinValue); | ||
479 | if (ret != 0) | ||
480 | { | ||
481 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
482 | } | ||
483 | } | ||
484 | |||
485 | /// <summary> | ||
486 | /// Sets the value of a field to a nullable integer. | ||
487 | /// </summary> | ||
488 | /// <param name="fieldName">Specifies the field to set.</param> | ||
489 | /// <param name="value">new value of the field</param> | ||
490 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
491 | /// of the named fields in the Record.</exception> | ||
492 | /// <seealso cref="SetInteger(string,int)"/> | ||
493 | public void SetNullableInteger(string fieldName, int? value) | ||
494 | { | ||
495 | int field = this.FindColumn(fieldName); | ||
496 | this.SetNullableInteger(field, value); | ||
497 | } | ||
498 | |||
499 | /// <summary> | ||
500 | /// Gets a field value as a string. | ||
501 | /// </summary> | ||
502 | /// <param name="field">Specifies the field to retrieve.</param> | ||
503 | /// <returns>String value of the field, or an empty string if the field is null.</returns> | ||
504 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
505 | /// number of fields in the Record.</exception> | ||
506 | /// <remarks><p> | ||
507 | /// Win32 MSI API: | ||
508 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a> | ||
509 | /// </p></remarks> | ||
510 | public string GetString(int field) | ||
511 | { | ||
512 | this.CheckRange(field); | ||
513 | |||
514 | StringBuilder buf = new StringBuilder(String.Empty); | ||
515 | uint bufSize = 0; | ||
516 | uint ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize); | ||
517 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
518 | { | ||
519 | buf.Capacity = (int) ++bufSize; | ||
520 | ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize); | ||
521 | } | ||
522 | if (ret != 0) | ||
523 | { | ||
524 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
525 | } | ||
526 | return buf.ToString(); | ||
527 | } | ||
528 | |||
529 | /// <summary> | ||
530 | /// Gets a field value as a string. | ||
531 | /// </summary> | ||
532 | /// <param name="fieldName">Specifies the field to retrieve.</param> | ||
533 | /// <returns>String value of the field, or an empty string if the field is null.</returns> | ||
534 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
535 | /// of the named fields in the Record.</exception> | ||
536 | public string GetString(string fieldName) | ||
537 | { | ||
538 | int field = this.FindColumn(fieldName); | ||
539 | return this.GetString(field); | ||
540 | } | ||
541 | |||
542 | /// <summary> | ||
543 | /// Sets the value of a field to a string. | ||
544 | /// </summary> | ||
545 | /// <param name="field">Specifies the field to set.</param> | ||
546 | /// <param name="value">new value of the field</param> | ||
547 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
548 | /// number of fields in the Record.</exception> | ||
549 | /// <remarks><p> | ||
550 | /// Win32 MSI API: | ||
551 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a> | ||
552 | /// </p></remarks> | ||
553 | public void SetString(int field, string value) | ||
554 | { | ||
555 | this.CheckRange(field); | ||
556 | |||
557 | if (value == null) | ||
558 | { | ||
559 | value = String.Empty; | ||
560 | } | ||
561 | |||
562 | uint ret = RemotableNativeMethods.MsiRecordSetString((int) this.Handle, (uint) field, value); | ||
563 | if (ret != 0) | ||
564 | { | ||
565 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
566 | } | ||
567 | |||
568 | // If we set the FormatString manually, then it should be valid again | ||
569 | if (field == 0) | ||
570 | { | ||
571 | this.IsFormatStringInvalid = false; | ||
572 | } | ||
573 | } | ||
574 | |||
575 | /// <summary> | ||
576 | /// Sets the value of a field to a string. | ||
577 | /// </summary> | ||
578 | /// <param name="fieldName">Specifies the field to set.</param> | ||
579 | /// <param name="value">new value of the field</param> | ||
580 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
581 | /// of the named fields in the Record.</exception> | ||
582 | public void SetString(string fieldName, string value) | ||
583 | { | ||
584 | int field = this.FindColumn(fieldName); | ||
585 | this.SetString(field, value); | ||
586 | } | ||
587 | |||
588 | /// <summary> | ||
589 | /// Reads a record stream field into a file. | ||
590 | /// </summary> | ||
591 | /// <param name="field">Specifies the field of the Record to get.</param> | ||
592 | /// <param name="filePath">Specifies the path to the file to contain the stream.</param> | ||
593 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
594 | /// number of fields in the Record.</exception> | ||
595 | /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open | ||
596 | /// in read-write mode, or from a database without an associated file path</exception> | ||
597 | /// <remarks><p> | ||
598 | /// This method is capable of directly extracting substorages. To do so, first select both the | ||
599 | /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. | ||
600 | /// However, substorages may only be extracted from a database that is open in read-only mode. | ||
601 | /// </p><p> | ||
602 | /// Win32 MSI API: | ||
603 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a> | ||
604 | /// </p></remarks> | ||
605 | public void GetStream(int field, string filePath) | ||
606 | { | ||
607 | if (String.IsNullOrEmpty(filePath)) | ||
608 | { | ||
609 | throw new ArgumentNullException("filePath"); | ||
610 | } | ||
611 | |||
612 | IList<TableInfo> tables = (this.view != null ? this.view.Tables : null); | ||
613 | if (tables != null && tables.Count == 1 && tables[0].Name == "_Storages" && field == this.FindColumn("Data")) | ||
614 | { | ||
615 | if (!this.view.Database.IsReadOnly) | ||
616 | { | ||
617 | throw new NotSupportedException("Database must be opened read-only to support substorage extraction."); | ||
618 | } | ||
619 | else if (this.view.Database.FilePath == null) | ||
620 | { | ||
621 | throw new NotSupportedException("Database must have an associated file path to support substorage extraction."); | ||
622 | } | ||
623 | else if (this.FindColumn("Name") <= 0) | ||
624 | { | ||
625 | throw new NotSupportedException("Name column must be part of the Record in order to extract substorage."); | ||
626 | } | ||
627 | else | ||
628 | { | ||
629 | Record.ExtractSubStorage(this.view.Database.FilePath, this.GetString("Name"), filePath); | ||
630 | } | ||
631 | } | ||
632 | else | ||
633 | { | ||
634 | if (!this.IsNull(field)) | ||
635 | { | ||
636 | Stream readStream = null, writeStream = null; | ||
637 | try | ||
638 | { | ||
639 | readStream = new RecordStream(this, field); | ||
640 | writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); | ||
641 | int count = 512; | ||
642 | byte[] buf = new byte[count]; | ||
643 | while (count == buf.Length) | ||
644 | { | ||
645 | if ((count = readStream.Read(buf, 0, buf.Length)) > 0) | ||
646 | { | ||
647 | writeStream.Write(buf, 0, count); | ||
648 | } | ||
649 | } | ||
650 | } | ||
651 | finally | ||
652 | { | ||
653 | if (readStream != null) readStream.Close(); | ||
654 | if (writeStream != null) writeStream.Close(); | ||
655 | } | ||
656 | } | ||
657 | } | ||
658 | } | ||
659 | |||
660 | /// <summary> | ||
661 | /// Reads a record stream field into a file. | ||
662 | /// </summary> | ||
663 | /// <param name="fieldName">Specifies the field of the Record to get.</param> | ||
664 | /// <param name="filePath">Specifies the path to the file to contain the stream.</param> | ||
665 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
666 | /// of the named fields in the Record.</exception> | ||
667 | /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open | ||
668 | /// in read-write mode, or from a database without an associated file path</exception> | ||
669 | /// <remarks><p> | ||
670 | /// This method is capable of directly extracting substorages. To do so, first select both the | ||
671 | /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. | ||
672 | /// However, substorages may only be extracted from a database that is open in read-only mode. | ||
673 | /// </p></remarks> | ||
674 | public void GetStream(string fieldName, string filePath) | ||
675 | { | ||
676 | int field = this.FindColumn(fieldName); | ||
677 | this.GetStream(field, filePath); | ||
678 | } | ||
679 | |||
680 | /// <summary> | ||
681 | /// Gets a record stream field. | ||
682 | /// </summary> | ||
683 | /// <param name="field">Specifies the field of the Record to get.</param> | ||
684 | /// <returns>A Stream that reads the field data.</returns> | ||
685 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
686 | /// number of fields in the Record.</exception> | ||
687 | /// <remarks><p> | ||
688 | /// This method is not capable of reading substorages. To extract a substorage, | ||
689 | /// use <see cref="GetStream(int,string)"/>. | ||
690 | /// </p><p> | ||
691 | /// Win32 MSI API: | ||
692 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a> | ||
693 | /// </p></remarks> | ||
694 | public Stream GetStream(int field) | ||
695 | { | ||
696 | this.CheckRange(field); | ||
697 | |||
698 | return this.IsNull(field) ? null : new RecordStream(this, field); | ||
699 | } | ||
700 | |||
701 | /// <summary> | ||
702 | /// Gets a record stream field. | ||
703 | /// </summary> | ||
704 | /// <param name="fieldName">Specifies the field of the Record to get.</param> | ||
705 | /// <returns>A Stream that reads the field data.</returns> | ||
706 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
707 | /// of the named fields in the Record.</exception> | ||
708 | /// <remarks><p> | ||
709 | /// This method is not capable of reading substorages. To extract a substorage, | ||
710 | /// use <see cref="GetStream(string,string)"/>. | ||
711 | /// </p></remarks> | ||
712 | public Stream GetStream(string fieldName) | ||
713 | { | ||
714 | int field = this.FindColumn(fieldName); | ||
715 | return this.GetStream(field); | ||
716 | } | ||
717 | |||
718 | /// <summary> | ||
719 | /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. | ||
720 | /// </summary> | ||
721 | /// <param name="field">Specifies the field of the Record to set.</param> | ||
722 | /// <param name="filePath">Specifies the path to the file containing the stream.</param> | ||
723 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
724 | /// number of fields in the Record.</exception> | ||
725 | /// <remarks><p> | ||
726 | /// The contents of the specified file are read into a stream object. The stream persists if | ||
727 | /// the Record is inserted into the Database and the Database is committed. | ||
728 | /// </p><p> | ||
729 | /// To reset the stream to its beginning you must pass in null for filePath. | ||
730 | /// Do not pass an empty string, "", to reset the stream. | ||
731 | /// </p><p> | ||
732 | /// Setting a stream with this method is more efficient than setting a field to a | ||
733 | /// FileStream object. | ||
734 | /// </p><p> | ||
735 | /// Win32 MSI API: | ||
736 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a> | ||
737 | /// </p></remarks> | ||
738 | public void SetStream(int field, string filePath) | ||
739 | { | ||
740 | this.CheckRange(field); | ||
741 | |||
742 | if (String.IsNullOrEmpty(filePath)) | ||
743 | { | ||
744 | throw new ArgumentNullException("filePath"); | ||
745 | } | ||
746 | |||
747 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, filePath); | ||
748 | if (ret != 0) | ||
749 | { | ||
750 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
751 | } | ||
752 | } | ||
753 | |||
754 | /// <summary> | ||
755 | /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. | ||
756 | /// </summary> | ||
757 | /// <param name="fieldName">Specifies the field name of the Record to set.</param> | ||
758 | /// <param name="filePath">Specifies the path to the file containing the stream.</param> | ||
759 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
760 | /// of the named fields in the Record.</exception> | ||
761 | /// <remarks><p> | ||
762 | /// The contents of the specified file are read into a stream object. The stream persists if | ||
763 | /// the Record is inserted into the Database and the Database is committed. | ||
764 | /// To reset the stream to its beginning you must pass in null for filePath. | ||
765 | /// Do not pass an empty string, "", to reset the stream. | ||
766 | /// </p><p> | ||
767 | /// Setting a stream with this method is more efficient than setting a field to a | ||
768 | /// FileStream object. | ||
769 | /// </p></remarks> | ||
770 | public void SetStream(string fieldName, string filePath) | ||
771 | { | ||
772 | int field = this.FindColumn(fieldName); | ||
773 | this.SetStream(field, filePath); | ||
774 | } | ||
775 | |||
776 | /// <summary> | ||
777 | /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. | ||
778 | /// </summary> | ||
779 | /// <param name="field">Specifies the field of the Record to set.</param> | ||
780 | /// <param name="stream">Specifies the stream data.</param> | ||
781 | /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the | ||
782 | /// number of fields in the Record.</exception> | ||
783 | /// <remarks><p> | ||
784 | /// The stream persists if the Record is inserted into the Database and the Database is committed. | ||
785 | /// </p><p> | ||
786 | /// Win32 MSI API: | ||
787 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a> | ||
788 | /// </p></remarks> | ||
789 | public void SetStream(int field, Stream stream) | ||
790 | { | ||
791 | this.CheckRange(field); | ||
792 | |||
793 | if (stream == null) | ||
794 | { | ||
795 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, null); | ||
796 | if (ret != 0) | ||
797 | { | ||
798 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
799 | } | ||
800 | } | ||
801 | else | ||
802 | { | ||
803 | Stream writeStream = null; | ||
804 | string tempPath = Path.GetTempFileName(); | ||
805 | try | ||
806 | { | ||
807 | writeStream = new FileStream(tempPath, FileMode.Truncate, FileAccess.Write); | ||
808 | byte[] buf = new byte[512]; | ||
809 | int count; | ||
810 | while ((count = stream.Read(buf, 0, buf.Length)) > 0) | ||
811 | { | ||
812 | writeStream.Write(buf, 0, count); | ||
813 | } | ||
814 | writeStream.Close(); | ||
815 | writeStream = null; | ||
816 | |||
817 | uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, tempPath); | ||
818 | if (ret != 0) | ||
819 | { | ||
820 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
821 | } | ||
822 | } | ||
823 | finally | ||
824 | { | ||
825 | if (writeStream != null) writeStream.Close(); | ||
826 | if (File.Exists(tempPath)) | ||
827 | { | ||
828 | try | ||
829 | { | ||
830 | File.Delete(tempPath); | ||
831 | } | ||
832 | catch (IOException) | ||
833 | { | ||
834 | if (this.view != null) | ||
835 | { | ||
836 | this.view.Database.DeleteOnClose(tempPath); | ||
837 | } | ||
838 | } | ||
839 | } | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | |||
844 | /// <summary> | ||
845 | /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. | ||
846 | /// </summary> | ||
847 | /// <param name="fieldName">Specifies the field name of the Record to set.</param> | ||
848 | /// <param name="stream">Specifies the stream data.</param> | ||
849 | /// <exception cref="ArgumentOutOfRangeException">The field name does not match any | ||
850 | /// of the named fields in the Record.</exception> | ||
851 | /// <remarks><p> | ||
852 | /// The stream persists if the Record is inserted into the Database and the Database is committed. | ||
853 | /// </p></remarks> | ||
854 | public void SetStream(string fieldName, Stream stream) | ||
855 | { | ||
856 | int field = this.FindColumn(fieldName); | ||
857 | this.SetStream(field, stream); | ||
858 | } | ||
859 | |||
860 | /// <summary> | ||
861 | /// Gets a formatted string representation of the Record. | ||
862 | /// </summary> | ||
863 | /// <returns>A formatted string representation of the Record.</returns> | ||
864 | /// <remarks><p> | ||
865 | /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. | ||
866 | /// </p><p> | ||
867 | /// Win32 MSI API: | ||
868 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
869 | /// </p></remarks> | ||
870 | /// <seealso cref="FormatString"/> | ||
871 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
872 | public override string ToString() | ||
873 | { | ||
874 | return this.ToString((IFormatProvider) null); | ||
875 | } | ||
876 | |||
877 | /// <summary> | ||
878 | /// Gets a formatted string representation of the Record, optionally using a Session to format properties. | ||
879 | /// </summary> | ||
880 | /// <param name="provider">an optional Session instance that will be used to lookup any | ||
881 | /// properties in the Record's format string</param> | ||
882 | /// <returns>A formatted string representation of the Record.</returns> | ||
883 | /// <remarks><p> | ||
884 | /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. | ||
885 | /// </p><p> | ||
886 | /// Win32 MSI API: | ||
887 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
888 | /// </p></remarks> | ||
889 | /// <seealso cref="FormatString"/> | ||
890 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
891 | public string ToString(IFormatProvider provider) | ||
892 | { | ||
893 | if (this.IsFormatStringInvalid) // Format string is invalid | ||
894 | { | ||
895 | // TODO: return all values by default? | ||
896 | return String.Empty; | ||
897 | } | ||
898 | |||
899 | InstallerHandle session = provider as InstallerHandle; | ||
900 | int sessionHandle = session != null ? (int) session.Handle : 0; | ||
901 | StringBuilder buf = new StringBuilder(String.Empty); | ||
902 | uint bufSize = 1; | ||
903 | uint ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize); | ||
904 | if (ret == (uint) NativeMethods.Error.MORE_DATA) | ||
905 | { | ||
906 | bufSize++; | ||
907 | buf = new StringBuilder((int) bufSize); | ||
908 | ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize); | ||
909 | } | ||
910 | if (ret != 0) | ||
911 | { | ||
912 | throw InstallerException.ExceptionFromReturnCode(ret); | ||
913 | } | ||
914 | return buf.ToString(); | ||
915 | } | ||
916 | |||
917 | /// <summary> | ||
918 | /// Gets a formatted string representation of the Record. | ||
919 | /// </summary> | ||
920 | /// <param name="format">String to be used to format the data in the Record, | ||
921 | /// instead of the Record's format string.</param> | ||
922 | /// <returns>A formatted string representation of the Record.</returns> | ||
923 | /// <remarks><p> | ||
924 | /// Win32 MSI API: | ||
925 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
926 | /// </p></remarks> | ||
927 | [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " + | ||
928 | "property separately before calling the ToString() override that takes no parameters.")] | ||
929 | public string ToString(string format) | ||
930 | { | ||
931 | return this.ToString(format, null); | ||
932 | } | ||
933 | |||
934 | /// <summary> | ||
935 | /// Gets a formatted string representation of the Record, optionally using a Session to format properties. | ||
936 | /// </summary> | ||
937 | /// <param name="format">String to be used to format the data in the Record, | ||
938 | /// instead of the Record's format string.</param> | ||
939 | /// <param name="provider">an optional Session instance that will be used to lookup any | ||
940 | /// properties in the Record's format string</param> | ||
941 | /// <returns>A formatted string representation of the Record.</returns> | ||
942 | /// <remarks><p> | ||
943 | /// Win32 MSI API: | ||
944 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a> | ||
945 | /// </p></remarks> | ||
946 | /// <seealso cref="FormatString"/> | ||
947 | /// <seealso cref="Session.FormatRecord(Record)"/> | ||
948 | [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " + | ||
949 | "property separately before calling the ToString() override that takes just a format provider.")] | ||
950 | public string ToString(string format, IFormatProvider provider) | ||
951 | { | ||
952 | if (format == null) | ||
953 | { | ||
954 | return this.ToString(provider); | ||
955 | } | ||
956 | else if (format.Length == 0) | ||
957 | { | ||
958 | return String.Empty; | ||
959 | } | ||
960 | else | ||
961 | { | ||
962 | string savedFormatString = (string) this[0]; | ||
963 | try | ||
964 | { | ||
965 | this.FormatString = format; | ||
966 | return this.ToString(provider); | ||
967 | } | ||
968 | finally | ||
969 | { | ||
970 | this.FormatString = savedFormatString; | ||
971 | } | ||
972 | } | ||
973 | } | ||
974 | |||
975 | [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] | ||
976 | private static void ExtractSubStorage(string databaseFile, string storageName, string extractFile) | ||
977 | { | ||
978 | IStorage storage; | ||
979 | NativeMethods.STGM openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_DENY_WRITE; | ||
980 | int hr = NativeMethods.StgOpenStorage(databaseFile, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0, out storage); | ||
981 | if (hr != 0) | ||
982 | { | ||
983 | Marshal.ThrowExceptionForHR(hr); | ||
984 | } | ||
985 | |||
986 | try | ||
987 | { | ||
988 | openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_EXCLUSIVE; | ||
989 | IStorage subStorage = storage.OpenStorage(storageName, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0); | ||
990 | |||
991 | try | ||
992 | { | ||
993 | IStorage newStorage; | ||
994 | openMode = NativeMethods.STGM.CREATE | NativeMethods.STGM.READWRITE | NativeMethods.STGM.SHARE_EXCLUSIVE; | ||
995 | hr = NativeMethods.StgCreateDocfile(extractFile, (uint) openMode, 0, out newStorage); | ||
996 | if (hr != 0) | ||
997 | { | ||
998 | Marshal.ThrowExceptionForHR(hr); | ||
999 | } | ||
1000 | |||
1001 | try | ||
1002 | { | ||
1003 | subStorage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, newStorage); | ||
1004 | |||
1005 | newStorage.Commit(0); | ||
1006 | } | ||
1007 | finally | ||
1008 | { | ||
1009 | Marshal.ReleaseComObject(newStorage); | ||
1010 | } | ||
1011 | } | ||
1012 | finally | ||
1013 | { | ||
1014 | Marshal.ReleaseComObject(subStorage); | ||
1015 | } | ||
1016 | } | ||
1017 | finally | ||
1018 | { | ||
1019 | Marshal.ReleaseComObject(storage); | ||
1020 | } | ||
1021 | } | ||
1022 | |||
1023 | private int FindColumn(string fieldName) | ||
1024 | { | ||
1025 | if (this.view == null) | ||
1026 | { | ||
1027 | throw new InvalidOperationException(); | ||
1028 | } | ||
1029 | ColumnCollection columns = this.view.Columns; | ||
1030 | for (int i = 0; i < columns.Count; i++) | ||
1031 | { | ||
1032 | if (columns[i].Name == fieldName) | ||
1033 | { | ||
1034 | return i + 1; | ||
1035 | } | ||
1036 | } | ||
1037 | throw new ArgumentOutOfRangeException("fieldName"); | ||
1038 | } | ||
1039 | |||
1040 | private void CheckRange(int field) | ||
1041 | { | ||
1042 | if (field < 0 || field > this.FieldCount) | ||
1043 | { | ||
1044 | throw new ArgumentOutOfRangeException("field"); | ||
1045 | } | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||