Datei: NDODLL/PersistenceManager.cs
Last Commit (01941c7)
1 | // |
2 | // Copyright (c) 2002-2024 Mirko Matytschak |
3 | // (www.netdataobjects.de) |
4 | // |
5 | // Author: Mirko Matytschak |
6 | // |
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
8 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation |
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
10 | // Software, and to permit persons to whom the Software is furnished to do so, subject to the following |
11 | // conditions: |
12 | |
13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions |
14 | // of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
19 | // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
20 | // DEALINGS IN THE SOFTWARE. |
21 | |
22 | |
23 | using System; |
24 | using System.Text; |
25 | using System.IO; |
26 | using System.Collections; |
27 | using System.Collections.Generic; |
28 | using System.Data; |
29 | using System.Diagnostics; |
30 | using System.Reflection; |
31 | using System.Text.RegularExpressions; |
32 | using System.Linq; |
33 | using System.Xml.Linq; |
34 | |
35 | using NDO.Mapping; |
36 | using NDOInterfaces; |
37 | using NDO.ShortId; |
38 | using System.Globalization; |
39 | using NDO.Linq; |
40 | using NDO.Query; |
41 | using NDO.ChangeLogging; |
42 | using Microsoft.Extensions.DependencyInjection; |
43 | using Microsoft.Extensions.Logging; |
44 | |
45 | namespace NDO |
46 | { |
47 | ····/// <summary> |
48 | ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager. |
49 | ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/> |
50 | ····/// </summary> |
51 | ····public delegate void CollisionHandler(object o); |
52 | ····/// <summary> |
53 | ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager. |
54 | ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/> |
55 | ····/// </summary> |
56 | ····public delegate void IdGenerationHandler(Type t, ObjectId oid); |
57 | ····/// <summary> |
58 | ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager. |
59 | ····/// </summary> |
60 | ····public delegate void OnSavingHandler(ICollection l); |
61 | ····/// <summary> |
62 | ····/// Delegate type for the OnSavedEvent. |
63 | ····/// </summary> |
64 | ····/// <param name="auditSet"></param> |
65 | ····public delegate void OnSavedHandler(AuditSet auditSet); |
66 | |
67 | ····/// <summary> |
68 | ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid. |
69 | ····/// </summary> |
70 | ····/// <param name="pc"></param> |
71 | ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns> |
72 | ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks> |
73 | ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc ); |
74 | |
75 | ····/// <summary> |
76 | ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets. |
77 | ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation. |
78 | ····/// </summary> |
79 | ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager |
80 | ····{········ |
81 | ········private bool hollowMode = false; |
82 | ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers |
83 | |
84 | ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations |
85 | ········private ObjectLock removeLock = new ObjectLock(); |
86 | ········private ObjectLock addLock = new ObjectLock(); |
87 | ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys. |
88 | ········// List of created objects that use mapping table and need to be stored in mapping table |
89 | ········// after they have been stored to the database to update foreign keys. |
90 | ········private ArrayList createdMappingTableObjects = new ArrayList();·· |
91 | ········private TypeManager typeManager; |
92 | ········internal bool DeferredMode { get; private set; } |
93 | ········private INDOTransactionScope transactionScope; |
94 | ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········ |
95 | |
96 | ········private OpenConnectionListener openConnectionListener; |
97 | |
98 | ········/// <summary> |
99 | ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps. |
100 | ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation. |
101 | ········/// </summary> |
102 | ········public event CollisionHandler CollisionEvent; |
103 | |
104 | ········/// <summary> |
105 | ········/// Register a listener to this event to handle situations where LoadData doesn't find an object. |
106 | ········/// The listener can determine, whether an exception should be thrown, if the situation occurs. |
107 | ········/// </summary> |
108 | ········public event ObjectNotPresentHandler ObjectNotPresentEvent; |
109 | |
110 | ········/// <summary> |
111 | ········/// Register a listener to this event, if you want to be notified about the end |
112 | ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed |
113 | ········/// during the transaction and are to be saved or deleted. |
114 | ········/// </summary> |
115 | ········public event OnSavingHandler OnSavingEvent; |
116 | ········/// <summary> |
117 | ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects. |
118 | ········/// </summary> |
119 | ········public event OnSavedHandler OnSavedEvent; |
120 | ········ |
121 | ········private const string hollowMarker = "Hollow"; |
122 | ········private byte[] encryptionKey; |
123 | ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>(); |
124 | ········private bool isClosing = false; |
125 | |
126 | ········/// <summary> |
127 | ········/// Gets a list of structures which represent relation changes, i.e. additions and removals |
128 | ········/// </summary> |
129 | ········protected internal List<RelationChangeRecord> RelationChanges |
130 | ········{ |
131 | ············get { return this.relationChanges; } |
132 | ········} |
133 | |
134 | ········/// <summary> |
135 | ········/// Initializes a new PersistenceManager instance. |
136 | ········/// </summary> |
137 | ········/// <param name="mappingFileName"></param> |
138 | ········protected override void Init(string mappingFileName) |
139 | ········{ |
140 | ············try |
141 | ············{ |
142 | ················base.Init(mappingFileName); |
143 | ············} |
144 | ············catch (Exception ex) |
145 | ············{ |
146 | ················if (ex is NDOException) |
147 | ····················throw; |
148 | ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString()); |
149 | ············} |
150 | |
151 | ········} |
152 | |
153 | ········/// <summary> |
154 | ········/// Initializes the persistence manager |
155 | ········/// </summary> |
156 | ········/// <remarks> |
157 | ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase. |
158 | ········/// </remarks> |
159 | ········/// <param name="mapping"></param> |
160 | ········internal override void Init( Mappings mapping ) |
161 | ········{ |
162 | ············base.Init( mapping ); |
163 | |
164 | ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this; |
165 | |
166 | ············string dir = Path.GetDirectoryName( mapping.FileName ); |
167 | |
168 | ············string typesFile = Path.Combine( dir, "NDOTypes.xml" ); |
169 | ············typeManager = new TypeManager( typesFile, this.mappings ); |
170 | |
171 | ············sm = new StateManager( this ); |
172 | |
173 | ············InitClasses(); |
174 | ········} |
175 | |
176 | |
177 | ········/// <summary> |
178 | ········/// Standard Constructor. |
179 | ········/// </summary> |
180 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
181 | ········/// <remarks> |
182 | ········/// Searches for a mapping file in the application directory. |
183 | ········/// The constructor tries to find a file with the same name as |
184 | ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a |
185 | ········/// file called AssemblyName.ndo.mapping in the application directory. |
186 | ········/// </remarks> |
187 | ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider ) |
188 | ········{ |
189 | ········} |
190 | |
191 | ········/// <summary> |
192 | ········/// Loads the mapping file from the specified location. This allows to use |
193 | ········/// different mapping files with different classes mapped in it. |
194 | ········/// </summary> |
195 | ········/// <param name="mappingFile">Path to the mapping file.</param> |
196 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
197 | ········/// <remarks>Only the Professional and Enterprise |
198 | ········/// Editions can handle more than one mapping file.</remarks> |
199 | ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider) |
200 | ········{ |
201 | ········} |
202 | |
203 | ········/// <summary> |
204 | ········/// Constructs a PersistenceManager and reuses a cached NDOMapping. |
205 | ········/// </summary> |
206 | ········/// <param name="mapping">The cached mapping object</param> |
207 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
208 | ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider) |
209 | ········{ |
210 | ········} |
211 | |
212 | ········#region Object Container Stuff |
213 | ········/// <summary> |
214 | ········/// Gets a container of all loaded objects and tries to load all child objects, |
215 | ········/// which are reachable through composite relations. |
216 | ········/// </summary> |
217 | ········/// <returns>An ObjectContainer object.</returns> |
218 | ········/// <remarks> |
219 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
220 | ········/// Persistent, or Transient. |
221 | ········/// The transfer format is binary. |
222 | ········/// </remarks> |
223 | ········public ObjectContainer GetObjectContainer() |
224 | ········{ |
225 | ············IList l = this.cache.AllObjects; |
226 | ············foreach(IPersistenceCapable pc in l) |
227 | ············{ |
228 | ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty) |
229 | ················{ |
230 | ····················if (Logger != null) |
231 | ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." ); |
232 | ················} |
233 | ············} |
234 | |
235 | ············ObjectContainer oc = new ObjectContainer(); |
236 | ············oc.AddList(l); |
237 | ············return oc; |
238 | ········} |
239 | |
240 | ········/// <summary> |
241 | ········/// Returns a container of all objects provided in the objects list and searches for |
242 | ········/// child objects according to the serFlags. |
243 | ········/// </summary> |
244 | ········/// <param name="objects">The list of the root objects to add to the container.</param> |
245 | ········/// <returns>An ObjectContainer object.</returns> |
246 | ········/// <remarks> |
247 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
248 | ········/// Persistent, or Transient. |
249 | ········/// </remarks> |
250 | ········public ObjectContainer GetObjectContainer(IList objects) |
251 | ········{ |
252 | ············foreach(object o in objects) |
253 | ············{ |
254 | ················CheckPc(o); |
255 | ················IPersistenceCapable pc = o as IPersistenceCapable; |
256 | ················if (pc.NDOObjectState == NDOObjectState.Hollow) |
257 | ····················LoadData(pc); |
258 | ············} |
259 | ············ObjectContainer oc = new ObjectContainer(); |
260 | ············oc.AddList(objects); |
261 | ············return oc; |
262 | ········} |
263 | |
264 | |
265 | ········/// <summary> |
266 | ········/// Returns a container containing the provided object |
267 | ········/// and tries to load all child objects |
268 | ········/// reachable through composite relations. |
269 | ········/// </summary> |
270 | ········/// <param name="obj">The object to be added to the container.</param> |
271 | ········/// <returns>An ObjectContainer object.</returns> |
272 | ········/// <remarks> |
273 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
274 | ········/// Persistent, or Transient. |
275 | ········/// The transfer format is binary. |
276 | ········/// </remarks> |
277 | ········public ObjectContainer GetObjectContainer(Object obj) |
278 | ········{ |
279 | ············CheckPc(obj); |
280 | ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow) |
281 | ················LoadData(obj); |
282 | ············ObjectContainer oc = new ObjectContainer(); |
283 | ············oc.AddObject(obj); |
284 | ············return oc; |
285 | ········} |
286 | |
287 | ········/// <summary> |
288 | ········/// Merges an object container to the active objects in the pm. All changes and the state |
289 | ········/// of the objects will be taken over by the pm. |
290 | ········/// </summary> |
291 | ········/// <remarks> |
292 | ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer. |
293 | ········/// The flag MarkAsTransient can be used to perform a kind |
294 | ········/// of object based replication using the ObjectContainer class. |
295 | ········/// Objects, which are persistent at one machine, can be transfered |
296 | ········/// to a second machine and treated by the receiving PersistenceManager like a newly created |
297 | ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole |
298 | ········/// transient object tree. |
299 | ········/// There is one difference to freshly created objects: If an object id exists, it will be |
300 | ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered |
301 | ········/// oids will be reused by the receiving PersistenceManager. |
302 | ········/// </remarks> |
303 | ········/// <param name="ocb">The object container to be merged.</param> |
304 | ········public void MergeObjectContainer(ObjectContainerBase ocb) |
305 | ········{ |
306 | ············ChangeSetContainer csc = ocb as ChangeSetContainer; |
307 | ············if (csc != null) |
308 | ············{ |
309 | ················MergeChangeSet(csc); |
310 | ················return; |
311 | ············} |
312 | ············ObjectContainer oc = ocb as ObjectContainer; |
313 | ············if (oc != null) |
314 | ············{ |
315 | ················InternalMergeObjectContainer(oc); |
316 | ················return; |
317 | ············} |
318 | ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter."); |
319 | ········} |
320 | |
321 | |
322 | ········void InternalMergeObjectContainer(ObjectContainer oc) |
323 | ········{ |
324 | ············// TODO: Check, if other states are useful. Find use scenarios. |
325 | ············foreach(IPersistenceCapable pc in oc.RootObjects) |
326 | ············{ |
327 | ················if (pc.NDOObjectState == NDOObjectState.Transient) |
328 | ····················MakePersistent(pc); |
329 | ············} |
330 | ············foreach(IPersistenceCapable pc in oc.RootObjects) |
331 | ············{ |
332 | ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc); |
333 | ············} |
334 | ········} |
335 | |
336 | ········void MergeChangeSet(ChangeSetContainer cs) |
337 | ········{ |
338 | ············foreach(IPersistenceCapable pc in cs.AddedObjects) |
339 | ············{ |
340 | ················InternalMakePersistent(pc, false); |
341 | ············} |
342 | ············foreach(ObjectId oid in cs.DeletedObjects) |
343 | ············{ |
344 | ················IPersistenceCapable pc2 = FindObject(oid); |
345 | ················Delete(pc2); |
346 | ············} |
347 | ············foreach(IPersistenceCapable pc in cs.ChangedObjects) |
348 | ············{ |
349 | ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId); |
350 | ················Class pcClass = GetClass(pc); |
351 | ················// Make sure, the object is loaded. |
352 | ················if (pc2.NDOObjectState == NDOObjectState.Hollow) |
353 | ····················LoadData(pc2); |
354 | ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row |
355 | ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId ); |
356 | ················DataRow row = entry.row; |
357 | ················pc.NDOWrite(row, pcClass.ColumnNames, 0); |
358 | ················pc2.NDORead(row, pcClass.ColumnNames, 0); |
359 | ············} |
360 | ············foreach(RelationChangeRecord rcr in cs.RelationChanges) |
361 | ············{ |
362 | ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId); |
363 | ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId); |
364 | ················Class pcClass = GetClass(parent); |
365 | ················Relation r = pcClass.FindRelation(rcr.RelationName); |
366 | ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal]) |
367 | ····················LoadRelation(parent, r, true); |
368 | ················if (rcr.IsAdded) |
369 | ················{ |
370 | ····················InternalAddRelatedObject(parent, r, child, true); |
371 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
372 | ····················{ |
373 | ························mappings.SetRelationField(parent, r.FieldName, child); |
374 | ····················} |
375 | ····················else |
376 | ····················{ |
377 | ························IList l = mappings.GetRelationContainer(parent, r); |
378 | ························l.Add(child); |
379 | ····················} |
380 | ················} |
381 | ················else |
382 | ················{ |
383 | ····················RemoveRelatedObject(parent, r.FieldName, child); |
384 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
385 | ····················{ |
386 | ························mappings.SetRelationField(parent, r.FieldName, null); |
387 | ····················} |
388 | ····················else |
389 | ····················{ |
390 | ························IList l = mappings.GetRelationContainer(parent, r); |
391 | ························try |
392 | ························{ |
393 | ····························ObjectListManipulator.Remove(l, child); |
394 | ························} |
395 | ························catch |
396 | ························{ |
397 | ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName); |
398 | ························} |
399 | ····················} |
400 | ················} |
401 | ············} |
402 | |
403 | ········} |
404 | ········#endregion |
405 | |
406 | ········#region Implementation of IPersistenceManager |
407 | |
408 | ········// Complete documentation can be found in IPersistenceManager |
409 | |
410 | |
411 | ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row) |
412 | ········{ |
413 | ············if (!cl.Oid.IsDependent) |
414 | ················return; |
415 | ············WriteForeignKeysToRow(pc, row); |
416 | ········} |
417 | |
418 | ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations) |
419 | ········{ |
420 | ············// Object is now under control of the state manager |
421 | ············pc.NDOStateManager = sm; |
422 | |
423 | ············Type pcType = pc.GetType(); |
424 | ············Class pcClass = GetClass(pc); |
425 | |
426 | ············// Create new object |
427 | ············DataTable dt = GetTable(pcType); |
428 | ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value |
429 | |
430 | ············// In case of a Guid oid the value will be computed now. |
431 | ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns) |
432 | ············{ |
433 | ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null) |
434 | ················{ |
435 | ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string)) |
436 | ························row[oidColumn.Name] = Guid.NewGuid().ToString(); |
437 | ····················else |
438 | ························row[oidColumn.Name] = Guid.NewGuid(); |
439 | ················} |
440 | ············} |
441 | |
442 | ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS |
443 | |
444 | ············// If the object is merged from an ObjectContainer, the id should be reused, |
445 | ············// if the id is client generated (not Autoincremented). |
446 | ············// In every other case, the oid is set to null, to force generating a new oid. |
447 | ············bool fireIdGeneration = (Object)pc.NDOObjectId == null; |
448 | ············if ((object)pc.NDOObjectId != null) |
449 | ············{ |
450 | ················bool hasAutoincrement = false; |
451 | ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns) |
452 | ················{ |
453 | ····················if (oidColumn.AutoIncremented) |
454 | ····················{ |
455 | ························hasAutoincrement = true; |
456 | ························break; |
457 | ····················} |
458 | ················} |
459 | ················if (hasAutoincrement) // can't store existing id |
460 | ················{ |
461 | ····················pc.NDOObjectId = null; |
462 | ····················fireIdGeneration = true; |
463 | ················} |
464 | ············} |
465 | |
466 | ············// In case of a dependent class the oid has to be read from the fields according to the relations |
467 | ············WriteDependentForeignKeysToRow(pc, pcClass, row); |
468 | |
469 | ············if ((object)pc.NDOObjectId == null) |
470 | ············{ |
471 | ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager); |
472 | ············} |
473 | |
474 | ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data |
475 | ············{ |
476 | ················if (fireIdGeneration) |
477 | ····················FireIdGenerationEvent(pcType, pc.NDOObjectId); |
478 | ················// At this place the oid might have been |
479 | ················// - deserialized (MergeObjectContainer) |
480 | ················// - created using NewObjectId |
481 | ················// - defined by the IdGenerationEvent |
482 | |
483 | ················// At this point we have a valid oid. |
484 | ················// If the object has a field mapped to the oid we have |
485 | ················// to write back the oid to the field |
486 | ················int i = 0; |
487 | ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement) |
488 | ················{ |
489 | ····················if (oidColumn.FieldName != null) |
490 | ····················{ |
491 | ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance); |
492 | ························fi.SetValue(pc, pc.NDOObjectId.Id[i]); |
493 | ····················} |
494 | ····················i++; |
495 | ················}); |
496 | |
497 | |
498 | |
499 | ················// Now write back the data into the row |
500 | ················pc.NDOObjectId.Id.ToRow(pcClass, row); |
501 | ············} |
502 | |
503 | ············ |
504 | ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment |
505 | ············dt.Rows.Add(row); |
506 | |
507 | ············cache.Register(pc); |
508 | |
509 | ············// new object that has never been written to the DS |
510 | ············pc.NDOObjectState = NDOObjectState.Created; |
511 | ············// Mark all Relations as loaded |
512 | ············SetRelationState(pc); |
513 | |
514 | ············if (checkRelations) |
515 | ············{ |
516 | ················// Handle related objects: |
517 | ················foreach(Relation r in pcClass.Relations) |
518 | ················{ |
519 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
520 | ····················{ |
521 | ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
522 | ························if(child != null) |
523 | ························{ |
524 | ····························AddRelatedObject(pc, r, child); |
525 | ························} |
526 | ····················} |
527 | ····················else |
528 | ····················{ |
529 | ························IList list = mappings.GetRelationContainer(pc, r); |
530 | ························if(list != null) |
531 | ························{ |
532 | ····························foreach(IPersistenceCapable relObj in list) |
533 | ····························{ |
534 | ································if (relObj != null) |
535 | ····································AddRelatedObject(pc, r, relObj); |
536 | ····························} |
537 | ························} |
538 | ····················} |
539 | ················} |
540 | ············} |
541 | |
542 | ············var relations··= CollectRelationStates(pc); |
543 | ············cache.Lock(pc, row, relations); |
544 | ········} |
545 | |
546 | |
547 | ········/// <summary> |
548 | ········/// Make an object persistent. |
549 | ········/// </summary> |
550 | ········/// <param name="o">the transient object that should be made persistent</param> |
551 | ········public void MakePersistent(object o) |
552 | ········{ |
553 | ············IPersistenceCapable pc = CheckPc(o); |
554 | |
555 | ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name); |
556 | ············//Debug.Indent(); |
557 | |
558 | ············if (pc.NDOObjectState != NDOObjectState.Transient) |
559 | ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump()); |
560 | |
561 | ············InternalMakePersistent(pc, true); |
562 | |
563 | ········} |
564 | |
565 | |
566 | |
567 | ········//········/// <summary> |
568 | ········//········/// Checks, if an object has a valid id, which was created by the database |
569 | ········//········/// </summary> |
570 | ········//········/// <param name="pc"></param> |
571 | ········//········/// <returns></returns> |
572 | ········//········private bool HasValidId(IPersistenceCapable pc) |
573 | ········//········{ |
574 | ········//············if (this.IdGenerationEvent != null) |
575 | ········//················return true; |
576 | ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient); |
577 | ········//········} |
578 | |
579 | |
580 | ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent) |
581 | ········{ |
582 | ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here. |
583 | ············if(r.HasSubclasses) |
584 | ············{ |
585 | ················// we don't support 1:n with foreign fields in subclasses because we would have to |
586 | ················// search for objects in all subclasses! Instead use a mapping table. |
587 | ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName); |
588 | ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table"); |
589 | ············} |
590 | |
591 | ············if (!makeRelObjPersistent) |
592 | ················MarkDirty(relObj); |
593 | ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn |
594 | ············DataRow relObjRow = this.cache.GetDataRow(relObj); |
595 | |
596 | ············if (relObjRow == null) |
597 | ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null"); |
598 | |
599 | ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow); |
600 | |
601 | ············if (relObj.NDOLoadState.LostRowInfo == null) |
602 | ············{ |
603 | ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow); |
604 | ············} |
605 | ············else |
606 | ············{ |
607 | ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id); |
608 | ············} |
609 | ········} |
610 | |
611 | ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj) |
612 | ········{ |
613 | ············switch(relObj.NDOObjectState) |
614 | ············{ |
615 | ················case NDOObjectState.Persistent: |
616 | ····················MarkDirty(relObj); |
617 | ····················break; |
618 | ················case NDOObjectState.Hollow: |
619 | ····················LoadData(relObj); |
620 | ····················MarkDirty(relObj); |
621 | ····················break; |
622 | ············} |
623 | |
624 | ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
625 | ············{ |
626 | ················IPersistenceCapable newpc; |
627 | ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null) |
628 | ················{ |
629 | ····················if (newpc != pc) |
630 | ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump()); |
631 | ················} |
632 | ················else |
633 | ················{ |
634 | ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc); |
635 | ················} |
636 | ············} |
637 | ············else |
638 | ············{ |
639 | ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal)) |
640 | ····················LoadRelation(relObj, r.ForeignRelation, true); |
641 | ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation); |
642 | ················if(l == null) |
643 | ················{ |
644 | ····················try |
645 | ····················{ |
646 | ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation); |
647 | ····················} |
648 | ····················catch |
649 | ····················{ |
650 | ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor."); |
651 | ····················} |
652 | ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l); |
653 | ················} |
654 | ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen. |
655 | ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so. |
656 | ················if (!ObjectListManipulator.Contains(l, pc)) |
657 | ····················l.Add(pc); |
658 | ············} |
659 | ············//AddRelatedObject(relObj, r.ForeignRelation, pc); |
660 | ········} |
661 | |
662 | |
663 | ········/// <summary> |
664 | ········/// Add a related object to the specified object. |
665 | ········/// </summary> |
666 | ········/// <param name="pc">the parent object</param> |
667 | ········/// <param name="fieldName">the field name of the relation</param> |
668 | ········/// <param name="relObj">the related object that should be added</param> |
669 | ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj) |
670 | ········{ |
671 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient); |
672 | ············Relation r = mappings.FindRelation(pc, fieldName); |
673 | ············AddRelatedObject(pc, r, relObj); |
674 | ········} |
675 | |
676 | ········/// <summary> |
677 | ········/// Core functionality to add an object to a relation container or relation field. |
678 | ········/// </summary> |
679 | ········/// <param name="pc"></param> |
680 | ········/// <param name="r"></param> |
681 | ········/// <param name="relObj"></param> |
682 | ········/// <param name="isMerging"></param> |
683 | ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging) |
684 | ········{ |
685 | ············ |
686 | ············// avoid recursion |
687 | ············if (!addLock.GetLock(relObj)) |
688 | ················return; |
689 | |
690 | ············try |
691 | ············{ |
692 | ················//TODO: We need a relation management, which is independent of |
693 | ················//the state management of an object. Currently the relation |
694 | ················//lists or elements are cached for restore, if an object is marked dirty. |
695 | ················//Thus we have to mark dirty our parent object in any case at the moment. |
696 | ················MarkDirty(pc); |
697 | |
698 | ················//We should mark pc as dirty if we have a 1:1 w/o mapping table |
699 | ················//We should mark relObj as dirty if we have a 1:n w/o mapping table |
700 | ················//The latter happens in CreateAddedObjectRow |
701 | |
702 | ················Class relClass = GetClass(relObj); |
703 | |
704 | ················if (r.Multiplicity == RelationMultiplicity.Element |
705 | ····················&& r.HasSubclasses |
706 | ····················&& r.MappingTable == null················ |
707 | ····················&& !this.HasOwnerCreatedIds |
708 | ····················&& GetClass(pc).Oid.HasAutoincrementedColumn |
709 | ····················&& !relClass.HasGuidOid) |
710 | ················{ |
711 | ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient )) |
712 | ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table."); |
713 | ····················if (r.Composition) |
714 | ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's."); |
715 | ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient) |
716 | ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." ); |
717 | ················} |
718 | |
719 | ················bool isDependent = relClass.Oid.IsDependent; |
720 | |
721 | ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent) |
722 | ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List."); |
723 | |
724 | ················// Need to patch pc into the relation relObj->pc, because |
725 | ················// the oid is built on base of this information |
726 | ················if (isDependent) |
727 | ················{ |
728 | ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass); |
729 | ················} |
730 | |
731 | ················if (r.Composition || isDependent) |
732 | ················{ |
733 | ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient) |
734 | ························MakePersistent(relObj); |
735 | ················} |
736 | |
737 | ················if(r.MappingTable == null) |
738 | ················{ |
739 | ····················if (r.Bidirectional) |
740 | ····················{ |
741 | ························// This object hasn't been saved yet, so the key is wrong. |
742 | ························// Therefore, the child must be written twice to update the foreign key. |
743 | #if trace |
744 | ························System.Text.StringBuilder sb = new System.Text.StringBuilder(); |
745 | ························if (r.Multiplicity == RelationMultiplicity.Element) |
746 | ····························sb.Append("1"); |
747 | ························else |
748 | ····························sb.Append("n"); |
749 | ························sb.Append(":"); |
750 | ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
751 | ····························sb.Append("1"); |
752 | ························else |
753 | ····························sb.Append("n"); |
754 | ························sb.Append ("OwnCreatedOther"); |
755 | ························sb.Append(relObj.NDOObjectState.ToString()); |
756 | ························sb.Append(' '); |
757 | |
758 | ························sb.Append(types[0].ToString()); |
759 | ························sb.Append(' '); |
760 | ························sb.Append(types[1].ToString()); |
761 | ························Debug.WriteLine(sb.ToString()); |
762 | #endif |
763 | ························//························if (r.Multiplicity == RelationMultiplicity.Element |
764 | ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
765 | ························//························{ |
766 | ························// Element means: |
767 | ························// pc is keyholder |
768 | ························// -> relObj is saved first |
769 | ························// -> UpdateOrder(pc) > UpdateOrder(relObj) |
770 | ························// Both are Created - use type sort order |
771 | ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient) |
772 | ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn) |
773 | ························{ |
774 | ····························if (mappings.GetUpdateOrder(pc.GetType()) |
775 | ································< mappings.GetUpdateOrder(relObj.GetType())) |
776 | ································createdDirectObjects.Add(pc); |
777 | ····························else |
778 | ································createdDirectObjects.Add( relObj ); |
779 | ························} |
780 | ····················} |
781 | ····················if (r.Multiplicity == RelationMultiplicity.List) |
782 | ····················{ |
783 | ························CreateAddedObjectRow(pc, r, relObj, r.Composition); |
784 | ····················} |
785 | ················} |
786 | ················else |
787 | ················{ |
788 | ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r)); |
789 | ················} |
790 | ················if(r.Bidirectional) |
791 | ················{ |
792 | ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null) |
793 | ····················{ |
794 | ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element ) |
795 | ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc); |
796 | ····················} |
797 | ····················else if ( !addLock.IsLocked( pc ) ) |
798 | ····················{ |
799 | ························PatchForeignRelation( pc, r, relObj ); |
800 | ····················} |
801 | ················} |
802 | |
803 | ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) ); |
804 | ············} |
805 | ············finally |
806 | ············{ |
807 | ················addLock.Unlock(relObj); |
808 | ················//Debug.Unindent(); |
809 | ············} |
810 | ········} |
811 | |
812 | ········/// <summary> |
813 | ········/// Returns an integer value which determines the rank of the given type in the update order list. |
814 | ········/// </summary> |
815 | ········/// <param name="t">The type to determine the update order.</param> |
816 | ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns> |
817 | ········/// <remarks> |
818 | ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code. |
819 | ········/// </remarks> |
820 | ········public int GetUpdateRank(Type t) |
821 | ········{ |
822 | ············return mappings.GetUpdateOrder(t); |
823 | ········} |
824 | |
825 | ········/// <summary> |
826 | ········/// Add a related object to the specified object. |
827 | ········/// </summary> |
828 | ········/// <param name="pc">the parent object</param> |
829 | ········/// <param name="r">the relation mapping info</param> |
830 | ········/// <param name="relObj">the related object that should be added</param> |
831 | ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj) |
832 | ········{ |
833 | ············//············string idstr; |
834 | ············//············if (relObj.NDOObjectId == null) |
835 | ············//················idstr = relObj.GetType().ToString(); |
836 | ············//············else |
837 | ············//················idstr = relObj.NDOObjectId.Dump(); |
838 | ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr); |
839 | ············//Debug.Indent(); |
840 | |
841 | ············Class relClass = GetClass(relObj); |
842 | ············bool isDependent = relClass.Oid.IsDependent; |
843 | |
844 | ············// Do some checks to guarantee that the assignment is correct |
845 | ············if(r.Composition) |
846 | ············{ |
847 | ················if(relObj.NDOObjectState != NDOObjectState.Transient) |
848 | ················{ |
849 | ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + "."); |
850 | ················} |
851 | ············} |
852 | ············else |
853 | ············{ |
854 | ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent) |
855 | ················{ |
856 | ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + "."); |
857 | ················} |
858 | ············} |
859 | |
860 | ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType())) |
861 | ············{ |
862 | ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType()); |
863 | ············} |
864 | |
865 | ············InternalAddRelatedObject(pc, r, relObj, false); |
866 | |
867 | ········} |
868 | |
869 | ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass) |
870 | ········{ |
871 | ············// Need to patch pc into the relation relObj->pc, because |
872 | ············// the oid is built on base of this information |
873 | ············// The second relation has to be set before adding relObj |
874 | ············// to the relation list. |
875 | ············PatchForeignRelation(pc, r, relObj); |
876 | ············IPersistenceCapable parent; |
877 | ············foreach (Relation oidRelation in relClass.Oid.Relations) |
878 | ············{ |
879 | ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName); |
880 | ················if (parent == null) |
881 | ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects."); |
882 | ················if (parent.NDOObjectState == NDOObjectState.Transient) |
883 | ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent."); |
884 | |
885 | ············} |
886 | ········} |
887 | |
888 | |
889 | ········/// <summary> |
890 | ········/// Remove a related object from the specified object. |
891 | ········/// </summary> |
892 | ········/// <param name="pc">the parent object</param> |
893 | ········/// <param name="fieldName">Field name of the relation</param> |
894 | ········/// <param name="relObj">the related object that should be removed</param> |
895 | ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj) |
896 | ········{ |
897 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient); |
898 | ············Relation r = mappings.FindRelation(pc, fieldName); |
899 | ············InternalRemoveRelatedObject(pc, r, relObj, true); |
900 | ········} |
901 | |
902 | ········/// <summary> |
903 | ········/// Registers a listener which will be notified, if a new connection is opened. |
904 | ········/// </summary> |
905 | ········/// <param name="listener">Delegate of a listener function</param> |
906 | ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks> |
907 | ········public virtual void RegisterConnectionListener(OpenConnectionListener listener) |
908 | ········{ |
909 | ············this.openConnectionListener = listener; |
910 | ········} |
911 | |
912 | ········internal string OnNewConnection(NDO.Mapping.Connection conn) |
913 | ········{ |
914 | ············if (openConnectionListener != null) |
915 | ················return openConnectionListener(conn); |
916 | ············return conn.Name; |
917 | ········} |
918 | |
919 | |
920 | ········/* |
921 | ········doCommit should be: |
922 | ········ |
923 | ····················Query····Save····Save(true) |
924 | ········Optimistic····1········1········0 |
925 | ········Pessimistic····0········1········0 |
926 | ············ |
927 | ········Deferred Mode············ |
928 | ····················Query····Save····Save(true) |
929 | ········Optimistic····0········1········0 |
930 | ········Pessimistic····0········1········0 |
931 | ········ */ |
932 | |
933 | ········internal void CheckEndTransaction(bool doCommit) |
934 | ········{ |
935 | ············if (doCommit) |
936 | ············{ |
937 | ················TransactionScope.Complete(); |
938 | ············} |
939 | ········} |
940 | |
941 | ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t) |
942 | ········{ |
943 | ············CheckTransaction(handler, this.GetClass(t).Connection); |
944 | ········} |
945 | |
946 | ········/// <summary> |
947 | ········/// Each and every database operation has to be preceded by a call to this function. |
948 | ········/// </summary> |
949 | ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn ) |
950 | ········{ |
951 | ············TransactionScope.CheckTransaction(); |
952 | ············ |
953 | ············if (handler.Connection == null) |
954 | ············{ |
955 | ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () => |
956 | ················{ |
957 | ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn ); |
958 | ····················string connStr = this.OnNewConnection( ndoConn ); |
959 | ····················var connection = p.NewConnection( connStr ); |
960 | ····················if (connection == null) |
961 | ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." ); |
962 | ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" ); |
963 | ····················return connection; |
964 | ················} ); |
965 | ············} |
966 | |
967 | ············if (TransactionMode != TransactionMode.None) |
968 | ············{ |
969 | ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID ); |
970 | ············} |
971 | |
972 | ············// During the tests, we work with a handler mock that always returns zero for the Connection property. |
973 | ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open) |
974 | ············{ |
975 | ················handler.Connection.Open(); |
976 | ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" ); |
977 | ············} |
978 | ········} |
979 | |
980 | ········/// <summary> |
981 | ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler. |
982 | ········/// We try to tell the object which caused the concurrency exception, that a collicion occured. |
983 | ········/// This is possible if there is a listener for the CollisionEvent. |
984 | ········/// Else we throw an exception. |
985 | ········/// </summary> |
986 | ········/// <param name="ex">Concurrency Exception which was catched during update.</param> |
987 | ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex) |
988 | ········{ |
989 | ············DataRow row = ex.Row; |
990 | ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0) |
991 | ················throw(ex); |
992 | ············if (row.RowState == DataRowState.Detached) |
993 | ················return; |
994 | ············foreach (Cache.Entry e in cache.LockedObjects) |
995 | ············{ |
996 | ················if (e.row == row) |
997 | ················{ |
998 | ····················CollisionEvent(e.pc); |
999 | ····················return; |
1000 | ················} |
1001 | ············} |
1002 | ············throw ex; |
1003 | ········} |
1004 | |
1005 | |
1006 | ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex) |
1007 | ········{ |
1008 | ············Class cl = GetClass(pc); |
1009 | ············string[] etypes = cl.EmbeddedTypes.ToArray(); |
1010 | ············Dictionary<string,MemberInfo> persistentFields = null; |
1011 | ············if (etypes.Length > 0) |
1012 | ············{ |
1013 | ················FieldMap fm = new FieldMap(cl); |
1014 | ················persistentFields = fm.PersistentFields; |
1015 | ············} |
1016 | ············foreach(string s in etypes) |
1017 | ············{ |
1018 | ················try |
1019 | ················{ |
1020 | ····················NDO.Mapping.Field f = cl.FindField(s); |
1021 | ····················if (f == null) |
1022 | ························continue; |
1023 | ····················object o = row[f.Column.Name]; |
1024 | ····················string[] arr = s.Split('.'); |
1025 | ····················// Suche Embedded Type-Feld mit Namen arr[0] |
1026 | ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType()); |
1027 | ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance); |
1028 | ····················// Hole das Embedded Object |
1029 | ····················object parentOb = parentFi.GetValue(pc); |
1030 | |
1031 | ····················if (parentOb == null) |
1032 | ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0])); |
1033 | |
1034 | ····················// Suche darin das Feld mit Namen Arr[1] |
1035 | |
1036 | ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance); |
1037 | ····················Type childType = childFi.FieldType; |
1038 | |
1039 | ····················// Don't initialize value types, if DBNull is stored in the field. |
1040 | ····················// Exception: DateTime and Guid. |
1041 | ····················if (o == DBNull.Value && childType.IsValueType |
1042 | ························&& childType != typeof(Guid) |
1043 | ························&& childType != typeof(DateTime)) |
1044 | ························continue; |
1045 | |
1046 | ····················if (childType == typeof(DateTime)) |
1047 | ····················{ |
1048 | ························if (o == DBNull.Value) |
1049 | ····························o = DateTime.MinValue; |
1050 | ····················} |
1051 | ····················if (childType.IsClass) |
1052 | ····················{ |
1053 | ························if (o == DBNull.Value) |
1054 | ····························o = null; |
1055 | ····················} |
1056 | |
1057 | ····················if (childType == typeof (Guid)) |
1058 | ····················{ |
1059 | ························if (o == DBNull.Value) |
1060 | ····························o = Guid.Empty; |
1061 | ························if (o is string) |
1062 | ························{ |
1063 | ····························childFi.SetValue(parentOb, new Guid((string)o)); |
1064 | ························} |
1065 | ························else if (o is Guid) |
1066 | ························{ |
1067 | ····························childFi.SetValue(parentOb, o); |
1068 | ························} |
1069 | ························else if (o is byte[]) |
1070 | ························{ |
1071 | ····························childFi.SetValue(parentOb, new Guid((byte[])o)); |
1072 | ························} |
1073 | ························else |
1074 | ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName)); |
1075 | ····················} |
1076 | ····················else if (childType.IsSubclassOf(typeof(System.Enum))) |
1077 | ····················{ |
1078 | ························object childOb = childFi.GetValue(parentOb); |
1079 | ························FieldInfo valueFi = childType.GetField("value__"); |
1080 | ························valueFi.SetValue(childOb, o); |
1081 | ························childFi.SetValue(parentOb, childOb); |
1082 | ····················} |
1083 | ····················else |
1084 | ····················{ |
1085 | ························childFi.SetValue(parentOb, o); |
1086 | ····················} |
1087 | ················} |
1088 | ················catch (Exception ex) |
1089 | ················{ |
1090 | ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}"; |
1091 | |
1092 | ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message)); |
1093 | ················} |
1094 | |
1095 | ············} |
1096 | ············ |
1097 | ············try |
1098 | ············{ |
1099 | ················if (cl.HasEncryptedFields) |
1100 | ················{ |
1101 | ····················foreach (var field in cl.Fields.Where( f => f.Encrypted )) |
1102 | ····················{ |
1103 | ························string name = field.Column.Name; |
1104 | ························string s = (string) row[name]; |
1105 | ························string es = AesHelper.Decrypt( s, EncryptionKey ); |
1106 | ························row[name] = es; |
1107 | ····················} |
1108 | ················} |
1109 | ················pc.NDORead(row, fieldNames, startIndex); |
1110 | ············} |
1111 | ············catch (Exception ex) |
1112 | ············{ |
1113 | ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n" |
1114 | ····················+ ex.Message); |
1115 | ············} |
1116 | ········} |
1117 | |
1118 | ········/// <summary> |
1119 | ········/// Executes a sql script to generate the database tables. |
1120 | ········/// The function will execute any sql statements in the script |
1121 | ········/// which are valid according to the |
1122 | ········/// rules of the underlying database. Result sets are ignored. |
1123 | ········/// </summary> |
1124 | ········/// <param name="scriptFile">The script file to execute.</param> |
1125 | ········/// <param name="conn">A connection object, containing the connection |
1126 | ········/// string to the database, which should be altered.</param> |
1127 | ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns> |
1128 | ········/// <remarks> |
1129 | ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown. |
1130 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1131 | ········/// Their message property will appear in the result array. |
1132 | ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1133 | ········/// </remarks> |
1134 | ········public string[] BuildDatabase( string scriptFile, Connection conn ) |
1135 | ········{ |
1136 | ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 ); |
1137 | ········} |
1138 | |
1139 | ········/// <summary> |
1140 | ········/// Executes a sql script to generate the database tables. |
1141 | ········/// The function will execute any sql statements in the script |
1142 | ········/// which are valid according to the |
1143 | ········/// rules of the underlying database. Result sets are ignored. |
1144 | ········/// </summary> |
1145 | ········/// <param name="scriptFile">The script file to execute.</param> |
1146 | ········/// <param name="conn">A connection object, containing the connection |
1147 | ········/// string to the database, which should be altered.</param> |
1148 | ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param> |
1149 | ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns> |
1150 | ········/// <remarks> |
1151 | ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown. |
1152 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1153 | ········/// Their message property will appear in the result array. |
1154 | ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1155 | ········/// </remarks> |
1156 | ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding) |
1157 | ········{ |
1158 | ············StreamReader sr = new StreamReader(scriptFile, encoding); |
1159 | ············string s = sr.ReadToEnd(); |
1160 | ············sr.Close(); |
1161 | ············string[] arr = s.Split(';'); |
1162 | ············string last = arr[arr.Length - 1]; |
1163 | ············bool lastInvalid = (last == null || last.Trim() == string.Empty); |
1164 | ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)]; |
1165 | ············using (var handler = GetSqlPassThroughHandler()) |
1166 | ············{ |
1167 | ················int i = 0; |
1168 | ················string ok = "OK"; |
1169 | ················foreach (string statement in arr) |
1170 | ················{ |
1171 | ····················if (statement != null && statement.Trim() != string.Empty) |
1172 | ····················{ |
1173 | ························try |
1174 | ························{ |
1175 | ····························handler.Execute(statement); |
1176 | ····························result[i] = ok; |
1177 | ························} |
1178 | ························catch (Exception ex) |
1179 | ························{ |
1180 | ····························result[i] = ex.Message; |
1181 | ························} |
1182 | ····················} |
1183 | ····················i++; |
1184 | ················} |
1185 | ················CheckEndTransaction(true); |
1186 | ············} |
1187 | ············return result; |
1188 | ········} |
1189 | |
1190 | ········/// <summary> |
1191 | ········/// Executes a sql script to generate the database tables. |
1192 | ········/// The function will execute any sql statements in the script |
1193 | ········/// which are valid according to the |
1194 | ········/// rules of the underlying database. Result sets are ignored. |
1195 | ········/// </summary> |
1196 | ········/// <param name="scriptFile">The script file to execute.</param> |
1197 | ········/// <returns></returns> |
1198 | ········/// <remarks> |
1199 | ········/// This function takes the first Connection object in the Connections list |
1200 | ········/// of the Mapping file und executes the script using that connection. |
1201 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1202 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1203 | ········/// Their message property will appear in the result array. |
1204 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1205 | ········/// </remarks> |
1206 | ········public string[] BuildDatabase(string scriptFile) |
1207 | ········{ |
1208 | ············if (!File.Exists(scriptFile)) |
1209 | ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist."); |
1210 | ············if (!this.mappings.Connections.Any()) |
1211 | ················throw new NDOException(48, "Mapping file doesn't define a connection."); |
1212 | ············Connection conn = new Connection( this.mappings ); |
1213 | ············Connection originalConnection = (Connection)this.mappings.Connections.First(); |
1214 | ············conn.Name = OnNewConnection( originalConnection ); |
1215 | ············conn.Type = originalConnection.Type; |
1216 | ············//Connection conn = (Connection) this.mappings.Connections[0]; |
1217 | ············return BuildDatabase(scriptFile, conn); |
1218 | ········} |
1219 | |
1220 | ········/// <summary> |
1221 | ········/// Executes a sql script to generate the database tables. |
1222 | ········/// The function will execute any sql statements in the script |
1223 | ········/// which are valid according to the |
1224 | ········/// rules of the underlying database. Result sets are ignored. |
1225 | ········/// </summary> |
1226 | ········/// <returns> |
1227 | ········/// A string array, containing the error messages produced by the statements |
1228 | ········/// contained in the script. |
1229 | ········/// </returns> |
1230 | ········/// <remarks> |
1231 | ········/// The sql script is assumed to be the executable name of the entry assembly with the |
1232 | ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script. |
1233 | ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown. |
1234 | ········/// This function takes the first Connection object in the Connections list |
1235 | ········/// of the Mapping file und executes the script using that connection. |
1236 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1237 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1238 | ········/// Their message property will appear in the result array. |
1239 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1240 | ········/// </remarks> |
1241 | ········public string[] BuildDatabase() |
1242 | ········{ |
1243 | ············Assembly ass = Assembly.GetEntryAssembly(); |
1244 | ············if (ass == null) |
1245 | ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase."); |
1246 | ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql"); |
1247 | ············return BuildDatabase(file); |
1248 | ········} |
1249 | |
1250 | ········/// <summary> |
1251 | ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements. |
1252 | ········/// </summary> |
1253 | ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param> |
1254 | ········/// <returns>An ISqlPassThroughHandler implementation</returns> |
1255 | ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null ) |
1256 | ········{ |
1257 | ············if (!this.mappings.Connections.Any()) |
1258 | ················throw new NDOException( 48, "Mapping file doesn't define a connection." ); |
1259 | ············if (conn == null) |
1260 | ············{ |
1261 | ················conn = new Connection( this.mappings ); |
1262 | ················Connection originalConnection = (Connection) this.mappings.Connections.First(); |
1263 | ················conn.Name = OnNewConnection( originalConnection ); |
1264 | ················conn.Type = originalConnection.Type; |
1265 | ············} |
1266 | |
1267 | ············return new SqlPassThroughHandler( this, conn ); |
1268 | ········} |
1269 | |
1270 | ········/// <summary> |
1271 | ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements. |
1272 | ········/// </summary> |
1273 | ········/// <param name="predicate">A predicate defining which connection has to be used.</param> |
1274 | ········/// <returns>An ISqlPassThroughHandler implementation</returns> |
1275 | ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate ) |
1276 | ········{ |
1277 | ············if (!this.mappings.Connections.Any()) |
1278 | ················throw new NDOException( 48, "The Mapping file doesn't define a connection." ); |
1279 | ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate ); |
1280 | ············if (conn == null) |
1281 | ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." ); |
1282 | ············return GetSqlPassThroughHandler( conn ); |
1283 | ········} |
1284 | |
1285 | ········/// <summary> |
1286 | ········/// Executes a xml script to generate the database tables. |
1287 | ········/// The function will generate and execute sql statements to perform |
1288 | ········/// the changes described by the xml. |
1289 | ········/// </summary> |
1290 | ········/// <returns></returns> |
1291 | ········/// <remarks> |
1292 | ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml. |
1293 | ········/// If several files match the search string biggest file name in the default sort order will be executed. |
1294 | ········/// This function takes the first Connection object in the Connections list |
1295 | ········/// of the Mapping file und executes the script using that connection. |
1296 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1297 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1298 | ········/// Their message property will appear in the result string array. |
1299 | ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown. |
1300 | ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry. |
1301 | ········/// </remarks> |
1302 | ········public string[] PerformSchemaTransitions() |
1303 | ········{ |
1304 | ············Assembly ass = Assembly.GetEntryAssembly(); |
1305 | ············if (ass == null) |
1306 | ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions."); |
1307 | ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml"; |
1308 | ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList(); |
1309 | ············if (fileNames.Count == 0) |
1310 | ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) }; |
1311 | ············if (fileNames.Count > 1) |
1312 | ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) ); |
1313 | ············return PerformSchemaTransitions( fileNames[0] ); |
1314 | ········} |
1315 | |
1316 | |
1317 | ········/// <summary> |
1318 | ········/// Executes a xml script to generate the database tables. |
1319 | ········/// The function will generate and execute sql statements to perform |
1320 | ········/// the changes described by the xml. |
1321 | ········/// </summary> |
1322 | ········/// <param name="scriptFile">The script file to execute.</param> |
1323 | ········/// <returns></returns> |
1324 | ········/// <remarks> |
1325 | ········/// This function takes the first Connection object in the Connections list |
1326 | ········/// of the Mapping file und executes the script using that connection. |
1327 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1328 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1329 | ········/// Their message property will appear in the result string array. |
1330 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1331 | ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry. |
1332 | ········/// </remarks> |
1333 | ········public string[] PerformSchemaTransitions(string scriptFile) |
1334 | ········{ |
1335 | ············if (!File.Exists(scriptFile)) |
1336 | ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist."); |
1337 | |
1338 | ············if (!this.mappings.Connections.Any()) |
1339 | ················throw new NDOException(48, "Mapping file doesn't define any connection."); |
1340 | ············Connection conn = new Connection( mappings ); |
1341 | ············Connection originalConnection = mappings.Connections.First(); |
1342 | ············conn.Name = OnNewConnection( originalConnection ); |
1343 | ············conn.Type = originalConnection.Type; |
1344 | ············return PerformSchemaTransitions(scriptFile, conn); |
1345 | ········} |
1346 | |
1347 | |
1348 | ········int CompareFileName( string fn1, string fn2) |
1349 | ········{ |
1350 | ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" ); |
1351 | ············Match match = regex.Match( fn1 ); |
1352 | ············string v1 = match.Groups[1].Value; |
1353 | ············match = regex.Match( fn2 ); |
1354 | ············string v2 = match.Groups[1].Value; |
1355 | ············return new Version( v2 ).CompareTo( new Version( v1 ) ); |
1356 | ········} |
1357 | |
1358 | ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider) |
1359 | ········{ |
1360 | ············var connection = provider.NewConnection( ndoConn.Name ); |
1361 | ············var resultList = new List<Guid>(); |
1362 | |
1363 | ············using (var handler = GetSqlPassThroughHandler()) |
1364 | ············{ |
1365 | ················string[] TableNames = provider.GetTableNames( connection ); |
1366 | ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 )) |
1367 | ················{ |
1368 | ····················var schemaIds = provider.GetQualifiedTableName("NDOSchemaIds"); |
1369 | ····················var sn = provider.GetQuotedName("SchemaName"); |
1370 | ····················var id = provider.GetQuotedName("Id"); |
1371 | ····················string sql = $"SELECT {id} from {schemaIds} WHERE {sn} "; |
1372 | ····················if (String.IsNullOrEmpty(schemaName)) |
1373 | ························sql += "IS NULL;"; |
1374 | ····················else |
1375 | ························sql += $"LIKE '{schemaName}'"; |
1376 | |
1377 | ····················using(IDataReader dr = handler.Execute(sql, true)) |
1378 | ····················{ |
1379 | ························while (dr.Read()) |
1380 | ····························resultList.Add( dr.GetGuid( 0 ) ); |
1381 | ····················} |
1382 | ················} |
1383 | ················else |
1384 | ················{ |
1385 | ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings ); |
1386 | ····················var gt = typeof(Guid); |
1387 | ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }"; |
1388 | ····················var st = typeof(String); |
1389 | ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }"; |
1390 | ····················var dt = typeof(DateTime); |
1391 | ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }"; |
1392 | ····················string transition = $@"<NdoSchemaTransition> |
1393 | ····<CreateTable name=""NDOSchemaIds""> |
1394 | ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" /> |
1395 | ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" isPrimary=""True"" /> |
1396 | ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" /> |
1397 | ····</CreateTable> |
1398 | </NdoSchemaTransition>"; |
1399 | ····················XElement transitionElement = XElement.Parse(transition); |
1400 | |
1401 | ····················string sql = schemaTransitionGenerator.Generate( transitionElement ); |
1402 | ····················handler.Execute(sql); |
1403 | ················} |
1404 | ················handler.CommitTransaction(); |
1405 | ············} |
1406 | |
1407 | ············return resultList.ToArray(); |
1408 | ········} |
1409 | |
1410 | ········/// <summary> |
1411 | ········/// Executes a xml script to generate the database tables. |
1412 | ········/// The function will generate and execute sql statements to perform |
1413 | ········/// the changes described by the xml. |
1414 | ········/// </summary> |
1415 | ········/// <param name="scriptFile">The xml script file.</param> |
1416 | ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param> |
1417 | ········/// <returns>A list of strings about the states of the different schema change commands.</returns> |
1418 | ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks> |
1419 | ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn) |
1420 | ········{ |
1421 | ············string schemaName = null; |
1422 | ············// Gespeicherte Version ermitteln. |
1423 | ············XElement transitionElements = XElement.Load( scriptFile ); |
1424 | ············if (transitionElements.Attribute( "schemaName" ) != null) |
1425 | ················schemaName = transitionElements.Attribute( "schemaName" ).Value; |
1426 | |
1427 | ············IProvider provider = this.mappings.GetProvider( ndoConn ); |
1428 | ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider ); |
1429 | ············var newIds = new List<Guid>(); |
1430 | ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings ); |
1431 | MemoryStream ms = new MemoryStream( ) ; |
1432 | StreamWriter sw = new StreamWriter( ms, Encoding. UTF8) ; |
1433 | bool hasChanges = false; |
1434 | |
1435 | foreach ( XElement transitionElement in transitionElements. Elements( "NdoSchemaTransition") ) |
1436 | ············{ |
1437 | ················var id = transitionElement.Attribute("id")?.Value; |
1438 | ················if (id == null) |
1439 | ····················continue; |
1440 | ················var gid = new Guid(id); |
1441 | ················if (installedIds.Contains( gid )) |
1442 | ····················continue; |
1443 | hasChanges = true; |
1444 | sw. WriteLine( schemaTransitionGenerator. Generate( transitionElement ) ) ; |
1445 | ················newIds.Add( gid ); |
1446 | ············} |
1447 | |
1448 | if ( !hasChanges) |
1449 | ················return new string[] { }; |
1450 | |
1451 | ············// dtLiteral contains the leading and trailing quotes |
1452 | ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now ); |
1453 | |
1454 | var ndoSchemaIds = provider. GetQualifiedTableName( "NDOSchemaIds") ; |
1455 | ············var schName = provider.GetQuotedName("SchemaName"); |
1456 | ············var idCol = provider.GetQuotedName("Id"); |
1457 | ············var insertTime = provider.GetQuotedName("InsertTime"); |
1458 | ············foreach (var tid in newIds) |
1459 | ············{ |
1460 | ················sw.WriteLine( $"INSERT INTO {ndoSchemaIds} ({schName},{idCol},{insertTime}) VALUES ('{schemaName}','{tid}',{dtLiteral});" ); |
1461 | ············} |
1462 | |
1463 | sw. Flush( ) ; |
1464 | ············ms.Position = 0L; |
1465 | |
1466 | ············StreamReader sr = new StreamReader(ms, Encoding.UTF8); |
1467 | ············string s = sr.ReadToEnd(); |
1468 | ············sr.Close(); |
1469 | |
1470 | ············return InternalPerformSchemaTransitions( ndoConn, s ); |
1471 | ········} |
1472 | |
1473 | ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql ) |
1474 | ········{ |
1475 | ············string[] arr = sql.Split( ';' ); |
1476 | |
1477 | ············string last = arr[arr.Length - 1]; |
1478 | ············bool lastInvalid = (last == null || last.Trim() == string.Empty); |
1479 | ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)]; |
1480 | ············int i = 0; |
1481 | ············string ok = "OK"; |
1482 | ············using (var handler = GetSqlPassThroughHandler()) |
1483 | ············{ |
1484 | ················handler.BeginTransaction(); |
1485 | ················var doCommit = true; |
1486 | ················foreach (string statement in arr) |
1487 | ················{ |
1488 | ····················if (!String.IsNullOrWhiteSpace(statement)) |
1489 | ····················{ |
1490 | ························try |
1491 | ························{ |
1492 | ····························handler.Execute( statement.Trim() ); |
1493 | ····························result[i] = ok; |
1494 | ························} |
1495 | ························catch (Exception ex) |
1496 | ························{ |
1497 | ····························result[i] = ex.Message; |
1498 | ····························doCommit = false; |
1499 | ························} |
1500 | ····················} |
1501 | ····················i++; |
1502 | ················} |
1503 | ················if (doCommit) |
1504 | ····················handler.CommitTransaction(); |
1505 | ················else |
1506 | ····················AbortTransaction(); |
1507 | ············} |
1508 | |
1509 | ············return result; |
1510 | ········} |
1511 | ········ |
1512 | ········/// <summary> |
1513 | ········/// Transfers Data from the object to the DataRow |
1514 | ········/// </summary> |
1515 | ········/// <param name="pc"></param> |
1516 | ········/// <param name="row"></param> |
1517 | ········/// <param name="fieldNames"></param> |
1518 | ········/// <param name="startIndex"></param> |
1519 | ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex) |
1520 | ········{ |
1521 | ············Class cl = GetClass( pc ); |
1522 | ············try |
1523 | ············{ |
1524 | ················pc.NDOWrite(row, fieldNames, startIndex); |
1525 | ················if (cl.HasEncryptedFields) |
1526 | ················{ |
1527 | ····················foreach (var field in cl.Fields.Where( f => f.Encrypted )) |
1528 | ····················{ |
1529 | ························string name = field.Column.Name; |
1530 | ························string s = (string) row[name]; |
1531 | ························string es = AesHelper.Encrypt( s, EncryptionKey ); |
1532 | ························row[name] = es; |
1533 | ····················} |
1534 | ················} |
1535 | ············} |
1536 | ············catch (Exception ex) |
1537 | ············{ |
1538 | ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n" |
1539 | ····················+ ex.Message); |
1540 | ············} |
1541 | |
1542 | ············if (cl.TypeNameColumn != null) |
1543 | ············{ |
1544 | ················Type t = pc.GetType(); |
1545 | ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name; |
1546 | ············} |
1547 | |
1548 | ············var etypes = cl.EmbeddedTypes; |
1549 | ············foreach(string s in etypes) |
1550 | ············{ |
1551 | ················try |
1552 | ················{ |
1553 | ····················NDO.Mapping.Field f = cl.FindField(s); |
1554 | ····················if (f == null) |
1555 | ························continue; |
1556 | ····················string[] arr = s.Split('.'); |
1557 | ····················// Suche Feld mit Namen arr[0] als object |
1558 | ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType()); |
1559 | ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance); |
1560 | ····················Object parentOb = parentFi.GetValue(pc); |
1561 | ····················if (parentOb == null) |
1562 | ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0])); |
1563 | ····················// Suche darin das Feld mit Namen Arr[1] |
1564 | ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance); |
1565 | ····················object o = childFi.GetValue(parentOb); |
1566 | ····················if (o == null |
1567 | ························|| o is DateTime && (DateTime) o == DateTime.MinValue |
1568 | ························|| o is Guid && (Guid) o == Guid.Empty) |
1569 | ························o = DBNull.Value; |
1570 | ····················row[f.Column.Name] = o; |
1571 | ················} |
1572 | ················catch (Exception ex) |
1573 | ················{ |
1574 | ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}"; |
1575 | |
1576 | ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message)); |
1577 | ················} |
1578 | ············} |
1579 | ········} |
1580 | |
1581 | ········/// <summary> |
1582 | ········/// Check, if the specific field is loaded. If not, LoadData will be called. |
1583 | ········/// </summary> |
1584 | ········/// <param name="o">The parent object.</param> |
1585 | ········/// <param name="fieldOrdinal">A number to identify the field.</param> |
1586 | ········public virtual void LoadField(object o, int fieldOrdinal) |
1587 | ········{ |
1588 | ············IPersistenceCapable pc = CheckPc(o); |
1589 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1590 | ············{ |
1591 | ················LoadState ls = pc.NDOLoadState; |
1592 | ················if (ls.FieldLoadState != null) |
1593 | ················{ |
1594 | ····················if (ls.FieldLoadState[fieldOrdinal]) |
1595 | ························return; |
1596 | ················} |
1597 | ················else |
1598 | ················{ |
1599 | ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() ); |
1600 | ················} |
1601 | ················LoadData(o); |
1602 | ········} |
1603 | ········} |
1604 | |
1605 | #pragma warning disable 419 |
1606 | ········/// <summary> |
1607 | ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent. |
1608 | ········/// </summary> |
1609 | ········/// <param name="o">The hollow object.</param> |
1610 | ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks> |
1611 | #pragma warning restore 419 |
1612 | ········public virtual void LoadData( object o ) |
1613 | ········{ |
1614 | ············IPersistenceCapable pc = CheckPc(o); |
1615 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects"); |
1616 | ············if (pc.NDOObjectState != NDOObjectState.Hollow) |
1617 | ················return; |
1618 | ············Class cl = GetClass(pc); |
1619 | ············IQuery q; |
1620 | ············q = CreateOidQuery(pc, cl); |
1621 | ············cache.UpdateCache(pc); // Make sure the object is in the cache |
1622 | |
1623 | ············var objects = q.Execute(); |
1624 | ············var count = objects.Count; |
1625 | |
1626 | ············if (count > 1) |
1627 | ············{ |
1628 | ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" ); |
1629 | ············} |
1630 | ············else if (count == 0) |
1631 | ············{ |
1632 | ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc)) |
1633 | ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." ); |
1634 | ············} |
1635 | ········} |
1636 | |
1637 | ········/// <summary> |
1638 | ········/// Creates a new IQuery object for the given type |
1639 | ········/// </summary> |
1640 | ········/// <param name="t"></param> |
1641 | ········/// <param name="oql"></param> |
1642 | ········/// <param name="hollow"></param> |
1643 | ········/// <param name="queryLanguage"></param> |
1644 | ········/// <returns></returns> |
1645 | ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql) |
1646 | ········{ |
1647 | ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition(); |
1648 | ············Type qt = template.MakeGenericType( t ); |
1649 | ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage ); |
1650 | ········} |
1651 | |
1652 | ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl) |
1653 | ········{ |
1654 | ············ArrayList parameters = new ArrayList(); |
1655 | ············string oql = "oid = {0}"; |
1656 | ············IQuery q = NewQuery(pc.GetType(), oql, false); |
1657 | ············q.Parameters.Add( pc.NDOObjectId ); |
1658 | ············q.AllowSubclasses = false; |
1659 | ············return q; |
1660 | ········} |
1661 | |
1662 | ········/// <summary> |
1663 | ········/// Mark the object dirty. The current state is |
1664 | ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the |
1665 | ········/// object is locked in the cache. |
1666 | ········/// </summary> |
1667 | ········/// <param name="pc"></param> |
1668 | ········internal virtual void MarkDirty(IPersistenceCapable pc) |
1669 | ········{ |
1670 | ············if (pc.NDOObjectState != NDOObjectState.Persistent) |
1671 | ················return; |
1672 | ············SaveObjectState(pc); |
1673 | ············pc.NDOObjectState = NDOObjectState.PersistentDirty; |
1674 | ········} |
1675 | |
1676 | ········/// <summary> |
1677 | ········/// Mark the object dirty, but make sure first, that the object is loaded. |
1678 | ········/// The current or loaded state is saved in a DataRow, which is stored in the DS. |
1679 | ········/// This is done, to allow easy rollback later. Also, the |
1680 | ········/// object is locked in the cache. |
1681 | ········/// </summary> |
1682 | ········/// <param name="pc"></param> |
1683 | ········internal void LoadAndMarkDirty(IPersistenceCapable pc) |
1684 | ········{ |
1685 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty."); |
1686 | |
1687 | ············if(pc.NDOObjectState == NDOObjectState.Deleted) |
1688 | ············{ |
1689 | ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed."); |
1690 | ············} |
1691 | |
1692 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1693 | ················LoadData(pc); |
1694 | |
1695 | ············// state is either (Created), Persistent, PersistentDirty |
1696 | ············if(pc.NDOObjectState == NDOObjectState.Persistent) |
1697 | ············{ |
1698 | ················MarkDirty(pc); |
1699 | ············} |
1700 | ········} |
1701 | |
1702 | |
1703 | |
1704 | ········/// <summary> |
1705 | ········/// Save current object state in DS and lock the object in the cache. |
1706 | ········/// The saved state can be used later to retrieve the original object value if the |
1707 | ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored. |
1708 | ········/// </summary> |
1709 | ········/// <param name="pc">The object that should be saved</param> |
1710 | ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param> |
1711 | ········/// <remarks> |
1712 | ········/// In a data row there are the following things: |
1713 | ········/// Item································Responsible for writing |
1714 | ········/// State (own, inherited, embedded)····WriteObject |
1715 | ········/// TimeStamp····························NDOPersistenceHandler |
1716 | ········/// Oid····································WriteId |
1717 | ········/// Foreign Keys and their Type Codes····WriteForeignKeys |
1718 | ········/// </remarks> |
1719 | ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false) |
1720 | ········{ |
1721 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState); |
1722 | ············ |
1723 | ············DataTable table = GetTable(pc); |
1724 | ············DataRow row = table.NewRow(); |
1725 | ············Class cl = GetClass(pc); |
1726 | ············WriteObject(pc, row, cl.ColumnNames, 0); |
1727 | ············WriteIdToRow(pc, row); |
1728 | ············if (!isDeleting) |
1729 | ················WriteLostForeignKeysToRow(cl, pc, row); |
1730 | ············table.Rows.Add(row); |
1731 | ············row.AcceptChanges(); |
1732 | ············ |
1733 | ············var relations = CollectRelationStates(pc); |
1734 | ············cache.Lock(pc, row, relations); |
1735 | ········} |
1736 | |
1737 | ········private void SaveFakeRow(IPersistenceCapable pc) |
1738 | ········{ |
1739 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState); |
1740 | ············ |
1741 | ············DataTable table = GetTable(pc); |
1742 | ············DataRow row = table.NewRow(); |
1743 | ············Class pcClass = GetClass(pc); |
1744 | ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker); |
1745 | ············Class cl = GetClass(pc); |
1746 | ············//WriteObject(pc, row, cl.FieldNames, 0); |
1747 | ············WriteIdToRow(pc, row); |
1748 | ············table.Rows.Add(row); |
1749 | ············row.AcceptChanges(); |
1750 | ············ |
1751 | ············cache.Lock(pc, row, null); |
1752 | ········} |
1753 | |
1754 | ········/// <summary> |
1755 | ········/// This defines one column of the row, in which we use the |
1756 | ········/// ColumnError property to determine, if the row is a fake row. |
1757 | ········/// </summary> |
1758 | ········/// <param name="pcClass"></param> |
1759 | ········/// <returns></returns> |
1760 | ········private string GetFakeRowOidColumnName(Class pcClass) |
1761 | ········{ |
1762 | ············// In case of several OidColumns the first column defined in the mapping |
1763 | ············// will be the one, holding the fake row info. |
1764 | ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name; |
1765 | ········} |
1766 | |
1767 | ········private bool IsFakeRow(Class cl, DataRow row) |
1768 | ········{ |
1769 | ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker); |
1770 | ········} |
1771 | |
1772 | ········/// <summary> |
1773 | ········/// Make a list of objects persistent. |
1774 | ········/// </summary> |
1775 | ········/// <param name="list">the list of IPersistenceCapable objects</param> |
1776 | ········public void MakePersistent(System.Collections.IList list) |
1777 | ········{ |
1778 | ············foreach (IPersistenceCapable pc in list) |
1779 | ············{ |
1780 | ················MakePersistent(pc); |
1781 | ············} |
1782 | ········} |
1783 | |
1784 | ········/// <summary> |
1785 | ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are |
1786 | ········/// not duplicated. |
1787 | ········/// </summary> |
1788 | ········/// <param name="pc">the parent object of all relations</param> |
1789 | ········/// <returns></returns> |
1790 | ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc) |
1791 | ········{ |
1792 | ············// Save state of relations |
1793 | ············Class c = GetClass(pc); |
1794 | ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count()); |
1795 | ············foreach(Relation r in c.Relations) |
1796 | ············{ |
1797 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
1798 | ················{ |
1799 | ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) ); |
1800 | ················} |
1801 | ················else |
1802 | ················{ |
1803 | ····················IList l = mappings.GetRelationContainer(pc, r); |
1804 | ····················if(l != null) |
1805 | ····················{ |
1806 | ························l = (IList) ListCloner.CloneList(l); |
1807 | ····················} |
1808 | ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) ); |
1809 | ················} |
1810 | ············} |
1811 | |
1812 | ············return relations; |
1813 | ········} |
1814 | |
1815 | |
1816 | ········/// <summary> |
1817 | ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently |
1818 | ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state, |
1819 | ········/// the list had at the beginning of the transaction. |
1820 | ········/// </summary> |
1821 | ········/// <param name="pc"></param> |
1822 | ········/// <param name="relations"></param> |
1823 | ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations ) |
1824 | ········{ |
1825 | ············Class c = GetClass(pc); |
1826 | |
1827 | ············foreach(var entry in relations) |
1828 | ············{ |
1829 | ················var r = entry.Key; |
1830 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
1831 | ················{ |
1832 | ····················mappings.SetRelationField(pc, r.FieldName, entry.Value); |
1833 | ················} |
1834 | ················else |
1835 | ················{ |
1836 | ····················if (pc.NDOGetLoadState(r.Ordinal)) |
1837 | ····················{ |
1838 | ························// Help GC by clearing lists |
1839 | ························IList l = mappings.GetRelationContainer(pc, r); |
1840 | ························if(l != null) |
1841 | ························{ |
1842 | ····························l.Clear(); |
1843 | ························} |
1844 | ························// Restore relation |
1845 | ························mappings.SetRelationContainer(pc, r, (IList)entry.Value); |
1846 | ····················} |
1847 | ················} |
1848 | ············} |
1849 | ········} |
1850 | |
1851 | |
1852 | ········/// <summary> |
1853 | ········/// Generates a query for related objects without mapping table. |
1854 | ········/// Note: this function can't be called in polymorphic scenarios, |
1855 | ········/// since they need a mapping table. |
1856 | ········/// </summary> |
1857 | ········/// <returns></returns> |
1858 | ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow) |
1859 | ········{ |
1860 | ············// At this point of execution we know, |
1861 | ············// that the target type is not polymorphic and is not 1:1. |
1862 | |
1863 | ············// We can't fetch these objects with an NDOql query |
1864 | ············// since this would require a relation in the opposite direction |
1865 | |
1866 | ············IList relatedObjects; |
1867 | ············if (l != null) |
1868 | ················relatedObjects = l; |
1869 | ············else |
1870 | ················relatedObjects = mappings.CreateRelationContainer( pc, r ); |
1871 | |
1872 | ············Type t = r.ReferencedType; |
1873 | ············Class cl = GetClass( t ); |
1874 | ············var provider = cl.Provider; |
1875 | |
1876 | ············StringBuilder sb = new StringBuilder("SELECT * FROM "); |
1877 | ············var relClass = GetClass( r.ReferencedType ); |
1878 | ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() ); |
1879 | ············sb.Append( " WHERE " ); |
1880 | ············int i = 0; |
1881 | ············List<object> parameters = new List<object>(); |
1882 | ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement ) |
1883 | ·············· { |
1884 | ·················· sb.Append( fkColumn.GetQualifiedName(relClass) ); |
1885 | ·················· sb.Append( " = {" ); |
1886 | ·················· sb.Append(i); |
1887 | ·················· sb.Append( '}' ); |
1888 | ·················· parameters.Add( pc.NDOObjectId.Id[i] ); |
1889 | ·················· if (!isLastElement) |
1890 | ······················ sb.Append( " AND " ); |
1891 | ·················· i++; |
1892 | ·············· } ); |
1893 | |
1894 | ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName ))) |
1895 | ············{ |
1896 | ················sb.Append( " AND " ); |
1897 | ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) ); |
1898 | ················sb.Append( " = " ); |
1899 | ················sb.Append( pc.NDOObjectId.Id.TypeId ); |
1900 | ············} |
1901 | |
1902 | ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql ); |
1903 | |
1904 | ············foreach (var p in parameters) |
1905 | ············{ |
1906 | ················q.Parameters.Add( p ); |
1907 | ············} |
1908 | |
1909 | ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table |
1910 | |
1911 | ············IList l2 = q.Execute(); |
1912 | |
1913 | ············foreach (object o in l2) |
1914 | ················relatedObjects.Add( o ); |
1915 | |
1916 | ············return relatedObjects; |
1917 | ········} |
1918 | |
1919 | |
1920 | ········/// <summary> |
1921 | ········/// Resolves an relation. The loaded objects will be hollow. |
1922 | ········/// </summary> |
1923 | ········/// <param name="o">The parent object.</param> |
1924 | ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param> |
1925 | ········/// <param name="hollow">True, if the fetched objects should be hollow.</param> |
1926 | ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks> |
1927 | ········public virtual void LoadRelation(object o, string fieldName, bool hollow) |
1928 | ········{ |
1929 | ············IPersistenceCapable pc = CheckPc(o); |
1930 | ············LoadRelationInternal(pc, fieldName, hollow); |
1931 | ········} |
1932 | |
1933 | ········ |
1934 | |
1935 | ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow) |
1936 | ········{ |
1937 | ············IList result = null; |
1938 | |
1939 | ············if (pc.NDOObjectState == NDOObjectState.Created) |
1940 | ················return null; |
1941 | |
1942 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1943 | ················LoadData(pc); |
1944 | |
1945 | ············if(r.MappingTable == null) |
1946 | ············{ |
1947 | ················// 1:1 are loaded with LoadData |
1948 | ················if (r.Multiplicity == RelationMultiplicity.List) |
1949 | ················{ |
1950 | ····················// Help GC by clearing lists |
1951 | ····················IList l = mappings.GetRelationContainer(pc, r); |
1952 | ····················if(l != null) |
1953 | ························l.Clear(); |
1954 | ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow); |
1955 | ····················mappings.SetRelationContainer(pc, r, relatedObjects); |
1956 | ····················result = relatedObjects; |
1957 | ················} |
1958 | ············} |
1959 | ············else |
1960 | ············{ |
1961 | ················DataTable dt = null; |
1962 | |
1963 | ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r )) |
1964 | ················{ |
1965 | ····················CheckTransaction( handler, r.MappingTable.Connection ); |
1966 | ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds); |
1967 | ················} |
1968 | |
1969 | ················IList relatedObjects; |
1970 | ················if(r.Multiplicity == RelationMultiplicity.Element) |
1971 | ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count); |
1972 | ················else |
1973 | ················{ |
1974 | ····················relatedObjects = mappings.GetRelationContainer(pc, r); |
1975 | ····················if(relatedObjects != null) |
1976 | ························relatedObjects.Clear();··// Objects will be reread |
1977 | ····················else |
1978 | ························relatedObjects = mappings.CreateRelationContainer(pc, r); |
1979 | ················} |
1980 | ···················· |
1981 | ················foreach(DataRow objRow in dt.Rows) |
1982 | ················{ |
1983 | ····················Type relType; |
1984 | |
1985 | ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························ |
1986 | ····················{ |
1987 | ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName]; |
1988 | ························if (typeCodeObj is System.DBNull) |
1989 | ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) ); |
1990 | ························relType = typeManager[(int)typeCodeObj]; |
1991 | ························if (relType == null) |
1992 | ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName)); |
1993 | ····················}························ |
1994 | ····················else |
1995 | ····················{ |
1996 | ························relType = r.ReferencedType; |
1997 | ····················} |
1998 | |
1999 | ····················//TODO: Generic Types: Exctract the type description from the type name column |
2000 | ····················if (relType.IsGenericTypeDefinition) |
2001 | ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables."); |
2002 | ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager); |
2003 | ····················IPersistenceCapable relObj = FindObject(id); |
2004 | ····················relatedObjects.Add(relObj); |
2005 | ················}···· |
2006 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
2007 | ················{ |
2008 | ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1"); |
2009 | ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null); |
2010 | ················} |
2011 | ················else |
2012 | ················{ |
2013 | ····················mappings.SetRelationContainer(pc, r, relatedObjects); |
2014 | ····················result = relatedObjects; |
2015 | ················} |
2016 | ············} |
2017 | ············// Mark relation as loaded |
2018 | ············pc.NDOSetLoadState(r.Ordinal, true); |
2019 | ············return result; |
2020 | ········} |
2021 | |
2022 | ········/// <summary> |
2023 | ········/// Loads elements of a relation |
2024 | ········/// </summary> |
2025 | ········/// <param name="pc">The object which needs to load the relation</param> |
2026 | ········/// <param name="relationName">The name of the relation</param> |
2027 | ········/// <param name="hollow">Determines, if the related objects should be hollow.</param> |
2028 | ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow) |
2029 | ········{ |
2030 | ············if (pc.NDOObjectState == NDOObjectState.Created) |
2031 | ················return null; |
2032 | ············Class cl = GetClass(pc); |
2033 | |
2034 | ············Relation r = cl.FindRelation(relationName); |
2035 | |
2036 | ············if ( r == null ) |
2037 | ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) ); |
2038 | |
2039 | ············if ( pc.NDOGetLoadState( r.Ordinal ) ) |
2040 | ················return null; |
2041 | |
2042 | ············return LoadRelation(pc, r, hollow); |
2043 | ········} |
2044 | |
2045 | ········/// <summary> |
2046 | ········/// Load the related objects of a parent object. The current value of the relation is replaced by the |
2047 | ········/// a list of objects that has been read from the DB. |
2048 | ········/// </summary> |
2049 | ········/// <param name="pc">The parent object of the relations</param> |
2050 | ········/// <param name="row">A data row containing the state of the object</param> |
2051 | ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row) |
2052 | ········{ |
2053 | ············// Stripped down to only serve 1:1-Relations w/out mapping table |
2054 | ············Class cl = GetClass(pc); |
2055 | ············foreach(Relation r in cl.Relations) |
2056 | ············{ |
2057 | ················if(r.MappingTable == null) |
2058 | ················{ |
2059 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
2060 | ····················{ |
2061 | ························bool isNull = false; |
2062 | ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns) |
2063 | ························{ |
2064 | ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value); |
2065 | ························} |
2066 | ························if (isNull) |
2067 | ························{ |
2068 | ····························mappings.SetRelationField(pc, r.FieldName, null); |
2069 | ························} |
2070 | ························else |
2071 | ························{ |
2072 | ····························Type relType; |
2073 | ····························if (r.HasSubclasses) |
2074 | ····························{ |
2075 | ································object o = row[r.ForeignKeyTypeColumnName]; |
2076 | ································if (o == DBNull.Value) |
2077 | ····································throw new NDOException(75, String.Format( |
2078 | ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.", |
2079 | ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName)); |
2080 | ································relType = typeManager[(int)o]; |
2081 | ····························} |
2082 | ····························else |
2083 | ····························{ |
2084 | ································relType = r.ReferencedType; |
2085 | ····························} |
2086 | ····························if (relType == null) |
2087 | ····························{ |
2088 | ································throw new NDOException(75, String.Format( |
2089 | ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", |
2090 | ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName)); |
2091 | ····························} |
2092 | ···· |
2093 | ····························int count = r.ForeignKeyColumns.Count(); |
2094 | ····························object[] keydata = new object[count]; |
2095 | ····························int i = 0; |
2096 | ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns) |
2097 | ····························{ |
2098 | ································keydata[i++] = row[fkColumn.Name]; |
2099 | ····························} |
2100 | |
2101 | ····························Type oidType = relType; |
2102 | ····························if (oidType.IsGenericTypeDefinition) |
2103 | ································oidType = mappings.GetRelationFieldType(r); |
2104 | |
2105 | ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager); |
2106 | ····························if(childOid.IsValid()) |
2107 | ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid)); |
2108 | ····························else |
2109 | ································mappings.SetRelationField(pc, r.FieldName, null); |
2110 | |
2111 | ························} |
2112 | ························pc.NDOSetLoadState(r.Ordinal, true); |
2113 | ····················} |
2114 | ················} |
2115 | ············} |
2116 | ········} |
2117 | |
2118 | ········ |
2119 | ········/// <summary> |
2120 | ········/// Creates a new ObjectId with the same Key value as a given ObjectId. |
2121 | ········/// </summary> |
2122 | ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param> |
2123 | ········/// <param name="t">The type of the object, the id will belong to.</param> |
2124 | ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns> |
2125 | ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks> |
2126 | ········public ObjectId NewObjectId(ObjectId oid, Type t) |
2127 | ········{ |
2128 | ············return new ObjectId(oid.Id, t); |
2129 | ········} |
2130 | |
2131 | ········/* |
2132 | ········/// <summary> |
2133 | ········/// Creates a new ObjectId which can be used to retrieve objects from the database. |
2134 | ········/// </summary> |
2135 | ········/// <param name="keyData">The id, which will be used to search for the object in the database</param> |
2136 | ········/// <param name="t">The type of the object.</param> |
2137 | ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns> |
2138 | ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks> |
2139 | ········public ObjectId NewObjectId(object keyData, Type t) |
2140 | ········{ |
2141 | ············if (keyData == null || keyData == DBNull.Value) |
2142 | ················return ObjectId.InvalidId; |
2143 | |
2144 | ············Class cl =··GetClass(t);············ |
2145 | ············ |
2146 | ············if (cl.Oid.FieldType == typeof(int)) |
2147 | ················return new ObjectId(new Int32Key(t, (int)keyData)); |
2148 | ············else if (cl.Oid.FieldType == typeof(string)) |
2149 | ················return new ObjectId(new StringKey(t, (String) keyData)); |
2150 | ············else if (cl.Oid.FieldType == typeof(Guid)) |
2151 | ················if (keyData is string) |
2152 | ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData))); |
2153 | ················else |
2154 | ····················return new ObjectId(new GuidKey(t, (Guid) keyData)); |
2155 | ············else if (cl.Oid.FieldType == typeof(MultiKey)) |
2156 | ················return new ObjectId(new MultiKey(t, (object[]) keyData)); |
2157 | ············else |
2158 | ················return ObjectId.InvalidId; |
2159 | ········} |
2160 | ········*/ |
2161 | |
2162 | |
2163 | ········/* |
2164 | ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds) |
2165 | ····················{ |
2166 | ························// The column, which hold the oid gets overwritten, if |
2167 | ························// Oid.FieldName contains a value. |
2168 | */ |
2169 | ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row) |
2170 | ········{ |
2171 | ············Class cl = GetClass(pc); |
2172 | |
2173 | ············if (cl.Oid.IsDependent) |
2174 | ················return; |
2175 | |
2176 | ············Key key = pc.NDOObjectId.Id; |
2177 | |
2178 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2179 | ············{ |
2180 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2181 | ················if (oidColumn.FieldName != null) |
2182 | ················{ |
2183 | ····················row[oidColumn.Name] = key[i]; |
2184 | ················} |
2185 | ············} |
2186 | ········} |
2187 | |
2188 | ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row) |
2189 | ········{ |
2190 | ············NDO.Mapping.Class cl = GetClass(pc); |
2191 | ············ObjectId oid = pc.NDOObjectId; |
2192 | |
2193 | ············if (cl.TimeStampColumn != null) |
2194 | ················row[cl.TimeStampColumn] = pc.NDOTimeStamp; |
2195 | |
2196 | ············if (cl.Oid.IsDependent)··// Oid data is in relation columns |
2197 | ················return; |
2198 | |
2199 | ············Key key = oid.Id; |
2200 | |
2201 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2202 | ············{ |
2203 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2204 | ················row[oidColumn.Name] = key[i]; |
2205 | ············} |
2206 | ········} |
2207 | |
2208 | ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row) |
2209 | ········{ |
2210 | ············ObjectId oid = pc.NDOObjectId; |
2211 | ············NDO.Mapping.Class cl = GetClass(pc); |
2212 | |
2213 | ············if (cl.Oid.IsDependent)··// Oid data is in relation columns |
2214 | ················return; |
2215 | |
2216 | ············Key key = oid.Id; |
2217 | |
2218 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2219 | ············{ |
2220 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2221 | ················object o = row[oidColumn.Name]; |
2222 | ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64)) |
2223 | ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName); |
2224 | ················if (oidColumn.SystemType == typeof(Guid) && (o is String)) |
2225 | ····················key[i] = new Guid((string)o); |
2226 | ················else |
2227 | ····················key[i] = o; |
2228 | ············} |
2229 | |
2230 | ········} |
2231 | |
2232 | ········private void ReadId (Cache.Entry e) |
2233 | ········{ |
2234 | ············ReadIdFromRow(e.pc, e.row); |
2235 | ········} |
2236 | |
2237 | ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames) |
2238 | ········{ |
2239 | ············WriteObject(pc, row, fieldNames, 0); |
2240 | ········} |
2241 | |
2242 | ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row) |
2243 | ········{ |
2244 | ············if (cl.TimeStampColumn == null) |
2245 | ················return; |
2246 | ············object col = row[cl.TimeStampColumn]; |
2247 | ············if (col is String) |
2248 | ················pc.NDOTimeStamp = new Guid(col.ToString()); |
2249 | ············else |
2250 | ················pc.NDOTimeStamp = (Guid) col; |
2251 | ········} |
2252 | |
2253 | |
2254 | |
2255 | ········private void ReadTimeStamp(Cache.Entry e) |
2256 | ········{ |
2257 | ············IPersistenceCapable pc = e.pc; |
2258 | ············NDO.Mapping.Class cl = GetClass(pc); |
2259 | ············Debug.Assert(!IsFakeRow(cl, e.row)); |
2260 | ············if (cl.TimeStampColumn == null) |
2261 | ················return; |
2262 | ············if (e.row[cl.TimeStampColumn] is String) |
2263 | ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString()); |
2264 | ············else |
2265 | ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn]; |
2266 | ········} |
2267 | |
2268 | ········/// <summary> |
2269 | ········/// Determines, if any objects are new, changed or deleted. |
2270 | ········/// </summary> |
2271 | ········public virtual bool HasChanges |
2272 | ········{ |
2273 | ············get |
2274 | ············{ |
2275 | ················return cache.LockedObjects.Count > 0; |
2276 | ············} |
2277 | ········} |
2278 | |
2279 | ········/// <summary> |
2280 | ········/// Do the update for all rows in the ds. |
2281 | ········/// </summary> |
2282 | ········/// <param name="types">Types with changes.</param> |
2283 | ········/// <param name="delete">True, if delete operations are to be performed.</param> |
2284 | ········/// <remarks> |
2285 | ········/// Delete and Insert/Update operations are to be separated to maintain the type order. |
2286 | ········/// </remarks> |
2287 | ········private void UpdateTypes(IList types, bool delete) |
2288 | ········{ |
2289 | ············foreach(Type t in types) |
2290 | ············{ |
2291 | ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name); |
2292 | ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t )) |
2293 | ················{ |
2294 | ····················CheckTransaction( handler, t ); |
2295 | ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError); |
2296 | ····················handler.ConcurrencyError += ceh; |
2297 | ····················try |
2298 | ····················{ |
2299 | ························DataTable dt = GetTable(t); |
2300 | ························if (delete) |
2301 | ····························handler.UpdateDeletedObjects( dt ); |
2302 | ························else |
2303 | ····························handler.Update( dt ); |
2304 | ····················} |
2305 | ····················finally |
2306 | ····················{ |
2307 | ························handler.ConcurrencyError -= ceh; |
2308 | ····················} |
2309 | ················} |
2310 | ············} |
2311 | ········} |
2312 | |
2313 | ········internal void UpdateCreatedMappingTableEntries() |
2314 | ········{ |
2315 | ············foreach (MappingTableEntry e in createdMappingTableObjects) |
2316 | ············{ |
2317 | ················if (!e.DeleteEntry) |
2318 | ····················WriteMappingTableEntry(e); |
2319 | ············} |
2320 | ············// Now update all mapping tables |
2321 | ············foreach (IMappingTableHandler handler in mappingHandler.Values) |
2322 | ············{ |
2323 | ················CheckTransaction( handler, handler.Relation.MappingTable.Connection ); |
2324 | ················handler.Update(ds); |
2325 | ············} |
2326 | ········} |
2327 | |
2328 | ········internal void UpdateDeletedMappingTableEntries() |
2329 | ········{ |
2330 | ············foreach (MappingTableEntry e in createdMappingTableObjects) |
2331 | ············{ |
2332 | ················if (e.DeleteEntry) |
2333 | ····················WriteMappingTableEntry(e); |
2334 | ············} |
2335 | ············// Now update all mapping tables |
2336 | ············foreach (IMappingTableHandler handler in mappingHandler.Values) |
2337 | ············{ |
2338 | ················CheckTransaction( handler, handler.Relation.MappingTable.Connection ); |
2339 | ················handler.Update(ds); |
2340 | ············} |
2341 | ········} |
2342 | |
2343 | ········/// <summary> |
2344 | ········/// Save all changed object into the DataSet and update the DB. |
2345 | ········/// When a newly created object is written to DB, the key might change. Therefore, |
2346 | ········/// the id is updated and the object is removed and re-inserted into the cache. |
2347 | ········/// </summary> |
2348 | ········public virtual void Save(bool deferCommit = false) |
2349 | ········{ |
2350 | ············this.DeferredMode = deferCommit; |
2351 | ············var htOnSaving = new HashSet<ObjectId>(); |
2352 | ············for(;;) |
2353 | ············{ |
2354 | ················// We need to work on a copy of the locked objects list, |
2355 | ················// since the handlers might add more objects to the cache |
2356 | ················var lockedObjects = cache.LockedObjects.ToList(); |
2357 | ················int count = lockedObjects.Count; |
2358 | ················foreach(Cache.Entry e in lockedObjects) |
2359 | ················{ |
2360 | ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted) |
2361 | ····················{ |
2362 | ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable; |
2363 | ························if (ipn != null) |
2364 | ························{ |
2365 | ····························if (!htOnSaving.Contains(e.pc.NDOObjectId)) |
2366 | ····························{ |
2367 | ································ipn.OnSaving(); |
2368 | ································htOnSaving.Add(e.pc.NDOObjectId); |
2369 | ····························} |
2370 | ························} |
2371 | ····················} |
2372 | ················} |
2373 | ················// The system is stable, if the count doesn't change |
2374 | ················if (cache.LockedObjects.Count == count) |
2375 | ····················break; |
2376 | ············} |
2377 | |
2378 | ············if (this.OnSavingEvent != null) |
2379 | ············{ |
2380 | ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count); |
2381 | ················foreach(Cache.Entry e in cache.LockedObjects) |
2382 | ····················onSavingObjects.Add(e.pc); |
2383 | ················OnSavingEvent(onSavingObjects); |
2384 | ············} |
2385 | |
2386 | ············List<Type> types = new List<Type>(); |
2387 | ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>(); |
2388 | ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null; |
2389 | ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>(); |
2390 | ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>(); |
2391 | ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>(); |
2392 | |
2393 | ············// Save current state in DataSet |
2394 | ············foreach (Cache.Entry e in cache.LockedObjects) |
2395 | ············{ |
2396 | ················Type objType = e.pc.GetType(); |
2397 | |
2398 | ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition) |
2399 | ····················objType = objType.GetGenericTypeDefinition(); |
2400 | |
2401 | ················Class cl = GetClass(e.pc); |
2402 | ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump()); |
2403 | ················if(!types.Contains(objType)) |
2404 | ················{ |
2405 | ····················//Debug.WriteLine("Added··type " + objType.Name); |
2406 | ····················types.Add(objType); |
2407 | ················} |
2408 | ················NDOObjectState objectState = e.pc.NDOObjectState; |
2409 | ················if(objectState == NDOObjectState.Deleted) |
2410 | ················{ |
2411 | ····················deletedObjects.Add(e.pc); |
2412 | ················} |
2413 | ················else if(objectState == NDOObjectState.Created) |
2414 | ················{ |
2415 | ····················WriteObject(e.pc, e.row, cl.ColumnNames);···················· |
2416 | ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row |
2417 | ····················WriteForeignKeysToRow(e.pc, e.row); |
2418 | ····················//····················Debug.WriteLine(e.pc.GetType().FullName); |
2419 | ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count]; |
2420 | ····················//····················i = 0; |
2421 | ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects) |
2422 | ····················//····················{ |
2423 | ····················//························rows[i++] = e2.row; |
2424 | ····················//····················} |
2425 | ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand"); |
2426 | ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows); |
2427 | |
2428 | ····················addedCacheEntries.Add(e); |
2429 | ····················addedObjects.Add( e.pc ); |
2430 | ····················//····················objectState = NDOObjectState.Persistent; |
2431 | ················} |
2432 | ················else |
2433 | ················{ |
2434 | ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty) |
2435 | ························changedObjects.Add( e.pc ); |
2436 | ····················WriteObject(e.pc, e.row, cl.ColumnNames); |
2437 | ····················WriteForeignKeysToRow(e.pc, e.row); ···················· |
2438 | ················} |
2439 | ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) ) |
2440 | ················{ |
2441 | ····················hollowModeObjects.Add(e.pc); |
2442 | ················} |
2443 | ············} |
2444 | |
2445 | ············// Before we delete any db rows, we have to make sure, to delete mapping |
2446 | ············// table entries first, which might have relations to the db rows to be deleted |
2447 | ············UpdateDeletedMappingTableEntries(); |
2448 | |
2449 | ············// Update DB |
2450 | ············if (ds.HasChanges()) |
2451 | ············{ |
2452 | |
2453 | ················// We need the reversed update order for deletions. |
2454 | ················types.Sort( ( t1, t2 ) => |
2455 | ················{ |
2456 | ····················int i1 = mappings.GetUpdateOrder( t1 ); |
2457 | ····················int i2 = mappings.GetUpdateOrder( t2 ); |
2458 | ····················if (i1 < i2) |
2459 | ····················{ |
2460 | ························if (!addedObjects.Any( pc => pc.GetType() == t1 )) |
2461 | ····························i1 += 100000; |
2462 | ····················} |
2463 | ····················else |
2464 | ····················{ |
2465 | ························if (!addedObjects.Any( pc => pc.GetType() == t2 )) |
2466 | ····························i2 += 100000; |
2467 | ····················} |
2468 | ····················return i2 - i1; |
2469 | ················} ); |
2470 | |
2471 | ················// Delete records first |
2472 | |
2473 | ················UpdateTypes(types, true); |
2474 | |
2475 | ················// Now do all other updates in correct order. |
2476 | ················types.Reverse(); |
2477 | |
2478 | ················UpdateTypes(types, false); |
2479 | ················ |
2480 | ················ds.AcceptChanges(); |
2481 | ················if(createdDirectObjects.Count > 0) |
2482 | ················{ |
2483 | ····················// Rewrite all children that have foreign keys to parents which have just been saved now. |
2484 | ····················// They must be written again to store the correct foreign keys. |
2485 | ····················foreach(IPersistenceCapable pc in createdDirectObjects) |
2486 | ····················{ |
2487 | ························Class cl = GetClass(pc); |
2488 | ························DataRow r = this.cache.GetDataRow(pc); |
2489 | ························string fakeColumnName = GetFakeRowOidColumnName(cl); |
2490 | ························object o = r[fakeColumnName]; |
2491 | ························r[fakeColumnName] = o; |
2492 | ····················} |
2493 | |
2494 | ····················UpdateTypes(types, false); |
2495 | ················} |
2496 | |
2497 | ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache. |
2498 | ················foreach(Cache.Entry e in addedCacheEntries) |
2499 | ················{ |
2500 | ····················cache.DeregisterLockedObject(e.pc); |
2501 | ····················ReadId(e); |
2502 | ····················cache.RegisterLockedObject(e.pc, e.row, e.relations); |
2503 | ················} |
2504 | |
2505 | ················// Now update all mapping tables. Because of possible subclasses, there is no |
2506 | ················// relation between keys in the dataset schema. Therefore, we can update mapping |
2507 | ················// tables only after all other objects have been written to ensure correct foreign keys. |
2508 | ················UpdateCreatedMappingTableEntries(); |
2509 | |
2510 | ················// The rows may contain now new Ids, which should be |
2511 | ················// stored in the lostRowInfo's before the rows get detached |
2512 | ················foreach(Cache.Entry e in cache.LockedObjects) |
2513 | ················{ |
2514 | ····················if (e.row.RowState != DataRowState.Detached) |
2515 | ····················{ |
2516 | ························IPersistenceCapable pc = e.pc; |
2517 | ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row); |
2518 | ····················} |
2519 | ················} |
2520 | |
2521 | ················ds.AcceptChanges(); |
2522 | ············} |
2523 | |
2524 | ············EndSave(!deferCommit); |
2525 | |
2526 | ············foreach(IPersistenceCapable pc in deletedObjects) |
2527 | ············{ |
2528 | ················MakeObjectTransient(pc, false); |
2529 | ············} |
2530 | |
2531 | ············ds.Clear(); |
2532 | ············mappingHandler.Clear(); |
2533 | ············createdDirectObjects.Clear(); |
2534 | ············createdMappingTableObjects.Clear(); |
2535 | ············this.relationChanges.Clear(); |
2536 | |
2537 | ············if(hollowMode) |
2538 | ············{ |
2539 | ················MakeHollow(hollowModeObjects); |
2540 | ············} |
2541 | |
2542 | ············if (this.OnSavedEvent != null) |
2543 | ············{ |
2544 | ················AuditSet auditSet = new AuditSet() |
2545 | ················{ |
2546 | ····················ChangedObjects = changedObjects, |
2547 | ····················CreatedObjects = addedObjects, |
2548 | ····················DeletedObjects = deletedObjects |
2549 | ················}; |
2550 | ················this.OnSavedEvent( auditSet ); |
2551 | ············} |
2552 | ········} |
2553 | |
2554 | ········private void EndSave(bool forceCommit) |
2555 | ········{ |
2556 | ············foreach(Cache.Entry e in cache.LockedObjects) |
2557 | ············{ |
2558 | ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty) |
2559 | ····················this.ReadTimeStamp(e); |
2560 | ················e.pc.NDOObjectState = NDOObjectState.Persistent; |
2561 | ············} |
2562 | |
2563 | ············cache.UnlockAll(); |
2564 | |
2565 | ············CheckEndTransaction(forceCommit); |
2566 | ········} |
2567 | |
2568 | ········/// <summary> |
2569 | ········/// Write all foreign keys for 1:1-relations. |
2570 | ········/// </summary> |
2571 | ········/// <param name="pc">The persistent object.</param> |
2572 | ········/// <param name="pcRow">The DataRow of the pesistent object.</param> |
2573 | ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow) |
2574 | ········{ |
2575 | ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType())) |
2576 | ············{ |
2577 | ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName); |
2578 | ················bool isDependent = GetClass(pc).Oid.IsDependent; |
2579 | |
2580 | ················if ( relObj != null ) |
2581 | ················{ |
2582 | ····················int i = 0; |
2583 | ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2584 | ····················{ |
2585 | ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++]; |
2586 | ····················} |
2587 | ····················if ( r.ForeignKeyTypeColumnName != null ) |
2588 | ····················{ |
2589 | ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId; |
2590 | ····················} |
2591 | ················} |
2592 | ················else |
2593 | ················{ |
2594 | ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2595 | ····················{ |
2596 | ························pcRow[fkColumn.Name] = DBNull.Value; |
2597 | ····················} |
2598 | ····················if ( r.ForeignKeyTypeColumnName != null ) |
2599 | ····················{ |
2600 | ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value; |
2601 | ····················} |
2602 | ················} |
2603 | ············} |
2604 | ········} |
2605 | |
2606 | |
2607 | |
2608 | ········/// <summary> |
2609 | ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys. |
2610 | ········/// </summary> |
2611 | ········/// <param name="e">the mapping table entry</param> |
2612 | ········private void WriteMappingTableEntry(MappingTableEntry e) |
2613 | ········{ |
2614 | ············IPersistenceCapable pc = e.ParentObject; |
2615 | ············IPersistenceCapable relObj = e.RelatedObject; |
2616 | ············Relation r = e.Relation; |
2617 | ············DataTable dt = GetTable(r.MappingTable.TableName); |
2618 | ············DataRow row = dt.NewRow(); |
2619 | ············int i = 0; |
2620 | ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2621 | ············{ |
2622 | ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++]; |
2623 | ············} |
2624 | ············i = 0; |
2625 | ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns) |
2626 | ············{ |
2627 | ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++]; |
2628 | ············} |
2629 | |
2630 | ············if (r.ForeignKeyTypeColumnName != null) |
2631 | ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId; |
2632 | ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null) |
2633 | ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId; |
2634 | |
2635 | ············dt.Rows.Add(row); |
2636 | ············if(e.DeleteEntry) |
2637 | ············{ |
2638 | ················row.AcceptChanges(); |
2639 | ················row.Delete(); |
2640 | ············} |
2641 | |
2642 | ············IMappingTableHandler handler; |
2643 | ············if (!mappingHandler.TryGetValue( r, out handler )) |
2644 | ············{ |
2645 | ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ); |
2646 | ············} |
2647 | ········} |
2648 | |
2649 | |
2650 | ········/// <summary> |
2651 | ········/// Undo changes of a certain object |
2652 | ········/// </summary> |
2653 | ········/// <param name="o">Object to undo</param> |
2654 | ········public void Restore(object o) |
2655 | ········{············ |
2656 | ············IPersistenceCapable pc = CheckPc(o); |
2657 | ············Cache.Entry e = null; |
2658 | ············foreach (Cache.Entry entry in cache.LockedObjects) |
2659 | ············{ |
2660 | ················if (entry.pc == pc) |
2661 | ················{ |
2662 | ····················e = entry; |
2663 | ····················break; |
2664 | ················} |
2665 | ············} |
2666 | ············if (e == null) |
2667 | ················return; |
2668 | ············Class cl = GetClass(e.pc); |
2669 | ············switch (pc.NDOObjectState) |
2670 | ············{ |
2671 | ················case NDOObjectState.PersistentDirty: |
2672 | ····················ObjectListManipulator.Remove(createdDirectObjects, pc); |
2673 | ····················foreach(Relation r in cl.Relations) |
2674 | ····················{ |
2675 | ························if (r.Multiplicity == RelationMultiplicity.Element) |
2676 | ························{ |
2677 | ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
2678 | ····························if (subPc != null && cache.IsLocked(subPc)) |
2679 | ································Restore(subPc); |
2680 | ························} |
2681 | ························else |
2682 | ························{ |
2683 | ····························if (!pc.NDOGetLoadState(r.Ordinal)) |
2684 | ································continue; |
2685 | ····························IList subList = (IList) mappings.GetRelationContainer(pc, r); |
2686 | ····························if (subList != null) |
2687 | ····························{ |
2688 | ································foreach(IPersistenceCapable subPc2 in subList) |
2689 | ································{ |
2690 | ····································if (cache.IsLocked(subPc2)) |
2691 | ········································Restore(subPc2); |
2692 | ································} |
2693 | ····························} |
2694 | ························} |
2695 | ····················} |
2696 | ····················RestoreRelatedObjects(pc, e.relations); |
2697 | ····················e.row.RejectChanges(); |
2698 | ····················ReadObject(pc, e.row, cl.ColumnNames, 0); |
2699 | ····················cache.Unlock(pc); |
2700 | ····················pc.NDOObjectState = NDOObjectState.Persistent; |
2701 | ····················break; |
2702 | ················case NDOObjectState.Created: |
2703 | ····················ReadObject(pc, e.row, cl.ColumnNames, 0); |
2704 | ····················cache.Unlock(pc); |
2705 | ····················MakeObjectTransient(pc, true); |
2706 | ····················break; |
2707 | ················case NDOObjectState.Deleted: |
2708 | ····················if (!this.IsFakeRow(cl, e.row)) |
2709 | ····················{ |
2710 | ························e.row.RejectChanges(); |
2711 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2712 | ························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2713 | ····················} |
2714 | ····················else |
2715 | ····················{ |
2716 | ························e.row.RejectChanges(); |
2717 | ························e.pc.NDOObjectState = NDOObjectState.Hollow; |
2718 | ····················} |
2719 | ····················cache.Unlock(pc); |
2720 | ····················break; |
2721 | |
2722 | ············} |
2723 | ········} |
2724 | |
2725 | ········/// <summary> |
2726 | ········/// Aborts a pending transaction without restoring the object state. |
2727 | ········/// </summary> |
2728 | ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks> |
2729 | ········public virtual void AbortTransaction() |
2730 | ········{ |
2731 | ············TransactionScope.Dispose(); |
2732 | ········} |
2733 | |
2734 | ········/// <summary> |
2735 | ········/// Rejects all changes and restores the original object state. Added Objects will be made transient. |
2736 | ········/// </summary> |
2737 | ········public virtual void Abort() |
2738 | ········{ |
2739 | ············// RejectChanges of the DS cannot be called because newly added rows would be deleted, |
2740 | ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each |
2741 | ············// individual row. |
2742 | ············createdDirectObjects.Clear(); |
2743 | ············createdMappingTableObjects.Clear(); |
2744 | ············ArrayList deletedObjects = new ArrayList(); |
2745 | ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null; |
2746 | |
2747 | ············// Read all objects from DataSet |
2748 | ············foreach (Cache.Entry e in cache.LockedObjects) |
2749 | ············{ |
2750 | ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name); |
2751 | |
2752 | ················Class cl = GetClass(e.pc); |
2753 | ················bool isFakeRow = this.IsFakeRow(cl, e.row); |
2754 | ················if (!isFakeRow) |
2755 | ················{ |
2756 | ····················RestoreRelatedObjects(e.pc, e.relations); |
2757 | ················} |
2758 | ················else |
2759 | ················{ |
2760 | ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state"); |
2761 | ················} |
2762 | |
2763 | ················switch(e.pc.NDOObjectState) |
2764 | ················{ |
2765 | ····················case NDOObjectState.Created: |
2766 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2767 | ························deletedObjects.Add(e.pc); |
2768 | ························break; |
2769 | |
2770 | ····················case NDOObjectState.PersistentDirty: |
2771 | ························e.row.RejectChanges(); |
2772 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2773 | ························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2774 | ························break; |
2775 | |
2776 | ····················case NDOObjectState.Deleted: |
2777 | ························if (!isFakeRow) |
2778 | ························{ |
2779 | ····························e.row.RejectChanges(); |
2780 | ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2781 | ····························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2782 | ························} |
2783 | ························else |
2784 | ························{ |
2785 | ····························e.row.RejectChanges(); |
2786 | ····························e.pc.NDOObjectState = NDOObjectState.Hollow; |
2787 | ························} |
2788 | ························break; |
2789 | |
2790 | ····················default: |
2791 | ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump()); |
2792 | ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState); |
2793 | ························//break; |
2794 | ················} |
2795 | ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent) |
2796 | ················{ |
2797 | ····················hollowModeObjects.Add(e.pc); |
2798 | ················} |
2799 | ············} |
2800 | ············cache.UnlockAll(); |
2801 | ············foreach(IPersistenceCapable pc in deletedObjects) |
2802 | ············{ |
2803 | ················MakeObjectTransient(pc, true); |
2804 | ············} |
2805 | ············ds.Clear(); |
2806 | ············mappingHandler.Clear(); |
2807 | ············if(hollowMode) |
2808 | ············{ |
2809 | ················MakeHollow(hollowModeObjects); |
2810 | ············} |
2811 | |
2812 | ············this.relationChanges.Clear(); |
2813 | |
2814 | |
2815 | ············AbortTransaction(); |
2816 | ········} |
2817 | |
2818 | |
2819 | ········/// <summary> |
2820 | ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to |
2821 | ········/// disable future access. |
2822 | ········/// </summary> |
2823 | ········/// <param name="pc"></param> |
2824 | ········/// <param name="removeId">Indicates if the object id should be nulled</param> |
2825 | ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId) |
2826 | ········{ |
2827 | ············cache.Deregister(pc); |
2828 | ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············ |
2829 | ············if(removeId) |
2830 | ············{ |
2831 | ················pc.NDOObjectId = null; |
2832 | ············} |
2833 | ············pc.NDOObjectState = NDOObjectState.Transient; |
2834 | ············pc.NDOStateManager = null; |
2835 | ········} |
2836 | |
2837 | ········/// <summary> |
2838 | ········/// Makes an object transient. |
2839 | ········/// The object can be used afterwards, but changes will not be saved in the database. |
2840 | ········/// </summary> |
2841 | ········/// <remarks> |
2842 | ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data. |
2843 | ········/// </remarks> |
2844 | ········/// <param name="o">The object to detach.</param> |
2845 | ········public void MakeTransient(object o) |
2846 | ········{ |
2847 | ············IPersistenceCapable pc = CheckPc(o); |
2848 | ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow) |
2849 | ············{ |
2850 | ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation"); |
2851 | ············} |
2852 | |
2853 | ············if(pc.NDOObjectState··== NDOObjectState.Hollow) |
2854 | ············{ |
2855 | ················LoadData(pc); |
2856 | ············} |
2857 | ············MakeObjectTransient(pc, true); |
2858 | ········} |
2859 | |
2860 | |
2861 | ········/// <summary> |
2862 | ········/// Make all objects of a list transient. |
2863 | ········/// </summary> |
2864 | ········/// <param name="list">the list of transient objects</param> |
2865 | ········public void MakeTransient(System.Collections.IList list) |
2866 | ········{ |
2867 | ············foreach (IPersistenceCapable pc in list) |
2868 | ················MakeTransient(pc); |
2869 | ········} |
2870 | |
2871 | ········/// <summary> |
2872 | ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used. |
2873 | ········/// </summary> |
2874 | ········/// <param name="o">The object to remove</param> |
2875 | ········public void Delete(object o) |
2876 | ········{ |
2877 | ············IPersistenceCapable pc = CheckPc(o); |
2878 | ············if (pc.NDOObjectState == NDOObjectState.Transient) |
2879 | ············{ |
2880 | ················throw new NDOException( 120, "Can't delete transient object" ); |
2881 | ············} |
2882 | ············if (pc.NDOObjectState != NDOObjectState.Deleted) |
2883 | ············{ |
2884 | ················Delete(pc, true); |
2885 | ············} |
2886 | ········} |
2887 | |
2888 | |
2889 | ········private void LoadAllRelations(object o) |
2890 | ········{ |
2891 | ············IPersistenceCapable pc = CheckPc(o); |
2892 | ············Class cl = GetClass(pc); |
2893 | ············foreach(Relation r in cl.Relations) |
2894 | ············{ |
2895 | ················if (!pc.NDOGetLoadState(r.Ordinal)) |
2896 | ····················LoadRelation(pc, r, true); |
2897 | ············} |
2898 | ········} |
2899 | |
2900 | |
2901 | ········/// <summary> |
2902 | ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used. |
2903 | ········/// </summary> |
2904 | ········/// <remarks> |
2905 | ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation. |
2906 | ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from |
2907 | ········/// the parent object. |
2908 | ········/// </remarks> |
2909 | ········/// <param name="pc">the object to remove</param> |
2910 | ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param> |
2911 | ········private void Delete(IPersistenceCapable pc, bool checkAssoziations) |
2912 | ········{ |
2913 | ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump()); |
2914 | ············//Debug.Indent(); |
2915 | ············IDeleteNotifiable idn = pc as IDeleteNotifiable; |
2916 | ············if (idn != null) |
2917 | ················idn.OnDelete(); |
2918 | |
2919 | ············LoadAllRelations(pc); |
2920 | ············DeleteRelatedObjects(pc, checkAssoziations); |
2921 | |
2922 | ············switch(pc.NDOObjectState) |
2923 | ············{ |
2924 | ················case NDOObjectState.Transient: |
2925 | ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId); |
2926 | |
2927 | ················case NDOObjectState.Created: |
2928 | ····················DataRow row = cache.GetDataRow(pc); |
2929 | ····················row.Delete(); |
2930 | ····················ArrayList cdosToDelete = new ArrayList(); |
2931 | ····················foreach (IPersistenceCapable cdo in createdDirectObjects) |
2932 | ························if ((object)cdo == (object)pc) |
2933 | ····························cdosToDelete.Add(cdo); |
2934 | ····················foreach (object o in cdosToDelete) |
2935 | ························ObjectListManipulator.Remove(createdDirectObjects, o); |
2936 | ····················MakeObjectTransient(pc, true); |
2937 | ····················break; |
2938 | ················case NDOObjectState.Persistent: |
2939 | ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden |
2940 | ························SaveObjectState(pc, true); |
2941 | ····················row = cache.GetDataRow(pc); |
2942 | ····················row.Delete(); |
2943 | ····················pc.NDOObjectState = NDOObjectState.Deleted; |
2944 | ····················break; |
2945 | ················case NDOObjectState.Hollow: |
2946 | ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden |
2947 | ························SaveFakeRow(pc); |
2948 | ····················row = cache.GetDataRow(pc); |
2949 | ····················row.Delete(); |
2950 | ····················pc.NDOObjectState = NDOObjectState.Deleted; |
2951 | ····················break; |
2952 | |
2953 | ················case NDOObjectState.PersistentDirty: |
2954 | ····················row = cache.GetDataRow(pc); |
2955 | ····················row.Delete(); |
2956 | ····················pc.NDOObjectState··= NDOObjectState.Deleted; |
2957 | ····················break; |
2958 | |
2959 | ················case NDOObjectState.Deleted: |
2960 | ····················break; |
2961 | ············} |
2962 | |
2963 | ············//Debug.Unindent(); |
2964 | ········} |
2965 | |
2966 | |
2967 | ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
2968 | ········{ |
2969 | ············MappingTableEntry mte = null; |
2970 | ············foreach(MappingTableEntry e in createdMappingTableObjects) |
2971 | ············{ |
2972 | ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r) |
2973 | ················{ |
2974 | ····················mte = e; |
2975 | ····················break; |
2976 | ················} |
2977 | ············} |
2978 | |
2979 | ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created) |
2980 | ············{ |
2981 | ················if (mte != null) |
2982 | ····················createdMappingTableObjects.Remove(mte); |
2983 | ············} |
2984 | ············else |
2985 | ············{ |
2986 | ················if (mte == null) |
2987 | ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true)); |
2988 | ············} |
2989 | ········} |
2990 | |
2991 | ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
2992 | ········{ |
2993 | ············// Two tasks: a) Null the foreign key |
2994 | ············//··············b) remove the element from the foreign container |
2995 | |
2996 | ············if (!r.Bidirectional) |
2997 | ················return; |
2998 | |
2999 | ············// this keeps the oid valid |
3000 | ············if (GetClass(child.GetType()).Oid.IsDependent) |
3001 | ················return; |
3002 | |
3003 | ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
3004 | ············{ |
3005 | ················LoadAndMarkDirty(child); |
3006 | ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null); |
3007 | ············} |
3008 | ············else //if(r.Multiplicity == RelationMultiplicity.List && |
3009 | ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)·· |
3010 | ············{ |
3011 | ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal)) |
3012 | ····················LoadRelation(child, r.ForeignRelation, true); |
3013 | ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation); |
3014 | ················if (l == null) |
3015 | ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null."); |
3016 | ················ObjectListManipulator.Remove(l, pc); |
3017 | ················// Don't need to delete the mapping table entry, because that was done |
3018 | ················// through the parent. |
3019 | ············} |
3020 | ········} |
3021 | |
3022 | ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
3023 | ········{ |
3024 | ············// 1) Element····nomap····ass |
3025 | ············// 2) Element····nomap····comp |
3026 | ············// 3) Element····map········ass |
3027 | ············// 4) Element····map········comp |
3028 | ············// 5) List········nomap····ass |
3029 | ············// 6) List········nomap····comp |
3030 | ············// 7) List········map········ass |
3031 | ············// 8) List········map········comp |
3032 | |
3033 | ············// Two tasks: Null foreign key and, if Composition, delete the child |
3034 | |
3035 | ············// If Mapping Table, delete the entry - 3,7 |
3036 | ············// If List and assoziation, null the foreign key in the foreign class - 5 |
3037 | ············// If Element, null the foreign key in the own class 1,2,3,4 |
3038 | ············// If composition, delete the child 2,4,6,8 |
3039 | |
3040 | ············// If the relObj is newly created |
3041 | ············ObjectListManipulator.Remove(createdDirectObjects, child); |
3042 | ············Class childClass = GetClass(child); |
3043 | |
3044 | ············if (r.MappingTable != null)··// 3,7 |
3045 | ············{················ |
3046 | ················DeleteMappingTableEntry(pc, r, child); |
3047 | ············} |
3048 | ············else if (r.Multiplicity == RelationMultiplicity.List |
3049 | ················&& !r.Composition && !childClass.Oid.IsDependent) // 5 |
3050 | ············{················ |
3051 | ················LoadAndMarkDirty(child); |
3052 | ················DataRow row = this.cache.GetDataRow(child); |
3053 | ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns) |
3054 | ················{ |
3055 | ····················row[fkColumnn.Name] = DBNull.Value; |
3056 | ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value); |
3057 | ················} |
3058 | ············} |
3059 | ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4 |
3060 | ············{ |
3061 | ················LoadAndMarkDirty(pc); |
3062 | ············} |
3063 | ············if (r.Composition || childClass.Oid.IsDependent) |
3064 | ············{ |
3065 | #if DEBUG |
3066 | ················if (child.NDOObjectState == NDOObjectState.Transient) |
3067 | ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName); |
3068 | #endif |
3069 | ················// Deletes the foreign key in case of List multiplicity |
3070 | ················// In case of Element multiplicity, the parent is either deleted, |
3071 | ················// or RemoveRelatedObject is called because the relation has been nulled. |
3072 | ················Delete(child);·· |
3073 | ············} |
3074 | ········} |
3075 | |
3076 | ········/// <summary> |
3077 | ········/// Remove a related object |
3078 | ········/// </summary> |
3079 | ········/// <param name="pc"></param> |
3080 | ········/// <param name="r"></param> |
3081 | ········/// <param name="child"></param> |
3082 | ········/// <param name="calledFromStateManager"></param> |
3083 | ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager) |
3084 | ········{ |
3085 | ············//TODO: We need a relation management, which is independent of |
3086 | ············//the state management of an object. At the moment the relation |
3087 | ············//lists or elements are cached for restore, if an object is marked dirty. |
3088 | ············//Thus we have to mark dirty our parent object in any case at the moment. |
3089 | ············if (calledFromStateManager) |
3090 | ················MarkDirty(pc); |
3091 | |
3092 | ············// Object can be deleted in an OnDelete-Handler |
3093 | ············if (child.NDOObjectState == NDOObjectState.Deleted) |
3094 | ················return; |
3095 | ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType()); |
3096 | ············// Preconditions |
3097 | ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject |
3098 | ············// The Object state is checked there |
3099 | |
3100 | ············// If there is a composition in the opposite direction |
3101 | ············// && the other direction hasn't been processed |
3102 | ············// throw an exception. |
3103 | ············// If an exception is thrown at this point, have a look at IsLocked.... |
3104 | ············if (r.Bidirectional && r.ForeignRelation.Composition |
3105 | ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc)) |
3106 | ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent."); |
3107 | |
3108 | ············if (!removeLock.GetLock(pc, r, child)) |
3109 | ················return; |
3110 | |
3111 | ············try |
3112 | ············{ |
3113 | ················// must be in this order, since the child |
3114 | ················// can be deleted in DeleteOrNullRelation |
3115 | ················//if (changeForeignRelations) |
3116 | ················DeleteOrNullForeignRelation(pc, r, child); |
3117 | ················DeleteOrNullRelation(pc, r, child); |
3118 | ············} |
3119 | ············finally |
3120 | ············{ |
3121 | ················removeLock.Unlock(pc, r, child); |
3122 | ············} |
3123 | |
3124 | ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) ); |
3125 | ········} |
3126 | |
3127 | ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r) |
3128 | ········{ |
3129 | ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName); |
3130 | ············//············Debug.Indent(); |
3131 | ············if (r.Multiplicity == RelationMultiplicity.Element) |
3132 | ············{ |
3133 | ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
3134 | ················if(child != null) |
3135 | ················{ |
3136 | ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition) |
3137 | ····················//····················{ |
3138 | ····················//························if (!r.ForeignRelation.Composition) |
3139 | ····················//························{ |
3140 | ····················//····························mappings.SetRelationField(pc, r.FieldName, null); |
3141 | ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null); |
3142 | ····················//························} |
3143 | ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name); |
3144 | ····················//························else |
3145 | ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName); |
3146 | ····················//····················} |
3147 | ····················InternalRemoveRelatedObject(pc, r, child, false); |
3148 | ················} |
3149 | ············} |
3150 | ············else |
3151 | ············{ |
3152 | ················IList list = mappings.GetRelationContainer(pc, r); |
3153 | ················if(list != null && list.Count > 0) |
3154 | ················{ |
3155 | ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition) |
3156 | ····················//····················{ |
3157 | ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first."); |
3158 | ····················//····················} |
3159 | ····················// Since RemoveRelatedObjects probably changes the list, |
3160 | ····················// we iterate through a copy of the list. |
3161 | ····················ArrayList al = new ArrayList(list); |
3162 | ····················foreach(IPersistenceCapable relObj in al) |
3163 | ····················{ |
3164 | ························InternalRemoveRelatedObject(pc, r, relObj, false); |
3165 | ····················} |
3166 | ················} |
3167 | ············} |
3168 | ············//············Debug.Unindent(); |
3169 | ········} |
3170 | |
3171 | ········/// <summary> |
3172 | ········/// Remove all related objects from a parent. |
3173 | ········/// </summary> |
3174 | ········/// <param name="pc">the parent object</param> |
3175 | ········/// <param name="checkAssoziations"></param> |
3176 | ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations) |
3177 | ········{ |
3178 | ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump()); |
3179 | ············//············Debug.Indent(); |
3180 | ············// delete all related objects: |
3181 | ············Class parentClass = GetClass(pc); |
3182 | ············// Remove Assoziations first |
3183 | ············foreach(Relation r in parentClass.Relations) |
3184 | ············{ |
3185 | ················if (!r.Composition) |
3186 | ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r); |
3187 | ············} |
3188 | ············foreach(Relation r in parentClass.Relations) |
3189 | ············{ |
3190 | ················if (r.Composition) |
3191 | ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r); |
3192 | ············} |
3193 | |
3194 | ············//············Debug.Unindent(); |
3195 | ········} |
3196 | |
3197 | ········/// <summary> |
3198 | ········/// Delete a list of objects |
3199 | ········/// </summary> |
3200 | ········/// <param name="list">the list of objects to remove</param> |
3201 | ········public void Delete(IList list) |
3202 | ········{ |
3203 | ············for (int i = 0; i < list.Count; i++) |
3204 | ············{ |
3205 | ················IPersistenceCapable pc = (IPersistenceCapable) list[i]; |
3206 | ················Delete(pc); |
3207 | ············} |
3208 | ········} |
3209 | |
3210 | ········/// <summary> |
3211 | ········/// Make object hollow. All relations will be unloaded and object data will be |
3212 | ········/// newly fetched during the next touch of a persistent field. |
3213 | ········/// </summary> |
3214 | ········/// <param name="o"></param> |
3215 | ········public virtual void MakeHollow(object o) |
3216 | ········{ |
3217 | ············IPersistenceCapable pc = CheckPc(o); |
3218 | ············MakeHollow(pc, false); |
3219 | ········} |
3220 | |
3221 | ········/// <summary> |
3222 | ········/// Make the object hollow if it is persistent. Unload all complex data. |
3223 | ········/// </summary> |
3224 | ········/// <param name="o"></param> |
3225 | ········/// <param name="recursive">if true then unload related objects as well</param> |
3226 | ········public virtual void MakeHollow(object o, bool recursive) |
3227 | ········{ |
3228 | ············IPersistenceCapable pc = CheckPc(o); |
3229 | ············if(pc.NDOObjectState == NDOObjectState.Hollow) |
3230 | ················return; |
3231 | ············if(pc.NDOObjectState != NDOObjectState.Persistent) |
3232 | ············{ |
3233 | ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")"); |
3234 | ············} |
3235 | ············pc.NDOObjectState = NDOObjectState.Hollow; |
3236 | ············MakeRelationsHollow(pc, recursive); |
3237 | ········} |
3238 | |
3239 | ········/// <summary> |
3240 | ········/// Make all objects of a list hollow. |
3241 | ········/// </summary> |
3242 | ········/// <param name="list">the list of objects that should be made hollow</param> |
3243 | ········public virtual void MakeHollow(System.Collections.IList list) |
3244 | ········{ |
3245 | ············MakeHollow(list, false); |
3246 | ········} |
3247 | |
3248 | ········/// <summary> |
3249 | ········/// Make all objects of a list hollow. |
3250 | ········/// </summary> |
3251 | ········/// <param name="list">the list of objects that should be made hollow</param> |
3252 | ········/// <param name="recursive">if true then unload related objects as well</param> |
3253 | ········public void MakeHollow(System.Collections.IList list, bool recursive) |
3254 | ········{ |
3255 | ············foreach (IPersistenceCapable pc in list) |
3256 | ················MakeHollow(pc, recursive);················ |
3257 | ········} |
3258 | |
3259 | ········/// <summary> |
3260 | ········/// Make all unlocked objects in the cache hollow. |
3261 | ········/// </summary> |
3262 | ········public virtual void MakeAllHollow() |
3263 | ········{ |
3264 | ············foreach(var pc in cache.UnlockedObjects) |
3265 | ············{ |
3266 | ················MakeHollow(pc, false); |
3267 | ············} |
3268 | ········} |
3269 | |
3270 | ········/// <summary> |
3271 | ········/// Make relations hollow. |
3272 | ········/// </summary> |
3273 | ········/// <param name="pc">The parent object</param> |
3274 | ········/// <param name="recursive">If true, the function unloads related objects as well.</param> |
3275 | ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive) |
3276 | ········{ |
3277 | ············Class c = GetClass(pc); |
3278 | ············foreach(Relation r in c.Relations) |
3279 | ············{ |
3280 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
3281 | ················{ |
3282 | ····················mappings.SetRelationField(pc, r.FieldName, null); |
3283 | ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
3284 | ····················//····················if((null != child) && recursive) |
3285 | ····················//····················{ |
3286 | ····················//························MakeHollow(child, true); |
3287 | ····················//····················} |
3288 | ················} |
3289 | ················else |
3290 | ················{ |
3291 | ····················if (!pc.NDOGetLoadState(r.Ordinal)) |
3292 | ························continue; |
3293 | ····················// Help GC by clearing lists |
3294 | ····················IList l = mappings.GetRelationContainer(pc, r); |
3295 | ····················if(l != null) |
3296 | ····················{ |
3297 | ························if(recursive) |
3298 | ························{ |
3299 | ····························MakeHollow(l, true); |
3300 | ························} |
3301 | ························l.Clear(); |
3302 | ····················} |
3303 | ····················// Hollow relation |
3304 | ····················mappings.SetRelationContainer(pc, r, null); |
3305 | ················} |
3306 | ············} |
3307 | ············ClearRelationState(pc); |
3308 | ········} |
3309 | |
3310 | ········private void ClearRelationState(IPersistenceCapable pc) |
3311 | ········{ |
3312 | ············Class cl = GetClass(pc); |
3313 | ············foreach(Relation r in cl.Relations) |
3314 | ················pc.NDOSetLoadState(r.Ordinal, false); |
3315 | ········} |
3316 | |
3317 | ········private void SetRelationState(IPersistenceCapable pc) |
3318 | ········{ |
3319 | ············Class cl = GetClass(pc); |
3320 | ············// Due to a bug in the enhancer the constructors are not always patched right, |
3321 | ············// so NDOLoadState might be uninitialized |
3322 | ············if (pc.NDOLoadState == null) |
3323 | ············{ |
3324 | ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic); |
3325 | ················if (fi == null) |
3326 | ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState"); |
3327 | ················fi.SetValue(pc, new LoadState()); |
3328 | ············} |
3329 | |
3330 | ············// After serialization the relation load state is null |
3331 | ············if (pc.NDOLoadState.RelationLoadState == null) |
3332 | ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize); |
3333 | ············foreach(Relation r in cl.Relations) |
3334 | ················pc.NDOSetLoadState(r.Ordinal, true); |
3335 | ········} |
3336 | |
3337 | ········/// <summary> |
3338 | ········/// Creates an object of a given type and resolves constructor parameters using the container. |
3339 | ········/// </summary> |
3340 | ········/// <param name="t">The type of the persistent object</param> |
3341 | ········/// <returns></returns> |
3342 | ········public IPersistenceCapable CreateObject(Type t) |
3343 | ········{ |
3344 | ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t ); |
3345 | ········} |
3346 | |
3347 | ········/// <summary> |
3348 | ········/// Creates an object of a given type and resolves constructor parameters using the container. |
3349 | ········/// </summary> |
3350 | ········/// <typeparam name="T">The type of the object to create.</typeparam> |
3351 | ········/// <returns></returns> |
3352 | ········public T CreateObject<T>() |
3353 | ········{ |
3354 | ············return (T)CreateObject( typeof( T ) ); |
3355 | ········} |
3356 | |
3357 | ········/// <summary> |
3358 | ········/// Gets the requested object. It first builds an ObjectId using the type and the |
3359 | ········/// key data. Then it uses FindObject to retrieve the object. No database access |
3360 | ········/// is performed. |
3361 | ········/// </summary> |
3362 | ········/// <param name="t">The type of the object to retrieve.</param> |
3363 | ········/// <param name="keyData">The key value to build the object id.</param> |
3364 | ········/// <returns>A hollow object</returns> |
3365 | ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks> |
3366 | ········public IPersistenceCapable FindObject(Type t, object keyData) |
3367 | ········{ |
3368 | ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager); |
3369 | ············return FindObject(oid); |
3370 | ········} |
3371 | |
3372 | ········/// <summary> |
3373 | ········/// Finds an object using a short id. |
3374 | ········/// </summary> |
3375 | ········/// <param name="encodedShortId"></param> |
3376 | ········/// <returns></returns> |
3377 | ········public IPersistenceCapable FindObject(string encodedShortId) |
3378 | ········{ |
3379 | ············string shortId = encodedShortId.Decode(); |
3380 | ············string[] arr = shortId.Split( '-' ); |
3381 | ············if (arr.Length != 3) |
3382 | ················throw new ArgumentException( "The format of the string is not valid", "shortId" ); |
3383 | ············Type t = shortId.GetObjectType(this);··// try readable format |
3384 | ············if (t == null) |
3385 | ············{ |
3386 | ················int typeCode = 0; |
3387 | ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode )) |
3388 | ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" ); |
3389 | ················t = this.typeManager[typeCode]; |
3390 | ················if (t == null) |
3391 | ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" ); |
3392 | ············} |
3393 | |
3394 | ············Class cls = GetClass( t ); |
3395 | ············if (cls == null) |
3396 | ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" ); |
3397 | |
3398 | ············object[] keydata = new object[cls.Oid.OidColumns.Count]; |
3399 | ············string[] oidValues = arr[2].Split( ' ' ); |
3400 | |
3401 | ············int i = 0; |
3402 | ············foreach (var oidValue in oidValues) |
3403 | ············{ |
3404 | ················Type oidType = cls.Oid.OidColumns[i].SystemType; |
3405 | ················if (oidType == typeof( int )) |
3406 | ················{ |
3407 | ····················int key; |
3408 | ····················if (!int.TryParse( oidValue, out key )) |
3409 | ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) ); |
3410 | ····················if (key > (int.MaxValue >> 1)) |
3411 | ························key = -(int.MaxValue - key); |
3412 | ····················keydata[i] = key; |
3413 | ················} |
3414 | ················else if (oidType == typeof( Guid )) |
3415 | ················{ |
3416 | ····················Guid key; |
3417 | ····················if (!Guid.TryParse( oidValue, out key )) |
3418 | ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) ); |
3419 | ····················keydata[i] = key; |
3420 | ················} |
3421 | ················else if (oidType == typeof( string )) |
3422 | ················{ |
3423 | ····················keydata[i] = oidValue; |
3424 | ················} |
3425 | ················else |
3426 | ················{ |
3427 | ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) ); |
3428 | ················} |
3429 | |
3430 | ················i++; |
3431 | ············} |
3432 | |
3433 | ············if (keydata.Length == 1) |
3434 | ················return FindObject( t, keydata[0] ); |
3435 | |
3436 | ············return FindObject( t, keydata );············ |
3437 | ········} |
3438 | |
3439 | ········/// <summary> |
3440 | ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow) |
3441 | ········/// instance of the object is returned. In either case, the DB is not accessed! |
3442 | ········/// </summary> |
3443 | ········/// <param name="id">Object id</param> |
3444 | ········/// <returns>The object to retrieve in hollow state</returns>········ |
3445 | ········public IPersistenceCapable FindObject(ObjectId id) |
3446 | ········{ |
3447 | ············if(id == null) |
3448 | ············{ |
3449 | ················throw new ArgumentNullException("id"); |
3450 | ············} |
3451 | |
3452 | ············if(!id.IsValid()) |
3453 | ············{ |
3454 | ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist"); |
3455 | ············} |
3456 | |
3457 | ············IPersistenceCapable pc = cache.GetObject(id); |
3458 | ············if(pc == null) |
3459 | ············{ |
3460 | ················pc = CreateObject(id.Id.Type); |
3461 | ················pc.NDOObjectId = id; |
3462 | ················pc.NDOStateManager = sm; |
3463 | ················pc.NDOObjectState = NDOObjectState.Hollow; |
3464 | ················cache.UpdateCache(pc); |
3465 | ············} |
3466 | ············return pc; |
3467 | ········} |
3468 | |
3469 | |
3470 | ········/// <summary> |
3471 | ········/// Reload an object from the database. |
3472 | ········/// </summary> |
3473 | ········/// <param name="o">The object to be reloaded.</param> |
3474 | ········public virtual void Refresh(object o) |
3475 | ········{ |
3476 | ············IPersistenceCapable pc = CheckPc(o); |
3477 | ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted) |
3478 | ············{ |
3479 | ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation"); |
3480 | ············} |
3481 | |
3482 | ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty) |
3483 | ················return; // Cannot update objects in current transaction |
3484 | |
3485 | ············MakeHollow(pc); |
3486 | ············LoadData(pc); |
3487 | ········} |
3488 | |
3489 | ········/// <summary> |
3490 | ········/// Refresh a list of objects. |
3491 | ········/// </summary> |
3492 | ········/// <param name="list">The list of objects to be refreshed.</param> |
3493 | ········public virtual void Refresh(IList list) |
3494 | ········{ |
3495 | ············foreach (IPersistenceCapable pc in list) |
3496 | ················Refresh(pc);························ |
3497 | ········} |
3498 | |
3499 | ········/// <summary> |
3500 | ········/// Refreshes all unlocked objects in the cache. |
3501 | ········/// </summary> |
3502 | ········public virtual void RefreshAll() |
3503 | ········{ |
3504 | ············Refresh( cache.UnlockedObjects.ToList() ); |
3505 | ········} |
3506 | |
3507 | ········/// <summary> |
3508 | ········/// Closes the PersistenceManager and releases all resources. |
3509 | ········/// </summary> |
3510 | ········public override void Close() |
3511 | ········{ |
3512 | ············if (this.isClosing) |
3513 | ················return; |
3514 | ············this.isClosing = true; |
3515 | ············TransactionScope.Dispose(); |
3516 | ············UnloadCache(); |
3517 | ············base.Close(); |
3518 | ········} |
3519 | |
3520 | ········internal void LogIfVerbose( string msg ) |
3521 | ········{ |
3522 | ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug )) |
3523 | ················Logger.LogDebug( msg ); |
3524 | ········} |
3525 | |
3526 | |
3527 | ········#endregion |
3528 | |
3529 | |
3530 | #region Class extent |
3531 | ········/// <summary> |
3532 | ········/// Gets all objects of a given class. |
3533 | ········/// </summary> |
3534 | ········/// <param name="t">the type of the class</param> |
3535 | ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns> |
3536 | ········public virtual IList GetClassExtent(Type t) |
3537 | ········{ |
3538 | ············return GetClassExtent(t, true); |
3539 | ········} |
3540 | |
3541 | ········/// <summary> |
3542 | ········/// Gets all objects of a given class. |
3543 | ········/// </summary> |
3544 | ········/// <param name="t">The type of the class.</param> |
3545 | ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param> |
3546 | ········/// <returns>A list of all persistent objects of the given class.</returns> |
3547 | ········/// <remarks>Subclasses of the given type are not fetched.</remarks> |
3548 | ········public virtual IList GetClassExtent(Type t, bool hollow) |
3549 | ········{ |
3550 | ············IQuery q = NewQuery( t, null, hollow ); |
3551 | ············return q.Execute(); |
3552 | ········} |
3553 | |
3554 | #endregion |
3555 | |
3556 | #region Query engine |
3557 | |
3558 | |
3559 | ········/// <summary> |
3560 | ········/// Returns a virtual table for Linq queries. |
3561 | ········/// </summary> |
3562 | ········/// <typeparam name="T"></typeparam> |
3563 | ········/// <returns></returns> |
3564 | ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable |
3565 | ········{ |
3566 | ············return new VirtualTable<T>( this ); |
3567 | ········} |
3568 | |
3569 | ········ |
3570 | ········/// <summary> |
3571 | ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B, |
3572 | ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have |
3573 | ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To |
3574 | ········/// not lose the foreign key it will be stored as part of the object. |
3575 | ········/// </summary> |
3576 | ········/// <param name="cl"></param> |
3577 | ········/// <param name="pc"></param> |
3578 | ········/// <param name="row"></param> |
3579 | ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row) |
3580 | ········{ |
3581 | ············if (cl.FKColumnNames != null && pc.NDOLoadState != null) |
3582 | ············{ |
3583 | ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump()); |
3584 | ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count()); |
3585 | ················foreach(string colName in cl.FKColumnNames) |
3586 | ····················kvl.Add(new KeyValuePair(colName, row[colName])); |
3587 | ················pc.NDOLoadState.LostRowInfo = kvl;················ |
3588 | ············} |
3589 | ········} |
3590 | |
3591 | ········/// <summary> |
3592 | ········/// Writes information into the data row, which cannot be stored in the object. |
3593 | ········/// </summary> |
3594 | ········/// <param name="cl"></param> |
3595 | ········/// <param name="pc"></param> |
3596 | ········/// <param name="row"></param> |
3597 | ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row) |
3598 | ········{ |
3599 | ············if (cl.FKColumnNames != null) |
3600 | ············{ |
3601 | ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump()); |
3602 | ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo; |
3603 | ················if (kvl == null) |
3604 | ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump()); |
3605 | ················foreach (KeyValuePair pair in kvl) |
3606 | ····················row[(string) pair.Key] = pair.Value; |
3607 | ············} |
3608 | ········} |
3609 | |
3610 | ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row) |
3611 | ········{ |
3612 | ············ReadObject(pc, row, cl.ColumnNames, 0); |
3613 | ············ReadTimeStamp(cl, pc, row); |
3614 | ············ReadLostForeignKeysFromRow(cl, pc, row); |
3615 | ············LoadRelated1To1Objects(pc, row); |
3616 | ············pc.NDOObjectState = NDOObjectState.Persistent; |
3617 | ········} |
3618 | |
3619 | |
3620 | ········/// <summary> |
3621 | ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type. |
3622 | ········/// </summary> |
3623 | ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow) |
3624 | ········{ |
3625 | ············IList queryResult = GenericListReflector.CreateList(t, rows.Count); |
3626 | ············if (rows.Count == 0) |
3627 | ················return queryResult; |
3628 | |
3629 | ············IList callbackObjects = new ArrayList(); |
3630 | ············Class cl = GetClass(t); |
3631 | ············if (t.IsGenericTypeDefinition) |
3632 | ············{ |
3633 | ················if (cl.TypeNameColumn == null) |
3634 | ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file."); |
3635 | ················IEnumerator en = rows.GetEnumerator(); |
3636 | ················en.MoveNext();··// Move to the first element |
3637 | ················DataRow testrow = (DataRow)en.Current; |
3638 | ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null) |
3639 | ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema."); |
3640 | ············} |
3641 | |
3642 | ············foreach(DataRow row in rows) |
3643 | ············{ |
3644 | ················Type concreteType = t; |
3645 | ················if (t.IsGenericTypeDefinition)··// type information is in the row |
3646 | ················{ |
3647 | ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value) |
3648 | ····················{ |
3649 | ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager); |
3650 | ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString()); |
3651 | ····················} |
3652 | ····················string typeStr = (string)row[cl.TypeNameColumn.Name]; |
3653 | ····················concreteType = Type.GetType(typeStr); |
3654 | ····················if (concreteType == null) |
3655 | ························throw new NDOException(106, "Can't load generic type " + typeStr); |
3656 | ················} |
3657 | ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager); |
3658 | ················IPersistenceCapable pc = cache.GetObject(id);················ |
3659 | ················if(pc == null) |
3660 | ················{ |
3661 | ····················pc = CreateObject( concreteType ); |
3662 | ····················pc.NDOObjectId = id; |
3663 | ····················pc.NDOStateManager = sm; |
3664 | ····················// If the object shouldn't be hollow, this will be overwritten later. |
3665 | ····················pc.NDOObjectState = NDOObjectState.Hollow; |
3666 | ················} |
3667 | ················// If we have found a non hollow object, the time stamp remains the old one. |
3668 | ················// In every other case we use the new time stamp. |
3669 | ················// Note, that there could be a hollow object in the cache. |
3670 | ················// We need the time stamp in hollow objects in order to correctly |
3671 | ················// delete objects using fake rows. |
3672 | ················if (pc.NDOObjectState == NDOObjectState.Hollow) |
3673 | ················{ |
3674 | ····················ReadTimeStamp(cl, pc, row); |
3675 | ················} |
3676 | ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty) |
3677 | ················{ |
3678 | ····················Row2Object(cl, pc, row); |
3679 | ····················if ((pc as IPersistenceNotifiable) != null) |
3680 | ························callbackObjects.Add(pc); |
3681 | ················} |
3682 | |
3683 | ················cache.UpdateCache(pc); |
3684 | ················queryResult.Add(pc); |
3685 | ············} |
3686 | ············// Make shure this is the last statement before returning |
3687 | ············// to the caller, so the user can recursively use persistent |
3688 | ············// objects |
3689 | ············foreach(IPersistenceNotifiable ipn in callbackObjects) |
3690 | ················ipn.OnLoaded(); |
3691 | |
3692 | ············return queryResult; |
3693 | ········} |
3694 | |
3695 | #endregion |
3696 | |
3697 | #region Cache Management |
3698 | ········/// <summary> |
3699 | ········/// Remove all unused entries from the cache. |
3700 | ········/// </summary> |
3701 | ········public void CleanupCache() |
3702 | ········{ |
3703 | ············GC.Collect(GC.MaxGeneration); |
3704 | ············GC.WaitForPendingFinalizers(); |
3705 | ············cache.Cleanup(); |
3706 | ········} |
3707 | |
3708 | ········/// <summary> |
3709 | ········/// Remove all unlocked objects from the cache. Use with care! |
3710 | ········/// </summary> |
3711 | ········public void UnloadCache() |
3712 | ········{ |
3713 | ············cache.Unload(); |
3714 | ········} |
3715 | #endregion |
3716 | |
3717 | ········/// <summary> |
3718 | ········/// Default encryption key for NDO |
3719 | ········/// </summary> |
3720 | ········/// <remarks> |
3721 | ········/// We recommend strongly to use an own encryption key, which can be set with this property. |
3722 | ········/// </remarks> |
3723 | ········public virtual byte[] EncryptionKey |
3724 | ········{ |
3725 | ············get |
3726 | ············{ |
3727 | ················if (this.encryptionKey == null) |
3728 | ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66, |
3729 | ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D }; |
3730 | ················return this.encryptionKey; |
3731 | ············} |
3732 | ············set { this.encryptionKey = value; } |
3733 | ········} |
3734 | |
3735 | ········/// <summary> |
3736 | ········/// Hollow mode: If true all objects are made hollow after each transaction. |
3737 | ········/// </summary> |
3738 | ········public virtual bool HollowMode |
3739 | ········{ |
3740 | ············get { return hollowMode; } |
3741 | ············set { hollowMode = value; } |
3742 | ········} |
3743 | |
3744 | ········internal TypeManager TypeManager |
3745 | ········{ |
3746 | ············get { return typeManager; } |
3747 | ········} |
3748 | |
3749 | ········/// <summary> |
3750 | ········/// Sets or gets transaction mode. Uses TransactionMode enum. |
3751 | ········/// </summary> |
3752 | ········/// <remarks> |
3753 | ········/// Set this value before you start any transactions. |
3754 | ········/// </remarks> |
3755 | ········public TransactionMode TransactionMode |
3756 | ········{ |
3757 | ············get { return TransactionScope.TransactionMode; } |
3758 | ············set { TransactionScope.TransactionMode = value; } |
3759 | ········} |
3760 | |
3761 | ········/// <summary> |
3762 | ········/// Sets or gets the Isolation Level. |
3763 | ········/// </summary> |
3764 | ········/// <remarks> |
3765 | ········/// Set this value before you start any transactions. |
3766 | ········/// </remarks> |
3767 | ········public IsolationLevel IsolationLevel |
3768 | ········{ |
3769 | ············get { return TransactionScope.IsolationLevel; } |
3770 | ············set { TransactionScope.IsolationLevel = value; } |
3771 | ········} |
3772 | |
3773 | ········internal class MappingTableEntry |
3774 | ········{ |
3775 | ············private IPersistenceCapable parentObject; |
3776 | ············private IPersistenceCapable relatedObject; |
3777 | ············private Relation relation; |
3778 | ············private bool deleteEntry; |
3779 | ············ |
3780 | ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false) |
3781 | ············{ |
3782 | ············} |
3783 | ············ |
3784 | ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry) |
3785 | ············{ |
3786 | ················parentObject = pc; |
3787 | ················relatedObject = relObj; |
3788 | ················relation = r; |
3789 | ················this.deleteEntry = deleteEntry; |
3790 | ············} |
3791 | |
3792 | ············public bool DeleteEntry |
3793 | ············{ |
3794 | ················get |
3795 | ················{ |
3796 | ····················return deleteEntry; |
3797 | ················} |
3798 | ············} |
3799 | |
3800 | ············public IPersistenceCapable ParentObject |
3801 | ············{ |
3802 | ················get |
3803 | ················{ |
3804 | ····················return parentObject; |
3805 | ················} |
3806 | ············} |
3807 | |
3808 | ············public IPersistenceCapable RelatedObject |
3809 | ············{ |
3810 | ················get |
3811 | ················{ |
3812 | ····················return relatedObject; |
3813 | ················} |
3814 | ············} |
3815 | |
3816 | ············public Relation Relation |
3817 | ············{ |
3818 | ················get |
3819 | ················{ |
3820 | ····················return relation; |
3821 | ················} |
3822 | ············} |
3823 | ········} |
3824 | |
3825 | ········/// <summary> |
3826 | ········/// Get a DataRow representing the given object. |
3827 | ········/// </summary> |
3828 | ········/// <param name="o"></param> |
3829 | ········/// <returns></returns> |
3830 | ········public DataRow GetClonedDataRow( object o ) |
3831 | ········{ |
3832 | ············IPersistenceCapable pc = CheckPc( o ); |
3833 | |
3834 | ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient) |
3835 | ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." ); |
3836 | |
3837 | ············DataRow row = cache.GetDataRow( pc ); |
3838 | ············DataTable newTable = row.Table.Clone(); |
3839 | ············newTable.ImportRow( row ); |
3840 | ············row = newTable.Rows[0]; |
3841 | |
3842 | ············Class cls = mappings.FindClass(o.GetType()); |
3843 | ············WriteObject( pc, row, cls.ColumnNames ); |
3844 | ············WriteForeignKeysToRow( pc, row ); |
3845 | |
3846 | ············return row; |
3847 | ········} |
3848 | |
3849 | ········/// <summary> |
3850 | ········/// Gets an object, which shows all changes applied to the given object. |
3851 | ········/// This function can be used to build an audit system. |
3852 | ········/// </summary> |
3853 | ········/// <param name="o"></param> |
3854 | ········/// <returns>An ExpandoObject</returns> |
3855 | ········public ChangeLog GetChangeSet( object o ) |
3856 | ········{ |
3857 | ············var changeLog = new ChangeLog(this); |
3858 | ············changeLog.Initialize( o ); |
3859 | ············return changeLog; |
3860 | ········} |
3861 | |
3862 | ········/// <summary> |
3863 | ········/// Outputs a revision number representing the assembly version. |
3864 | ········/// </summary> |
3865 | ········/// <remarks>This can be used for debugging purposes</remarks> |
3866 | ········public int Revision |
3867 | ········{ |
3868 | ············get |
3869 | ············{ |
3870 | ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version; |
3871 | ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision ); |
3872 | ················return int.Parse( vstring ); |
3873 | ············} |
3874 | ········} |
3875 | ····} |
3876 | } |
3877 |
New Commit (e187d8e)
1 | // |
2 | // Copyright (c) 2002-2024 Mirko Matytschak |
3 | // (www.netdataobjects.de) |
4 | // |
5 | // Author: Mirko Matytschak |
6 | // |
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
8 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation |
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
10 | // Software, and to permit persons to whom the Software is furnished to do so, subject to the following |
11 | // conditions: |
12 | |
13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions |
14 | // of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
19 | // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
20 | // DEALINGS IN THE SOFTWARE. |
21 | |
22 | |
23 | using System; |
24 | using System.Text; |
25 | using System.IO; |
26 | using System.Collections; |
27 | using System.Collections.Generic; |
28 | using System.Data; |
29 | using System.Diagnostics; |
30 | using System.Reflection; |
31 | using System.Text.RegularExpressions; |
32 | using System.Linq; |
33 | using System.Xml.Linq; |
34 | |
35 | using NDO.Mapping; |
36 | using NDOInterfaces; |
37 | using NDO.ShortId; |
38 | using System.Globalization; |
39 | using NDO.Linq; |
40 | using NDO.Query; |
41 | using NDO.ChangeLogging; |
42 | using Microsoft.Extensions.DependencyInjection; |
43 | using Microsoft.Extensions.Logging; |
44 | |
45 | namespace NDO |
46 | { |
47 | ····/// <summary> |
48 | ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager. |
49 | ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/> |
50 | ····/// </summary> |
51 | ····public delegate void CollisionHandler(object o); |
52 | ····/// <summary> |
53 | ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager. |
54 | ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/> |
55 | ····/// </summary> |
56 | ····public delegate void IdGenerationHandler(Type t, ObjectId oid); |
57 | ····/// <summary> |
58 | ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager. |
59 | ····/// </summary> |
60 | ····public delegate void OnSavingHandler(ICollection l); |
61 | ····/// <summary> |
62 | ····/// Delegate type for the OnSavedEvent. |
63 | ····/// </summary> |
64 | ····/// <param name="auditSet"></param> |
65 | ····public delegate void OnSavedHandler(AuditSet auditSet); |
66 | |
67 | ····/// <summary> |
68 | ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid. |
69 | ····/// </summary> |
70 | ····/// <param name="pc"></param> |
71 | ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns> |
72 | ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks> |
73 | ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc ); |
74 | |
75 | ····/// <summary> |
76 | ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets. |
77 | ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation. |
78 | ····/// </summary> |
79 | ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager |
80 | ····{········ |
81 | ········private bool hollowMode = false; |
82 | ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers |
83 | |
84 | ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations |
85 | ········private ObjectLock removeLock = new ObjectLock(); |
86 | ········private ObjectLock addLock = new ObjectLock(); |
87 | ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys. |
88 | ········// List of created objects that use mapping table and need to be stored in mapping table |
89 | ········// after they have been stored to the database to update foreign keys. |
90 | ········private ArrayList createdMappingTableObjects = new ArrayList();·· |
91 | ········private TypeManager typeManager; |
92 | ········internal bool DeferredMode { get; private set; } |
93 | ········private INDOTransactionScope transactionScope; |
94 | ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········ |
95 | |
96 | ········private OpenConnectionListener openConnectionListener; |
97 | |
98 | ········/// <summary> |
99 | ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps. |
100 | ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation. |
101 | ········/// </summary> |
102 | ········public event CollisionHandler CollisionEvent; |
103 | |
104 | ········/// <summary> |
105 | ········/// Register a listener to this event to handle situations where LoadData doesn't find an object. |
106 | ········/// The listener can determine, whether an exception should be thrown, if the situation occurs. |
107 | ········/// </summary> |
108 | ········public event ObjectNotPresentHandler ObjectNotPresentEvent; |
109 | |
110 | ········/// <summary> |
111 | ········/// Register a listener to this event, if you want to be notified about the end |
112 | ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed |
113 | ········/// during the transaction and are to be saved or deleted. |
114 | ········/// </summary> |
115 | ········public event OnSavingHandler OnSavingEvent; |
116 | ········/// <summary> |
117 | ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects. |
118 | ········/// </summary> |
119 | ········public event OnSavedHandler OnSavedEvent; |
120 | ········ |
121 | ········private const string hollowMarker = "Hollow"; |
122 | ········private byte[] encryptionKey; |
123 | ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>(); |
124 | ········private bool isClosing = false; |
125 | |
126 | ········/// <summary> |
127 | ········/// Gets a list of structures which represent relation changes, i.e. additions and removals |
128 | ········/// </summary> |
129 | ········protected internal List<RelationChangeRecord> RelationChanges |
130 | ········{ |
131 | ············get { return this.relationChanges; } |
132 | ········} |
133 | |
134 | ········/// <summary> |
135 | ········/// Initializes a new PersistenceManager instance. |
136 | ········/// </summary> |
137 | ········/// <param name="mappingFileName"></param> |
138 | ········protected override void Init(string mappingFileName) |
139 | ········{ |
140 | ············try |
141 | ············{ |
142 | ················base.Init(mappingFileName); |
143 | ············} |
144 | ············catch (Exception ex) |
145 | ············{ |
146 | ················if (ex is NDOException) |
147 | ····················throw; |
148 | ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString()); |
149 | ············} |
150 | |
151 | ········} |
152 | |
153 | ········/// <summary> |
154 | ········/// Initializes the persistence manager |
155 | ········/// </summary> |
156 | ········/// <remarks> |
157 | ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase. |
158 | ········/// </remarks> |
159 | ········/// <param name="mapping"></param> |
160 | ········internal override void Init( Mappings mapping ) |
161 | ········{ |
162 | ············base.Init( mapping ); |
163 | |
164 | ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this; |
165 | |
166 | ············string dir = Path.GetDirectoryName( mapping.FileName ); |
167 | |
168 | ············string typesFile = Path.Combine( dir, "NDOTypes.xml" ); |
169 | ············typeManager = new TypeManager( typesFile, this.mappings ); |
170 | |
171 | ············sm = new StateManager( this ); |
172 | |
173 | ············InitClasses(); |
174 | ········} |
175 | |
176 | |
177 | ········/// <summary> |
178 | ········/// Standard Constructor. |
179 | ········/// </summary> |
180 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
181 | ········/// <remarks> |
182 | ········/// Searches for a mapping file in the application directory. |
183 | ········/// The constructor tries to find a file with the same name as |
184 | ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a |
185 | ········/// file called AssemblyName.ndo.mapping in the application directory. |
186 | ········/// </remarks> |
187 | ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider ) |
188 | ········{ |
189 | ········} |
190 | |
191 | ········/// <summary> |
192 | ········/// Loads the mapping file from the specified location. This allows to use |
193 | ········/// different mapping files with different classes mapped in it. |
194 | ········/// </summary> |
195 | ········/// <param name="mappingFile">Path to the mapping file.</param> |
196 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
197 | ········/// <remarks>Only the Professional and Enterprise |
198 | ········/// Editions can handle more than one mapping file.</remarks> |
199 | ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider) |
200 | ········{ |
201 | ········} |
202 | |
203 | ········/// <summary> |
204 | ········/// Constructs a PersistenceManager and reuses a cached NDOMapping. |
205 | ········/// </summary> |
206 | ········/// <param name="mapping">The cached mapping object</param> |
207 | ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param> |
208 | ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider) |
209 | ········{ |
210 | ········} |
211 | |
212 | ········#region Object Container Stuff |
213 | ········/// <summary> |
214 | ········/// Gets a container of all loaded objects and tries to load all child objects, |
215 | ········/// which are reachable through composite relations. |
216 | ········/// </summary> |
217 | ········/// <returns>An ObjectContainer object.</returns> |
218 | ········/// <remarks> |
219 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
220 | ········/// Persistent, or Transient. |
221 | ········/// The transfer format is binary. |
222 | ········/// </remarks> |
223 | ········public ObjectContainer GetObjectContainer() |
224 | ········{ |
225 | ············IList l = this.cache.AllObjects; |
226 | ············foreach(IPersistenceCapable pc in l) |
227 | ············{ |
228 | ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty) |
229 | ················{ |
230 | ····················if (Logger != null) |
231 | ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." ); |
232 | ················} |
233 | ············} |
234 | |
235 | ············ObjectContainer oc = new ObjectContainer(); |
236 | ············oc.AddList(l); |
237 | ············return oc; |
238 | ········} |
239 | |
240 | ········/// <summary> |
241 | ········/// Returns a container of all objects provided in the objects list and searches for |
242 | ········/// child objects according to the serFlags. |
243 | ········/// </summary> |
244 | ········/// <param name="objects">The list of the root objects to add to the container.</param> |
245 | ········/// <returns>An ObjectContainer object.</returns> |
246 | ········/// <remarks> |
247 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
248 | ········/// Persistent, or Transient. |
249 | ········/// </remarks> |
250 | ········public ObjectContainer GetObjectContainer(IList objects) |
251 | ········{ |
252 | ············foreach(object o in objects) |
253 | ············{ |
254 | ················CheckPc(o); |
255 | ················IPersistenceCapable pc = o as IPersistenceCapable; |
256 | ················if (pc.NDOObjectState == NDOObjectState.Hollow) |
257 | ····················LoadData(pc); |
258 | ············} |
259 | ············ObjectContainer oc = new ObjectContainer(); |
260 | ············oc.AddList(objects); |
261 | ············return oc; |
262 | ········} |
263 | |
264 | |
265 | ········/// <summary> |
266 | ········/// Returns a container containing the provided object |
267 | ········/// and tries to load all child objects |
268 | ········/// reachable through composite relations. |
269 | ········/// </summary> |
270 | ········/// <param name="obj">The object to be added to the container.</param> |
271 | ········/// <returns>An ObjectContainer object.</returns> |
272 | ········/// <remarks> |
273 | ········/// It is not recommended, to transfer objects with a state other than Hollow, |
274 | ········/// Persistent, or Transient. |
275 | ········/// The transfer format is binary. |
276 | ········/// </remarks> |
277 | ········public ObjectContainer GetObjectContainer(Object obj) |
278 | ········{ |
279 | ············CheckPc(obj); |
280 | ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow) |
281 | ················LoadData(obj); |
282 | ············ObjectContainer oc = new ObjectContainer(); |
283 | ············oc.AddObject(obj); |
284 | ············return oc; |
285 | ········} |
286 | |
287 | ········/// <summary> |
288 | ········/// Merges an object container to the active objects in the pm. All changes and the state |
289 | ········/// of the objects will be taken over by the pm. |
290 | ········/// </summary> |
291 | ········/// <remarks> |
292 | ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer. |
293 | ········/// The flag MarkAsTransient can be used to perform a kind |
294 | ········/// of object based replication using the ObjectContainer class. |
295 | ········/// Objects, which are persistent at one machine, can be transfered |
296 | ········/// to a second machine and treated by the receiving PersistenceManager like a newly created |
297 | ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole |
298 | ········/// transient object tree. |
299 | ········/// There is one difference to freshly created objects: If an object id exists, it will be |
300 | ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered |
301 | ········/// oids will be reused by the receiving PersistenceManager. |
302 | ········/// </remarks> |
303 | ········/// <param name="ocb">The object container to be merged.</param> |
304 | ········public void MergeObjectContainer(ObjectContainerBase ocb) |
305 | ········{ |
306 | ············ChangeSetContainer csc = ocb as ChangeSetContainer; |
307 | ············if (csc != null) |
308 | ············{ |
309 | ················MergeChangeSet(csc); |
310 | ················return; |
311 | ············} |
312 | ············ObjectContainer oc = ocb as ObjectContainer; |
313 | ············if (oc != null) |
314 | ············{ |
315 | ················InternalMergeObjectContainer(oc); |
316 | ················return; |
317 | ············} |
318 | ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter."); |
319 | ········} |
320 | |
321 | |
322 | ········void InternalMergeObjectContainer(ObjectContainer oc) |
323 | ········{ |
324 | ············// TODO: Check, if other states are useful. Find use scenarios. |
325 | ············foreach(IPersistenceCapable pc in oc.RootObjects) |
326 | ············{ |
327 | ················if (pc.NDOObjectState == NDOObjectState.Transient) |
328 | ····················MakePersistent(pc); |
329 | ············} |
330 | ············foreach(IPersistenceCapable pc in oc.RootObjects) |
331 | ············{ |
332 | ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc); |
333 | ············} |
334 | ········} |
335 | |
336 | ········void MergeChangeSet(ChangeSetContainer cs) |
337 | ········{ |
338 | ············foreach(IPersistenceCapable pc in cs.AddedObjects) |
339 | ············{ |
340 | ················InternalMakePersistent(pc, false); |
341 | ············} |
342 | ············foreach(ObjectId oid in cs.DeletedObjects) |
343 | ············{ |
344 | ················IPersistenceCapable pc2 = FindObject(oid); |
345 | ················Delete(pc2); |
346 | ············} |
347 | ············foreach(IPersistenceCapable pc in cs.ChangedObjects) |
348 | ············{ |
349 | ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId); |
350 | ················Class pcClass = GetClass(pc); |
351 | ················// Make sure, the object is loaded. |
352 | ················if (pc2.NDOObjectState == NDOObjectState.Hollow) |
353 | ····················LoadData(pc2); |
354 | ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row |
355 | ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId ); |
356 | ················DataRow row = entry.row; |
357 | ················pc.NDOWrite(row, pcClass.ColumnNames, 0); |
358 | ················pc2.NDORead(row, pcClass.ColumnNames, 0); |
359 | ············} |
360 | ············foreach(RelationChangeRecord rcr in cs.RelationChanges) |
361 | ············{ |
362 | ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId); |
363 | ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId); |
364 | ················Class pcClass = GetClass(parent); |
365 | ················Relation r = pcClass.FindRelation(rcr.RelationName); |
366 | ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal]) |
367 | ····················LoadRelation(parent, r, true); |
368 | ················if (rcr.IsAdded) |
369 | ················{ |
370 | ····················InternalAddRelatedObject(parent, r, child, true); |
371 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
372 | ····················{ |
373 | ························mappings.SetRelationField(parent, r.FieldName, child); |
374 | ····················} |
375 | ····················else |
376 | ····················{ |
377 | ························IList l = mappings.GetRelationContainer(parent, r); |
378 | ························l.Add(child); |
379 | ····················} |
380 | ················} |
381 | ················else |
382 | ················{ |
383 | ····················RemoveRelatedObject(parent, r.FieldName, child); |
384 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
385 | ····················{ |
386 | ························mappings.SetRelationField(parent, r.FieldName, null); |
387 | ····················} |
388 | ····················else |
389 | ····················{ |
390 | ························IList l = mappings.GetRelationContainer(parent, r); |
391 | ························try |
392 | ························{ |
393 | ····························ObjectListManipulator.Remove(l, child); |
394 | ························} |
395 | ························catch |
396 | ························{ |
397 | ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName); |
398 | ························} |
399 | ····················} |
400 | ················} |
401 | ············} |
402 | |
403 | ········} |
404 | ········#endregion |
405 | |
406 | ········#region Implementation of IPersistenceManager |
407 | |
408 | ········// Complete documentation can be found in IPersistenceManager |
409 | |
410 | |
411 | ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row) |
412 | ········{ |
413 | ············if (!cl.Oid.IsDependent) |
414 | ················return; |
415 | ············WriteForeignKeysToRow(pc, row); |
416 | ········} |
417 | |
418 | ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations) |
419 | ········{ |
420 | ············// Object is now under control of the state manager |
421 | ············pc.NDOStateManager = sm; |
422 | |
423 | ············Type pcType = pc.GetType(); |
424 | ············Class pcClass = GetClass(pc); |
425 | |
426 | ············// Create new object |
427 | ············DataTable dt = GetTable(pcType); |
428 | ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value |
429 | |
430 | ············// In case of a Guid oid the value will be computed now. |
431 | ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns) |
432 | ············{ |
433 | ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null) |
434 | ················{ |
435 | ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string)) |
436 | ························row[oidColumn.Name] = Guid.NewGuid().ToString(); |
437 | ····················else |
438 | ························row[oidColumn.Name] = Guid.NewGuid(); |
439 | ················} |
440 | ············} |
441 | |
442 | ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS |
443 | |
444 | ············// If the object is merged from an ObjectContainer, the id should be reused, |
445 | ············// if the id is client generated (not Autoincremented). |
446 | ············// In every other case, the oid is set to null, to force generating a new oid. |
447 | ············bool fireIdGeneration = (Object)pc.NDOObjectId == null; |
448 | ············if ((object)pc.NDOObjectId != null) |
449 | ············{ |
450 | ················bool hasAutoincrement = false; |
451 | ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns) |
452 | ················{ |
453 | ····················if (oidColumn.AutoIncremented) |
454 | ····················{ |
455 | ························hasAutoincrement = true; |
456 | ························break; |
457 | ····················} |
458 | ················} |
459 | ················if (hasAutoincrement) // can't store existing id |
460 | ················{ |
461 | ····················pc.NDOObjectId = null; |
462 | ····················fireIdGeneration = true; |
463 | ················} |
464 | ············} |
465 | |
466 | ············// In case of a dependent class the oid has to be read from the fields according to the relations |
467 | ············WriteDependentForeignKeysToRow(pc, pcClass, row); |
468 | |
469 | ············if ((object)pc.NDOObjectId == null) |
470 | ············{ |
471 | ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager); |
472 | ············} |
473 | |
474 | ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data |
475 | ············{ |
476 | ················if (fireIdGeneration) |
477 | ····················FireIdGenerationEvent(pcType, pc.NDOObjectId); |
478 | ················// At this place the oid might have been |
479 | ················// - deserialized (MergeObjectContainer) |
480 | ················// - created using NewObjectId |
481 | ················// - defined by the IdGenerationEvent |
482 | |
483 | ················// At this point we have a valid oid. |
484 | ················// If the object has a field mapped to the oid we have |
485 | ················// to write back the oid to the field |
486 | ················int i = 0; |
487 | ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement) |
488 | ················{ |
489 | ····················if (oidColumn.FieldName != null) |
490 | ····················{ |
491 | ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance); |
492 | ························fi.SetValue(pc, pc.NDOObjectId.Id[i]); |
493 | ····················} |
494 | ····················i++; |
495 | ················}); |
496 | |
497 | |
498 | |
499 | ················// Now write back the data into the row |
500 | ················pc.NDOObjectId.Id.ToRow(pcClass, row); |
501 | ············} |
502 | |
503 | ············ |
504 | ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment |
505 | ············dt.Rows.Add(row); |
506 | |
507 | ············cache.Register(pc); |
508 | |
509 | ············// new object that has never been written to the DS |
510 | ············pc.NDOObjectState = NDOObjectState.Created; |
511 | ············// Mark all Relations as loaded |
512 | ············SetRelationState(pc); |
513 | |
514 | ············if (checkRelations) |
515 | ············{ |
516 | ················// Handle related objects: |
517 | ················foreach(Relation r in pcClass.Relations) |
518 | ················{ |
519 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
520 | ····················{ |
521 | ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
522 | ························if(child != null) |
523 | ························{ |
524 | ····························AddRelatedObject(pc, r, child); |
525 | ························} |
526 | ····················} |
527 | ····················else |
528 | ····················{ |
529 | ························IList list = mappings.GetRelationContainer(pc, r); |
530 | ························if(list != null) |
531 | ························{ |
532 | ····························foreach(IPersistenceCapable relObj in list) |
533 | ····························{ |
534 | ································if (relObj != null) |
535 | ····································AddRelatedObject(pc, r, relObj); |
536 | ····························} |
537 | ························} |
538 | ····················} |
539 | ················} |
540 | ············} |
541 | |
542 | ············var relations··= CollectRelationStates(pc); |
543 | ············cache.Lock(pc, row, relations); |
544 | ········} |
545 | |
546 | |
547 | ········/// <summary> |
548 | ········/// Make an object persistent. |
549 | ········/// </summary> |
550 | ········/// <param name="o">the transient object that should be made persistent</param> |
551 | ········public void MakePersistent(object o) |
552 | ········{ |
553 | ············IPersistenceCapable pc = CheckPc(o); |
554 | |
555 | ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name); |
556 | ············//Debug.Indent(); |
557 | |
558 | ············if (pc.NDOObjectState != NDOObjectState.Transient) |
559 | ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump()); |
560 | |
561 | ············InternalMakePersistent(pc, true); |
562 | |
563 | ········} |
564 | |
565 | |
566 | |
567 | ········//········/// <summary> |
568 | ········//········/// Checks, if an object has a valid id, which was created by the database |
569 | ········//········/// </summary> |
570 | ········//········/// <param name="pc"></param> |
571 | ········//········/// <returns></returns> |
572 | ········//········private bool HasValidId(IPersistenceCapable pc) |
573 | ········//········{ |
574 | ········//············if (this.IdGenerationEvent != null) |
575 | ········//················return true; |
576 | ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient); |
577 | ········//········} |
578 | |
579 | |
580 | ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent) |
581 | ········{ |
582 | ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here. |
583 | ············if(r.HasSubclasses) |
584 | ············{ |
585 | ················// we don't support 1:n with foreign fields in subclasses because we would have to |
586 | ················// search for objects in all subclasses! Instead use a mapping table. |
587 | ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName); |
588 | ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table"); |
589 | ············} |
590 | |
591 | ············if (!makeRelObjPersistent) |
592 | ················MarkDirty(relObj); |
593 | ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn |
594 | ············DataRow relObjRow = this.cache.GetDataRow(relObj); |
595 | |
596 | ············if (relObjRow == null) |
597 | ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null"); |
598 | |
599 | ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow); |
600 | |
601 | ············if (relObj.NDOLoadState.LostRowInfo == null) |
602 | ············{ |
603 | ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow); |
604 | ············} |
605 | ············else |
606 | ············{ |
607 | ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id); |
608 | ············} |
609 | ········} |
610 | |
611 | ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj) |
612 | ········{ |
613 | ············switch(relObj.NDOObjectState) |
614 | ············{ |
615 | ················case NDOObjectState.Persistent: |
616 | ····················MarkDirty(relObj); |
617 | ····················break; |
618 | ················case NDOObjectState.Hollow: |
619 | ····················LoadData(relObj); |
620 | ····················MarkDirty(relObj); |
621 | ····················break; |
622 | ············} |
623 | |
624 | ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
625 | ············{ |
626 | ················IPersistenceCapable newpc; |
627 | ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null) |
628 | ················{ |
629 | ····················if (newpc != pc) |
630 | ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump()); |
631 | ················} |
632 | ················else |
633 | ················{ |
634 | ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc); |
635 | ················} |
636 | ············} |
637 | ············else |
638 | ············{ |
639 | ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal)) |
640 | ····················LoadRelation(relObj, r.ForeignRelation, true); |
641 | ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation); |
642 | ················if(l == null) |
643 | ················{ |
644 | ····················try |
645 | ····················{ |
646 | ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation); |
647 | ····················} |
648 | ····················catch |
649 | ····················{ |
650 | ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor."); |
651 | ····················} |
652 | ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l); |
653 | ················} |
654 | ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen. |
655 | ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so. |
656 | ················if (!ObjectListManipulator.Contains(l, pc)) |
657 | ····················l.Add(pc); |
658 | ············} |
659 | ············//AddRelatedObject(relObj, r.ForeignRelation, pc); |
660 | ········} |
661 | |
662 | |
663 | ········/// <summary> |
664 | ········/// Add a related object to the specified object. |
665 | ········/// </summary> |
666 | ········/// <param name="pc">the parent object</param> |
667 | ········/// <param name="fieldName">the field name of the relation</param> |
668 | ········/// <param name="relObj">the related object that should be added</param> |
669 | ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj) |
670 | ········{ |
671 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient); |
672 | ············Relation r = mappings.FindRelation(pc, fieldName); |
673 | ············AddRelatedObject(pc, r, relObj); |
674 | ········} |
675 | |
676 | ········/// <summary> |
677 | ········/// Core functionality to add an object to a relation container or relation field. |
678 | ········/// </summary> |
679 | ········/// <param name="pc"></param> |
680 | ········/// <param name="r"></param> |
681 | ········/// <param name="relObj"></param> |
682 | ········/// <param name="isMerging"></param> |
683 | ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging) |
684 | ········{ |
685 | ············ |
686 | ············// avoid recursion |
687 | ············if (!addLock.GetLock(relObj)) |
688 | ················return; |
689 | |
690 | ············try |
691 | ············{ |
692 | ················//TODO: We need a relation management, which is independent of |
693 | ················//the state management of an object. Currently the relation |
694 | ················//lists or elements are cached for restore, if an object is marked dirty. |
695 | ················//Thus we have to mark dirty our parent object in any case at the moment. |
696 | ················MarkDirty(pc); |
697 | |
698 | ················//We should mark pc as dirty if we have a 1:1 w/o mapping table |
699 | ················//We should mark relObj as dirty if we have a 1:n w/o mapping table |
700 | ················//The latter happens in CreateAddedObjectRow |
701 | |
702 | ················Class relClass = GetClass(relObj); |
703 | |
704 | ················if (r.Multiplicity == RelationMultiplicity.Element |
705 | ····················&& r.HasSubclasses |
706 | ····················&& r.MappingTable == null················ |
707 | ····················&& !this.HasOwnerCreatedIds |
708 | ····················&& GetClass(pc).Oid.HasAutoincrementedColumn |
709 | ····················&& !relClass.HasGuidOid) |
710 | ················{ |
711 | ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient )) |
712 | ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table."); |
713 | ····················if (r.Composition) |
714 | ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's."); |
715 | ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient) |
716 | ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." ); |
717 | ················} |
718 | |
719 | ················bool isDependent = relClass.Oid.IsDependent; |
720 | |
721 | ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent) |
722 | ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List."); |
723 | |
724 | ················// Need to patch pc into the relation relObj->pc, because |
725 | ················// the oid is built on base of this information |
726 | ················if (isDependent) |
727 | ················{ |
728 | ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass); |
729 | ················} |
730 | |
731 | ················if (r.Composition || isDependent) |
732 | ················{ |
733 | ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient) |
734 | ························MakePersistent(relObj); |
735 | ················} |
736 | |
737 | ················if(r.MappingTable == null) |
738 | ················{ |
739 | ····················if (r.Bidirectional) |
740 | ····················{ |
741 | ························// This object hasn't been saved yet, so the key is wrong. |
742 | ························// Therefore, the child must be written twice to update the foreign key. |
743 | #if trace |
744 | ························System.Text.StringBuilder sb = new System.Text.StringBuilder(); |
745 | ························if (r.Multiplicity == RelationMultiplicity.Element) |
746 | ····························sb.Append("1"); |
747 | ························else |
748 | ····························sb.Append("n"); |
749 | ························sb.Append(":"); |
750 | ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
751 | ····························sb.Append("1"); |
752 | ························else |
753 | ····························sb.Append("n"); |
754 | ························sb.Append ("OwnCreatedOther"); |
755 | ························sb.Append(relObj.NDOObjectState.ToString()); |
756 | ························sb.Append(' '); |
757 | |
758 | ························sb.Append(types[0].ToString()); |
759 | ························sb.Append(' '); |
760 | ························sb.Append(types[1].ToString()); |
761 | ························Debug.WriteLine(sb.ToString()); |
762 | #endif |
763 | ························//························if (r.Multiplicity == RelationMultiplicity.Element |
764 | ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
765 | ························//························{ |
766 | ························// Element means: |
767 | ························// pc is keyholder |
768 | ························// -> relObj is saved first |
769 | ························// -> UpdateOrder(pc) > UpdateOrder(relObj) |
770 | ························// Both are Created - use type sort order |
771 | ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient) |
772 | ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn) |
773 | ························{ |
774 | ····························if (mappings.GetUpdateOrder(pc.GetType()) |
775 | ································< mappings.GetUpdateOrder(relObj.GetType())) |
776 | ································createdDirectObjects.Add(pc); |
777 | ····························else |
778 | ································createdDirectObjects.Add( relObj ); |
779 | ························} |
780 | ····················} |
781 | ····················if (r.Multiplicity == RelationMultiplicity.List) |
782 | ····················{ |
783 | ························CreateAddedObjectRow(pc, r, relObj, r.Composition); |
784 | ····················} |
785 | ················} |
786 | ················else |
787 | ················{ |
788 | ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r)); |
789 | ················} |
790 | ················if(r.Bidirectional) |
791 | ················{ |
792 | ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null) |
793 | ····················{ |
794 | ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element ) |
795 | ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc); |
796 | ····················} |
797 | ····················else if ( !addLock.IsLocked( pc ) ) |
798 | ····················{ |
799 | ························PatchForeignRelation( pc, r, relObj ); |
800 | ····················} |
801 | ················} |
802 | |
803 | ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) ); |
804 | ············} |
805 | ············finally |
806 | ············{ |
807 | ················addLock.Unlock(relObj); |
808 | ················//Debug.Unindent(); |
809 | ············} |
810 | ········} |
811 | |
812 | ········/// <summary> |
813 | ········/// Returns an integer value which determines the rank of the given type in the update order list. |
814 | ········/// </summary> |
815 | ········/// <param name="t">The type to determine the update order.</param> |
816 | ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns> |
817 | ········/// <remarks> |
818 | ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code. |
819 | ········/// </remarks> |
820 | ········public int GetUpdateRank(Type t) |
821 | ········{ |
822 | ············return mappings.GetUpdateOrder(t); |
823 | ········} |
824 | |
825 | ········/// <summary> |
826 | ········/// Add a related object to the specified object. |
827 | ········/// </summary> |
828 | ········/// <param name="pc">the parent object</param> |
829 | ········/// <param name="r">the relation mapping info</param> |
830 | ········/// <param name="relObj">the related object that should be added</param> |
831 | ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj) |
832 | ········{ |
833 | ············//············string idstr; |
834 | ············//············if (relObj.NDOObjectId == null) |
835 | ············//················idstr = relObj.GetType().ToString(); |
836 | ············//············else |
837 | ············//················idstr = relObj.NDOObjectId.Dump(); |
838 | ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr); |
839 | ············//Debug.Indent(); |
840 | |
841 | ············Class relClass = GetClass(relObj); |
842 | ············bool isDependent = relClass.Oid.IsDependent; |
843 | |
844 | ············// Do some checks to guarantee that the assignment is correct |
845 | ············if(r.Composition) |
846 | ············{ |
847 | ················if(relObj.NDOObjectState != NDOObjectState.Transient) |
848 | ················{ |
849 | ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + "."); |
850 | ················} |
851 | ············} |
852 | ············else |
853 | ············{ |
854 | ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent) |
855 | ················{ |
856 | ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + "."); |
857 | ················} |
858 | ············} |
859 | |
860 | ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType())) |
861 | ············{ |
862 | ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType()); |
863 | ············} |
864 | |
865 | ············InternalAddRelatedObject(pc, r, relObj, false); |
866 | |
867 | ········} |
868 | |
869 | ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass) |
870 | ········{ |
871 | ············// Need to patch pc into the relation relObj->pc, because |
872 | ············// the oid is built on base of this information |
873 | ············// The second relation has to be set before adding relObj |
874 | ············// to the relation list. |
875 | ············PatchForeignRelation(pc, r, relObj); |
876 | ············IPersistenceCapable parent; |
877 | ············foreach (Relation oidRelation in relClass.Oid.Relations) |
878 | ············{ |
879 | ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName); |
880 | ················if (parent == null) |
881 | ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects."); |
882 | ················if (parent.NDOObjectState == NDOObjectState.Transient) |
883 | ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent."); |
884 | |
885 | ············} |
886 | ········} |
887 | |
888 | |
889 | ········/// <summary> |
890 | ········/// Remove a related object from the specified object. |
891 | ········/// </summary> |
892 | ········/// <param name="pc">the parent object</param> |
893 | ········/// <param name="fieldName">Field name of the relation</param> |
894 | ········/// <param name="relObj">the related object that should be removed</param> |
895 | ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj) |
896 | ········{ |
897 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient); |
898 | ············Relation r = mappings.FindRelation(pc, fieldName); |
899 | ············InternalRemoveRelatedObject(pc, r, relObj, true); |
900 | ········} |
901 | |
902 | ········/// <summary> |
903 | ········/// Registers a listener which will be notified, if a new connection is opened. |
904 | ········/// </summary> |
905 | ········/// <param name="listener">Delegate of a listener function</param> |
906 | ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks> |
907 | ········public virtual void RegisterConnectionListener(OpenConnectionListener listener) |
908 | ········{ |
909 | ············this.openConnectionListener = listener; |
910 | ········} |
911 | |
912 | ········internal string OnNewConnection(NDO.Mapping.Connection conn) |
913 | ········{ |
914 | ············if (openConnectionListener != null) |
915 | ················return openConnectionListener(conn); |
916 | ············return conn.Name; |
917 | ········} |
918 | |
919 | |
920 | ········/* |
921 | ········doCommit should be: |
922 | ········ |
923 | ····················Query····Save····Save(true) |
924 | ········Optimistic····1········1········0 |
925 | ········Pessimistic····0········1········0 |
926 | ············ |
927 | ········Deferred Mode············ |
928 | ····················Query····Save····Save(true) |
929 | ········Optimistic····0········1········0 |
930 | ········Pessimistic····0········1········0 |
931 | ········ */ |
932 | |
933 | ········internal void CheckEndTransaction(bool doCommit) |
934 | ········{ |
935 | ············if (doCommit) |
936 | ············{ |
937 | ················TransactionScope.Complete(); |
938 | ············} |
939 | ········} |
940 | |
941 | ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t) |
942 | ········{ |
943 | ············CheckTransaction(handler, this.GetClass(t).Connection); |
944 | ········} |
945 | |
946 | ········/// <summary> |
947 | ········/// Each and every database operation has to be preceded by a call to this function. |
948 | ········/// </summary> |
949 | ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn ) |
950 | ········{ |
951 | ············TransactionScope.CheckTransaction(); |
952 | ············ |
953 | ············if (handler.Connection == null) |
954 | ············{ |
955 | ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () => |
956 | ················{ |
957 | ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn ); |
958 | ····················string connStr = this.OnNewConnection( ndoConn ); |
959 | ····················var connection = p.NewConnection( connStr ); |
960 | ····················if (connection == null) |
961 | ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." ); |
962 | ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" ); |
963 | ····················return connection; |
964 | ················} ); |
965 | ············} |
966 | |
967 | ············if (TransactionMode != TransactionMode.None) |
968 | ············{ |
969 | ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID ); |
970 | ············} |
971 | |
972 | ············// During the tests, we work with a handler mock that always returns zero for the Connection property. |
973 | ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open) |
974 | ············{ |
975 | ················handler.Connection.Open(); |
976 | ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" ); |
977 | ············} |
978 | ········} |
979 | |
980 | ········/// <summary> |
981 | ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler. |
982 | ········/// We try to tell the object which caused the concurrency exception, that a collicion occured. |
983 | ········/// This is possible if there is a listener for the CollisionEvent. |
984 | ········/// Else we throw an exception. |
985 | ········/// </summary> |
986 | ········/// <param name="ex">Concurrency Exception which was catched during update.</param> |
987 | ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex) |
988 | ········{ |
989 | ············DataRow row = ex.Row; |
990 | ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0) |
991 | ················throw(ex); |
992 | ············if (row.RowState == DataRowState.Detached) |
993 | ················return; |
994 | ············foreach (Cache.Entry e in cache.LockedObjects) |
995 | ············{ |
996 | ················if (e.row == row) |
997 | ················{ |
998 | ····················CollisionEvent(e.pc); |
999 | ····················return; |
1000 | ················} |
1001 | ············} |
1002 | ············throw ex; |
1003 | ········} |
1004 | |
1005 | |
1006 | ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex) |
1007 | ········{ |
1008 | ············Class cl = GetClass(pc); |
1009 | ············string[] etypes = cl.EmbeddedTypes.ToArray(); |
1010 | ············Dictionary<string,MemberInfo> persistentFields = null; |
1011 | ············if (etypes.Length > 0) |
1012 | ············{ |
1013 | ················FieldMap fm = new FieldMap(cl); |
1014 | ················persistentFields = fm.PersistentFields; |
1015 | ············} |
1016 | ············foreach(string s in etypes) |
1017 | ············{ |
1018 | ················try |
1019 | ················{ |
1020 | ····················NDO.Mapping.Field f = cl.FindField(s); |
1021 | ····················if (f == null) |
1022 | ························continue; |
1023 | ····················object o = row[f.Column.Name]; |
1024 | ····················string[] arr = s.Split('.'); |
1025 | ····················// Suche Embedded Type-Feld mit Namen arr[0] |
1026 | ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType()); |
1027 | ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance); |
1028 | ····················// Hole das Embedded Object |
1029 | ····················object parentOb = parentFi.GetValue(pc); |
1030 | |
1031 | ····················if (parentOb == null) |
1032 | ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0])); |
1033 | |
1034 | ····················// Suche darin das Feld mit Namen Arr[1] |
1035 | |
1036 | ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance); |
1037 | ····················Type childType = childFi.FieldType; |
1038 | |
1039 | ····················// Don't initialize value types, if DBNull is stored in the field. |
1040 | ····················// Exception: DateTime and Guid. |
1041 | ····················if (o == DBNull.Value && childType.IsValueType |
1042 | ························&& childType != typeof(Guid) |
1043 | ························&& childType != typeof(DateTime)) |
1044 | ························continue; |
1045 | |
1046 | ····················if (childType == typeof(DateTime)) |
1047 | ····················{ |
1048 | ························if (o == DBNull.Value) |
1049 | ····························o = DateTime.MinValue; |
1050 | ····················} |
1051 | ····················if (childType.IsClass) |
1052 | ····················{ |
1053 | ························if (o == DBNull.Value) |
1054 | ····························o = null; |
1055 | ····················} |
1056 | |
1057 | ····················if (childType == typeof (Guid)) |
1058 | ····················{ |
1059 | ························if (o == DBNull.Value) |
1060 | ····························o = Guid.Empty; |
1061 | ························if (o is string) |
1062 | ························{ |
1063 | ····························childFi.SetValue(parentOb, new Guid((string)o)); |
1064 | ························} |
1065 | ························else if (o is Guid) |
1066 | ························{ |
1067 | ····························childFi.SetValue(parentOb, o); |
1068 | ························} |
1069 | ························else if (o is byte[]) |
1070 | ························{ |
1071 | ····························childFi.SetValue(parentOb, new Guid((byte[])o)); |
1072 | ························} |
1073 | ························else |
1074 | ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName)); |
1075 | ····················} |
1076 | ····················else if (childType.IsSubclassOf(typeof(System.Enum))) |
1077 | ····················{ |
1078 | ························object childOb = childFi.GetValue(parentOb); |
1079 | ························FieldInfo valueFi = childType.GetField("value__"); |
1080 | ························valueFi.SetValue(childOb, o); |
1081 | ························childFi.SetValue(parentOb, childOb); |
1082 | ····················} |
1083 | ····················else |
1084 | ····················{ |
1085 | ························childFi.SetValue(parentOb, o); |
1086 | ····················} |
1087 | ················} |
1088 | ················catch (Exception ex) |
1089 | ················{ |
1090 | ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}"; |
1091 | |
1092 | ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message)); |
1093 | ················} |
1094 | |
1095 | ············} |
1096 | ············ |
1097 | ············try |
1098 | ············{ |
1099 | ················if (cl.HasEncryptedFields) |
1100 | ················{ |
1101 | ····················foreach (var field in cl.Fields.Where( f => f.Encrypted )) |
1102 | ····················{ |
1103 | ························string name = field.Column.Name; |
1104 | ························string s = (string) row[name]; |
1105 | ························string es = AesHelper.Decrypt( s, EncryptionKey ); |
1106 | ························row[name] = es; |
1107 | ····················} |
1108 | ················} |
1109 | ················pc.NDORead(row, fieldNames, startIndex); |
1110 | ············} |
1111 | ············catch (Exception ex) |
1112 | ············{ |
1113 | ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n" |
1114 | ····················+ ex.Message); |
1115 | ············} |
1116 | ········} |
1117 | |
1118 | ········/// <summary> |
1119 | ········/// Executes a sql script to generate the database tables. |
1120 | ········/// The function will execute any sql statements in the script |
1121 | ········/// which are valid according to the |
1122 | ········/// rules of the underlying database. Result sets are ignored. |
1123 | ········/// </summary> |
1124 | ········/// <param name="scriptFile">The script file to execute.</param> |
1125 | ········/// <param name="conn">A connection object, containing the connection |
1126 | ········/// string to the database, which should be altered.</param> |
1127 | ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns> |
1128 | ········/// <remarks> |
1129 | ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown. |
1130 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1131 | ········/// Their message property will appear in the result array. |
1132 | ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1133 | ········/// </remarks> |
1134 | ········public string[] BuildDatabase( string scriptFile, Connection conn ) |
1135 | ········{ |
1136 | ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 ); |
1137 | ········} |
1138 | |
1139 | ········/// <summary> |
1140 | ········/// Executes a sql script to generate the database tables. |
1141 | ········/// The function will execute any sql statements in the script |
1142 | ········/// which are valid according to the |
1143 | ········/// rules of the underlying database. Result sets are ignored. |
1144 | ········/// </summary> |
1145 | ········/// <param name="scriptFile">The script file to execute.</param> |
1146 | ········/// <param name="conn">A connection object, containing the connection |
1147 | ········/// string to the database, which should be altered.</param> |
1148 | ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param> |
1149 | ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns> |
1150 | ········/// <remarks> |
1151 | ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown. |
1152 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1153 | ········/// Their message property will appear in the result array. |
1154 | ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1155 | ········/// </remarks> |
1156 | ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding) |
1157 | ········{ |
1158 | ············StreamReader sr = new StreamReader(scriptFile, encoding); |
1159 | ············string s = sr.ReadToEnd(); |
1160 | ············sr.Close(); |
1161 | ············string[] arr = s.Split(';'); |
1162 | ············string last = arr[arr.Length - 1]; |
1163 | ············bool lastInvalid = (last == null || last.Trim() == string.Empty); |
1164 | ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)]; |
1165 | ············using (var handler = GetSqlPassThroughHandler()) |
1166 | ············{ |
1167 | ················int i = 0; |
1168 | ················string ok = "OK"; |
1169 | ················foreach (string statement in arr) |
1170 | ················{ |
1171 | ····················if (statement != null && statement.Trim() != string.Empty) |
1172 | ····················{ |
1173 | ························try |
1174 | ························{ |
1175 | ····························handler.Execute(statement); |
1176 | ····························result[i] = ok; |
1177 | ························} |
1178 | ························catch (Exception ex) |
1179 | ························{ |
1180 | ····························result[i] = ex.Message; |
1181 | ························} |
1182 | ····················} |
1183 | ····················i++; |
1184 | ················} |
1185 | ················CheckEndTransaction(true); |
1186 | ············} |
1187 | ············return result; |
1188 | ········} |
1189 | |
1190 | ········/// <summary> |
1191 | ········/// Executes a sql script to generate the database tables. |
1192 | ········/// The function will execute any sql statements in the script |
1193 | ········/// which are valid according to the |
1194 | ········/// rules of the underlying database. Result sets are ignored. |
1195 | ········/// </summary> |
1196 | ········/// <param name="scriptFile">The script file to execute.</param> |
1197 | ········/// <returns></returns> |
1198 | ········/// <remarks> |
1199 | ········/// This function takes the first Connection object in the Connections list |
1200 | ········/// of the Mapping file und executes the script using that connection. |
1201 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1202 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1203 | ········/// Their message property will appear in the result array. |
1204 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1205 | ········/// </remarks> |
1206 | ········public string[] BuildDatabase(string scriptFile) |
1207 | ········{ |
1208 | ············if (!File.Exists(scriptFile)) |
1209 | ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist."); |
1210 | ············if (!this.mappings.Connections.Any()) |
1211 | ················throw new NDOException(48, "Mapping file doesn't define a connection."); |
1212 | ············Connection conn = new Connection( this.mappings ); |
1213 | ············Connection originalConnection = (Connection)this.mappings.Connections.First(); |
1214 | ············conn.Name = OnNewConnection( originalConnection ); |
1215 | ············conn.Type = originalConnection.Type; |
1216 | ············//Connection conn = (Connection) this.mappings.Connections[0]; |
1217 | ············return BuildDatabase(scriptFile, conn); |
1218 | ········} |
1219 | |
1220 | ········/// <summary> |
1221 | ········/// Executes a sql script to generate the database tables. |
1222 | ········/// The function will execute any sql statements in the script |
1223 | ········/// which are valid according to the |
1224 | ········/// rules of the underlying database. Result sets are ignored. |
1225 | ········/// </summary> |
1226 | ········/// <returns> |
1227 | ········/// A string array, containing the error messages produced by the statements |
1228 | ········/// contained in the script. |
1229 | ········/// </returns> |
1230 | ········/// <remarks> |
1231 | ········/// The sql script is assumed to be the executable name of the entry assembly with the |
1232 | ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script. |
1233 | ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown. |
1234 | ········/// This function takes the first Connection object in the Connections list |
1235 | ········/// of the Mapping file und executes the script using that connection. |
1236 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1237 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1238 | ········/// Their message property will appear in the result array. |
1239 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1240 | ········/// </remarks> |
1241 | ········public string[] BuildDatabase() |
1242 | ········{ |
1243 | ············Assembly ass = Assembly.GetEntryAssembly(); |
1244 | ············if (ass == null) |
1245 | ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase."); |
1246 | ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql"); |
1247 | ············return BuildDatabase(file); |
1248 | ········} |
1249 | |
1250 | ········/// <summary> |
1251 | ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements. |
1252 | ········/// </summary> |
1253 | ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param> |
1254 | ········/// <returns>An ISqlPassThroughHandler implementation</returns> |
1255 | ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null ) |
1256 | ········{ |
1257 | ············if (!this.mappings.Connections.Any()) |
1258 | ················throw new NDOException( 48, "Mapping file doesn't define a connection." ); |
1259 | ············if (conn == null) |
1260 | ············{ |
1261 | ················conn = new Connection( this.mappings ); |
1262 | ················Connection originalConnection = (Connection) this.mappings.Connections.First(); |
1263 | ················conn.Name = OnNewConnection( originalConnection ); |
1264 | ················conn.Type = originalConnection.Type; |
1265 | ············} |
1266 | |
1267 | ············return new SqlPassThroughHandler( this, conn ); |
1268 | ········} |
1269 | |
1270 | ········/// <summary> |
1271 | ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements. |
1272 | ········/// </summary> |
1273 | ········/// <param name="predicate">A predicate defining which connection has to be used.</param> |
1274 | ········/// <returns>An ISqlPassThroughHandler implementation</returns> |
1275 | ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate ) |
1276 | ········{ |
1277 | ············if (!this.mappings.Connections.Any()) |
1278 | ················throw new NDOException( 48, "The Mapping file doesn't define a connection." ); |
1279 | ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate ); |
1280 | ············if (conn == null) |
1281 | ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." ); |
1282 | ············return GetSqlPassThroughHandler( conn ); |
1283 | ········} |
1284 | |
1285 | ········/// <summary> |
1286 | ········/// Executes a xml script to generate the database tables. |
1287 | ········/// The function will generate and execute sql statements to perform |
1288 | ········/// the changes described by the xml. |
1289 | ········/// </summary> |
1290 | ········/// <returns></returns> |
1291 | ········/// <remarks> |
1292 | ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml. |
1293 | ········/// If several files match the search string biggest file name in the default sort order will be executed. |
1294 | ········/// This function takes the first Connection object in the Connections list |
1295 | ········/// of the Mapping file und executes the script using that connection. |
1296 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1297 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1298 | ········/// Their message property will appear in the result string array. |
1299 | ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown. |
1300 | ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry. |
1301 | ········/// </remarks> |
1302 | ········public string[] PerformSchemaTransitions() |
1303 | ········{ |
1304 | ············Assembly ass = Assembly.GetEntryAssembly(); |
1305 | ············if (ass == null) |
1306 | ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions."); |
1307 | ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml"; |
1308 | ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList(); |
1309 | ············if (fileNames.Count == 0) |
1310 | ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) }; |
1311 | ············if (fileNames.Count > 1) |
1312 | ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) ); |
1313 | ············return PerformSchemaTransitions( fileNames[0] ); |
1314 | ········} |
1315 | |
1316 | |
1317 | ········/// <summary> |
1318 | ········/// Executes a xml script to generate the database tables. |
1319 | ········/// The function will generate and execute sql statements to perform |
1320 | ········/// the changes described by the xml. |
1321 | ········/// </summary> |
1322 | ········/// <param name="scriptFile">The script file to execute.</param> |
1323 | ········/// <returns></returns> |
1324 | ········/// <remarks> |
1325 | ········/// This function takes the first Connection object in the Connections list |
1326 | ········/// of the Mapping file und executes the script using that connection. |
1327 | ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown. |
1328 | ········/// Any exceptions thrown while executing a statement in the script, will be caught. |
1329 | ········/// Their message property will appear in the result string array. |
1330 | ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown. |
1331 | ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry. |
1332 | ········/// </remarks> |
1333 | ········public string[] PerformSchemaTransitions(string scriptFile) |
1334 | ········{ |
1335 | ············if (!File.Exists(scriptFile)) |
1336 | ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist."); |
1337 | |
1338 | ············if (!this.mappings.Connections.Any()) |
1339 | ················throw new NDOException(48, "Mapping file doesn't define any connection."); |
1340 | ············Connection conn = new Connection( mappings ); |
1341 | ············Connection originalConnection = mappings.Connections.First(); |
1342 | ············conn.Name = OnNewConnection( originalConnection ); |
1343 | ············conn.Type = originalConnection.Type; |
1344 | ············return PerformSchemaTransitions(scriptFile, conn); |
1345 | ········} |
1346 | |
1347 | |
1348 | ········int CompareFileName( string fn1, string fn2) |
1349 | ········{ |
1350 | ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" ); |
1351 | ············Match match = regex.Match( fn1 ); |
1352 | ············string v1 = match.Groups[1].Value; |
1353 | ············match = regex.Match( fn2 ); |
1354 | ············string v2 = match.Groups[1].Value; |
1355 | ············return new Version( v2 ).CompareTo( new Version( v1 ) ); |
1356 | ········} |
1357 | |
1358 | ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider) |
1359 | ········{ |
1360 | ············var connection = provider.NewConnection( ndoConn.Name ); |
1361 | ············var resultList = new List<Guid>(); |
1362 | |
1363 | ············using (var handler = GetSqlPassThroughHandler()) |
1364 | ············{ |
1365 | ················string[] TableNames = provider.GetTableNames( connection ); |
1366 | ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 )) |
1367 | ················{ |
1368 | ····················var schemaIds = provider.GetQualifiedTableName("NDOSchemaIds"); |
1369 | ····················var sn = provider.GetQuotedName("SchemaName"); |
1370 | ····················var id = provider.GetQuotedName("Id"); |
1371 | ····················string sql = $"SELECT {id} from {schemaIds} WHERE {sn} "; |
1372 | ····················if (String.IsNullOrEmpty(schemaName)) |
1373 | ························sql += "IS NULL;"; |
1374 | ····················else |
1375 | ························sql += $"LIKE '{schemaName}'"; |
1376 | |
1377 | ····················using(IDataReader dr = handler.Execute(sql, true)) |
1378 | ····················{ |
1379 | ························while (dr.Read()) |
1380 | ····························resultList.Add( dr.GetGuid( 0 ) ); |
1381 | ····················} |
1382 | ················} |
1383 | ················else |
1384 | ················{ |
1385 | ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings ); |
1386 | ····················var gt = typeof(Guid); |
1387 | ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }"; |
1388 | ····················var st = typeof(String); |
1389 | ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }"; |
1390 | ····················var dt = typeof(DateTime); |
1391 | ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }"; |
1392 | ····················string transition = $@"<NdoSchemaTransition> |
1393 | ····<CreateTable name=""NDOSchemaIds""> |
1394 | ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" /> |
1395 | ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" isPrimary=""True"" /> |
1396 | ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" /> |
1397 | ····</CreateTable> |
1398 | </NdoSchemaTransition>"; |
1399 | ····················XElement transitionElement = XElement.Parse(transition); |
1400 | |
1401 | ····················string sql = schemaTransitionGenerator.Generate( transitionElement ); |
1402 | ····················handler.Execute(sql); |
1403 | ················} |
1404 | ················handler.CommitTransaction(); |
1405 | ············} |
1406 | |
1407 | ············return resultList.ToArray(); |
1408 | ········} |
1409 | |
1410 | ········/// <summary> |
1411 | ········/// Executes a xml script to generate the database tables. |
1412 | ········/// The function will generate and execute sql statements to perform |
1413 | ········/// the changes described by the xml. |
1414 | ········/// </summary> |
1415 | ········/// <param name="scriptFile">The xml script file.</param> |
1416 | ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param> |
1417 | ········/// <returns>A list of strings about the states of the different schema change commands.</returns> |
1418 | ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks> |
1419 | ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn) |
1420 | ········{ |
1421 | ············string schemaName = null; |
1422 | ············// Gespeicherte Version ermitteln. |
1423 | ············XElement transitionElements = XElement.Load( scriptFile ); |
1424 | ············if (transitionElements.Attribute( "schemaName" ) != null) |
1425 | ················schemaName = transitionElements.Attribute( "schemaName" ).Value; |
1426 | |
1427 | ············IProvider provider = this.mappings.GetProvider( ndoConn ); |
1428 | ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider ); |
1429 | ············var newIds = new List<Guid>(); |
1430 | ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings ); |
1431 | |
1432 | // dtLiteral contains the leading and trailing quotes |
1433 | var dtLiteral = provider. GetSqlLiteral( DateTime. Now ) ; |
1434 | ············var ndoSchemaIds = provider.GetQualifiedTableName("NDOSchemaIds"); |
1435 | ············var schName = provider.GetQuotedName("SchemaName"); |
1436 | ············var idCol = provider.GetQuotedName("Id"); |
1437 | ············var insertTime = provider.GetQuotedName("InsertTime"); |
1438 | ············var results = new List<string>(); |
1439 | |
1440 | // Each transitionElement gets it's own transaction. |
1441 | ············// The insert into NDOSchemaIds is part of the transaction. |
1442 | ············// If an error occurs, InternalPerformSchemaTransitions aborts the transaction. |
1443 | ············foreach (XElement transitionElement in transitionElements.Elements( "NdoSchemaTransition" )) |
1444 | ············{ |
1445 | ················var id = transitionElement.Attribute("id")?.Value; |
1446 | ················if (id == null) |
1447 | ····················continue; |
1448 | ················var gid = new Guid(id); |
1449 | ················if (installedIds.Contains( gid )) |
1450 | ····················continue; |
1451 | |
1452 | var sb = new StringBuilder( ) ; |
1453 | ················sb.Append( schemaTransitionGenerator.Generate( transitionElement ) ); |
1454 | ················newIds.Add( gid ); |
1455 | |
1456 | sb. Append( $"INSERT INTO { ndoSchemaIds} ( { schName} , { idCol} , { insertTime} ) VALUES ( '{ schemaName} ', '{ gid} ', { dtLiteral} ) ;" ) ; |
1457 | ················ |
1458 | results. AddRange( InternalPerformSchemaTransitions( ndoConn, sb. ToString( ) ) ) ; |
1459 | ············} |
1460 | |
1461 | return results. ToArray( ) ; |
1462 | ········} |
1463 | |
1464 | ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql ) |
1465 | ········{ |
1466 | ············string[] arr = sql.Split( ';' ); |
1467 | |
1468 | ············string last = arr[arr.Length - 1]; |
1469 | ············bool lastInvalid = (last == null || last.Trim() == string.Empty); |
1470 | ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)]; |
1471 | ············int i = 0; |
1472 | ············string ok = "OK"; |
1473 | ············using (var handler = GetSqlPassThroughHandler()) |
1474 | ············{ |
1475 | ················handler.BeginTransaction(); |
1476 | ················var doCommit = true; |
1477 | ················foreach (string statement in arr) |
1478 | ················{ |
1479 | ····················if (!String.IsNullOrWhiteSpace(statement)) |
1480 | ····················{ |
1481 | ························try |
1482 | ························{ |
1483 | ····························handler.Execute( statement.Trim() ); |
1484 | ····························result[i] = ok; |
1485 | ························} |
1486 | ························catch (Exception ex) |
1487 | ························{ |
1488 | ····························result[i] = ex.Message; |
1489 | ····························doCommit = false; |
1490 | ························} |
1491 | ····················} |
1492 | ····················i++; |
1493 | ················} |
1494 | ················if (doCommit) |
1495 | ····················handler.CommitTransaction(); |
1496 | ················else |
1497 | ····················AbortTransaction(); |
1498 | ············} |
1499 | |
1500 | ············return result; |
1501 | ········} |
1502 | ········ |
1503 | ········/// <summary> |
1504 | ········/// Transfers Data from the object to the DataRow |
1505 | ········/// </summary> |
1506 | ········/// <param name="pc"></param> |
1507 | ········/// <param name="row"></param> |
1508 | ········/// <param name="fieldNames"></param> |
1509 | ········/// <param name="startIndex"></param> |
1510 | ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex) |
1511 | ········{ |
1512 | ············Class cl = GetClass( pc ); |
1513 | ············try |
1514 | ············{ |
1515 | ················pc.NDOWrite(row, fieldNames, startIndex); |
1516 | ················if (cl.HasEncryptedFields) |
1517 | ················{ |
1518 | ····················foreach (var field in cl.Fields.Where( f => f.Encrypted )) |
1519 | ····················{ |
1520 | ························string name = field.Column.Name; |
1521 | ························string s = (string) row[name]; |
1522 | ························string es = AesHelper.Encrypt( s, EncryptionKey ); |
1523 | ························row[name] = es; |
1524 | ····················} |
1525 | ················} |
1526 | ············} |
1527 | ············catch (Exception ex) |
1528 | ············{ |
1529 | ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n" |
1530 | ····················+ ex.Message); |
1531 | ············} |
1532 | |
1533 | ············if (cl.TypeNameColumn != null) |
1534 | ············{ |
1535 | ················Type t = pc.GetType(); |
1536 | ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name; |
1537 | ············} |
1538 | |
1539 | ············var etypes = cl.EmbeddedTypes; |
1540 | ············foreach(string s in etypes) |
1541 | ············{ |
1542 | ················try |
1543 | ················{ |
1544 | ····················NDO.Mapping.Field f = cl.FindField(s); |
1545 | ····················if (f == null) |
1546 | ························continue; |
1547 | ····················string[] arr = s.Split('.'); |
1548 | ····················// Suche Feld mit Namen arr[0] als object |
1549 | ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType()); |
1550 | ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance); |
1551 | ····················Object parentOb = parentFi.GetValue(pc); |
1552 | ····················if (parentOb == null) |
1553 | ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0])); |
1554 | ····················// Suche darin das Feld mit Namen Arr[1] |
1555 | ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance); |
1556 | ····················object o = childFi.GetValue(parentOb); |
1557 | ····················if (o == null |
1558 | ························|| o is DateTime && (DateTime) o == DateTime.MinValue |
1559 | ························|| o is Guid && (Guid) o == Guid.Empty) |
1560 | ························o = DBNull.Value; |
1561 | ····················row[f.Column.Name] = o; |
1562 | ················} |
1563 | ················catch (Exception ex) |
1564 | ················{ |
1565 | ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}"; |
1566 | |
1567 | ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message)); |
1568 | ················} |
1569 | ············} |
1570 | ········} |
1571 | |
1572 | ········/// <summary> |
1573 | ········/// Check, if the specific field is loaded. If not, LoadData will be called. |
1574 | ········/// </summary> |
1575 | ········/// <param name="o">The parent object.</param> |
1576 | ········/// <param name="fieldOrdinal">A number to identify the field.</param> |
1577 | ········public virtual void LoadField(object o, int fieldOrdinal) |
1578 | ········{ |
1579 | ············IPersistenceCapable pc = CheckPc(o); |
1580 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1581 | ············{ |
1582 | ················LoadState ls = pc.NDOLoadState; |
1583 | ················if (ls.FieldLoadState != null) |
1584 | ················{ |
1585 | ····················if (ls.FieldLoadState[fieldOrdinal]) |
1586 | ························return; |
1587 | ················} |
1588 | ················else |
1589 | ················{ |
1590 | ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() ); |
1591 | ················} |
1592 | ················LoadData(o); |
1593 | ········} |
1594 | ········} |
1595 | |
1596 | #pragma warning disable 419 |
1597 | ········/// <summary> |
1598 | ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent. |
1599 | ········/// </summary> |
1600 | ········/// <param name="o">The hollow object.</param> |
1601 | ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks> |
1602 | #pragma warning restore 419 |
1603 | ········public virtual void LoadData( object o ) |
1604 | ········{ |
1605 | ············IPersistenceCapable pc = CheckPc(o); |
1606 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects"); |
1607 | ············if (pc.NDOObjectState != NDOObjectState.Hollow) |
1608 | ················return; |
1609 | ············Class cl = GetClass(pc); |
1610 | ············IQuery q; |
1611 | ············q = CreateOidQuery(pc, cl); |
1612 | ············cache.UpdateCache(pc); // Make sure the object is in the cache |
1613 | |
1614 | ············var objects = q.Execute(); |
1615 | ············var count = objects.Count; |
1616 | |
1617 | ············if (count > 1) |
1618 | ············{ |
1619 | ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" ); |
1620 | ············} |
1621 | ············else if (count == 0) |
1622 | ············{ |
1623 | ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc)) |
1624 | ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." ); |
1625 | ············} |
1626 | ········} |
1627 | |
1628 | ········/// <summary> |
1629 | ········/// Creates a new IQuery object for the given type |
1630 | ········/// </summary> |
1631 | ········/// <param name="t"></param> |
1632 | ········/// <param name="oql"></param> |
1633 | ········/// <param name="hollow"></param> |
1634 | ········/// <param name="queryLanguage"></param> |
1635 | ········/// <returns></returns> |
1636 | ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql) |
1637 | ········{ |
1638 | ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition(); |
1639 | ············Type qt = template.MakeGenericType( t ); |
1640 | ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage ); |
1641 | ········} |
1642 | |
1643 | ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl) |
1644 | ········{ |
1645 | ············ArrayList parameters = new ArrayList(); |
1646 | ············string oql = "oid = {0}"; |
1647 | ············IQuery q = NewQuery(pc.GetType(), oql, false); |
1648 | ············q.Parameters.Add( pc.NDOObjectId ); |
1649 | ············q.AllowSubclasses = false; |
1650 | ············return q; |
1651 | ········} |
1652 | |
1653 | ········/// <summary> |
1654 | ········/// Mark the object dirty. The current state is |
1655 | ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the |
1656 | ········/// object is locked in the cache. |
1657 | ········/// </summary> |
1658 | ········/// <param name="pc"></param> |
1659 | ········internal virtual void MarkDirty(IPersistenceCapable pc) |
1660 | ········{ |
1661 | ············if (pc.NDOObjectState != NDOObjectState.Persistent) |
1662 | ················return; |
1663 | ············SaveObjectState(pc); |
1664 | ············pc.NDOObjectState = NDOObjectState.PersistentDirty; |
1665 | ········} |
1666 | |
1667 | ········/// <summary> |
1668 | ········/// Mark the object dirty, but make sure first, that the object is loaded. |
1669 | ········/// The current or loaded state is saved in a DataRow, which is stored in the DS. |
1670 | ········/// This is done, to allow easy rollback later. Also, the |
1671 | ········/// object is locked in the cache. |
1672 | ········/// </summary> |
1673 | ········/// <param name="pc"></param> |
1674 | ········internal void LoadAndMarkDirty(IPersistenceCapable pc) |
1675 | ········{ |
1676 | ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty."); |
1677 | |
1678 | ············if(pc.NDOObjectState == NDOObjectState.Deleted) |
1679 | ············{ |
1680 | ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed."); |
1681 | ············} |
1682 | |
1683 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1684 | ················LoadData(pc); |
1685 | |
1686 | ············// state is either (Created), Persistent, PersistentDirty |
1687 | ············if(pc.NDOObjectState == NDOObjectState.Persistent) |
1688 | ············{ |
1689 | ················MarkDirty(pc); |
1690 | ············} |
1691 | ········} |
1692 | |
1693 | |
1694 | |
1695 | ········/// <summary> |
1696 | ········/// Save current object state in DS and lock the object in the cache. |
1697 | ········/// The saved state can be used later to retrieve the original object value if the |
1698 | ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored. |
1699 | ········/// </summary> |
1700 | ········/// <param name="pc">The object that should be saved</param> |
1701 | ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param> |
1702 | ········/// <remarks> |
1703 | ········/// In a data row there are the following things: |
1704 | ········/// Item································Responsible for writing |
1705 | ········/// State (own, inherited, embedded)····WriteObject |
1706 | ········/// TimeStamp····························NDOPersistenceHandler |
1707 | ········/// Oid····································WriteId |
1708 | ········/// Foreign Keys and their Type Codes····WriteForeignKeys |
1709 | ········/// </remarks> |
1710 | ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false) |
1711 | ········{ |
1712 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState); |
1713 | ············ |
1714 | ············DataTable table = GetTable(pc); |
1715 | ············DataRow row = table.NewRow(); |
1716 | ············Class cl = GetClass(pc); |
1717 | ············WriteObject(pc, row, cl.ColumnNames, 0); |
1718 | ············WriteIdToRow(pc, row); |
1719 | ············if (!isDeleting) |
1720 | ················WriteLostForeignKeysToRow(cl, pc, row); |
1721 | ············table.Rows.Add(row); |
1722 | ············row.AcceptChanges(); |
1723 | ············ |
1724 | ············var relations = CollectRelationStates(pc); |
1725 | ············cache.Lock(pc, row, relations); |
1726 | ········} |
1727 | |
1728 | ········private void SaveFakeRow(IPersistenceCapable pc) |
1729 | ········{ |
1730 | ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState); |
1731 | ············ |
1732 | ············DataTable table = GetTable(pc); |
1733 | ············DataRow row = table.NewRow(); |
1734 | ············Class pcClass = GetClass(pc); |
1735 | ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker); |
1736 | ············Class cl = GetClass(pc); |
1737 | ············//WriteObject(pc, row, cl.FieldNames, 0); |
1738 | ············WriteIdToRow(pc, row); |
1739 | ············table.Rows.Add(row); |
1740 | ············row.AcceptChanges(); |
1741 | ············ |
1742 | ············cache.Lock(pc, row, null); |
1743 | ········} |
1744 | |
1745 | ········/// <summary> |
1746 | ········/// This defines one column of the row, in which we use the |
1747 | ········/// ColumnError property to determine, if the row is a fake row. |
1748 | ········/// </summary> |
1749 | ········/// <param name="pcClass"></param> |
1750 | ········/// <returns></returns> |
1751 | ········private string GetFakeRowOidColumnName(Class pcClass) |
1752 | ········{ |
1753 | ············// In case of several OidColumns the first column defined in the mapping |
1754 | ············// will be the one, holding the fake row info. |
1755 | ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name; |
1756 | ········} |
1757 | |
1758 | ········private bool IsFakeRow(Class cl, DataRow row) |
1759 | ········{ |
1760 | ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker); |
1761 | ········} |
1762 | |
1763 | ········/// <summary> |
1764 | ········/// Make a list of objects persistent. |
1765 | ········/// </summary> |
1766 | ········/// <param name="list">the list of IPersistenceCapable objects</param> |
1767 | ········public void MakePersistent(System.Collections.IList list) |
1768 | ········{ |
1769 | ············foreach (IPersistenceCapable pc in list) |
1770 | ············{ |
1771 | ················MakePersistent(pc); |
1772 | ············} |
1773 | ········} |
1774 | |
1775 | ········/// <summary> |
1776 | ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are |
1777 | ········/// not duplicated. |
1778 | ········/// </summary> |
1779 | ········/// <param name="pc">the parent object of all relations</param> |
1780 | ········/// <returns></returns> |
1781 | ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc) |
1782 | ········{ |
1783 | ············// Save state of relations |
1784 | ············Class c = GetClass(pc); |
1785 | ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count()); |
1786 | ············foreach(Relation r in c.Relations) |
1787 | ············{ |
1788 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
1789 | ················{ |
1790 | ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) ); |
1791 | ················} |
1792 | ················else |
1793 | ················{ |
1794 | ····················IList l = mappings.GetRelationContainer(pc, r); |
1795 | ····················if(l != null) |
1796 | ····················{ |
1797 | ························l = (IList) ListCloner.CloneList(l); |
1798 | ····················} |
1799 | ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) ); |
1800 | ················} |
1801 | ············} |
1802 | |
1803 | ············return relations; |
1804 | ········} |
1805 | |
1806 | |
1807 | ········/// <summary> |
1808 | ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently |
1809 | ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state, |
1810 | ········/// the list had at the beginning of the transaction. |
1811 | ········/// </summary> |
1812 | ········/// <param name="pc"></param> |
1813 | ········/// <param name="relations"></param> |
1814 | ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations ) |
1815 | ········{ |
1816 | ············Class c = GetClass(pc); |
1817 | |
1818 | ············foreach(var entry in relations) |
1819 | ············{ |
1820 | ················var r = entry.Key; |
1821 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
1822 | ················{ |
1823 | ····················mappings.SetRelationField(pc, r.FieldName, entry.Value); |
1824 | ················} |
1825 | ················else |
1826 | ················{ |
1827 | ····················if (pc.NDOGetLoadState(r.Ordinal)) |
1828 | ····················{ |
1829 | ························// Help GC by clearing lists |
1830 | ························IList l = mappings.GetRelationContainer(pc, r); |
1831 | ························if(l != null) |
1832 | ························{ |
1833 | ····························l.Clear(); |
1834 | ························} |
1835 | ························// Restore relation |
1836 | ························mappings.SetRelationContainer(pc, r, (IList)entry.Value); |
1837 | ····················} |
1838 | ················} |
1839 | ············} |
1840 | ········} |
1841 | |
1842 | |
1843 | ········/// <summary> |
1844 | ········/// Generates a query for related objects without mapping table. |
1845 | ········/// Note: this function can't be called in polymorphic scenarios, |
1846 | ········/// since they need a mapping table. |
1847 | ········/// </summary> |
1848 | ········/// <returns></returns> |
1849 | ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow) |
1850 | ········{ |
1851 | ············// At this point of execution we know, |
1852 | ············// that the target type is not polymorphic and is not 1:1. |
1853 | |
1854 | ············// We can't fetch these objects with an NDOql query |
1855 | ············// since this would require a relation in the opposite direction |
1856 | |
1857 | ············IList relatedObjects; |
1858 | ············if (l != null) |
1859 | ················relatedObjects = l; |
1860 | ············else |
1861 | ················relatedObjects = mappings.CreateRelationContainer( pc, r ); |
1862 | |
1863 | ············Type t = r.ReferencedType; |
1864 | ············Class cl = GetClass( t ); |
1865 | ············var provider = cl.Provider; |
1866 | |
1867 | ············StringBuilder sb = new StringBuilder("SELECT * FROM "); |
1868 | ············var relClass = GetClass( r.ReferencedType ); |
1869 | ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() ); |
1870 | ············sb.Append( " WHERE " ); |
1871 | ············int i = 0; |
1872 | ············List<object> parameters = new List<object>(); |
1873 | ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement ) |
1874 | ·············· { |
1875 | ·················· sb.Append( fkColumn.GetQualifiedName(relClass) ); |
1876 | ·················· sb.Append( " = {" ); |
1877 | ·················· sb.Append(i); |
1878 | ·················· sb.Append( '}' ); |
1879 | ·················· parameters.Add( pc.NDOObjectId.Id[i] ); |
1880 | ·················· if (!isLastElement) |
1881 | ······················ sb.Append( " AND " ); |
1882 | ·················· i++; |
1883 | ·············· } ); |
1884 | |
1885 | ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName ))) |
1886 | ············{ |
1887 | ················sb.Append( " AND " ); |
1888 | ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) ); |
1889 | ················sb.Append( " = " ); |
1890 | ················sb.Append( pc.NDOObjectId.Id.TypeId ); |
1891 | ············} |
1892 | |
1893 | ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql ); |
1894 | |
1895 | ············foreach (var p in parameters) |
1896 | ············{ |
1897 | ················q.Parameters.Add( p ); |
1898 | ············} |
1899 | |
1900 | ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table |
1901 | |
1902 | ············IList l2 = q.Execute(); |
1903 | |
1904 | ············foreach (object o in l2) |
1905 | ················relatedObjects.Add( o ); |
1906 | |
1907 | ············return relatedObjects; |
1908 | ········} |
1909 | |
1910 | |
1911 | ········/// <summary> |
1912 | ········/// Resolves an relation. The loaded objects will be hollow. |
1913 | ········/// </summary> |
1914 | ········/// <param name="o">The parent object.</param> |
1915 | ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param> |
1916 | ········/// <param name="hollow">True, if the fetched objects should be hollow.</param> |
1917 | ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks> |
1918 | ········public virtual void LoadRelation(object o, string fieldName, bool hollow) |
1919 | ········{ |
1920 | ············IPersistenceCapable pc = CheckPc(o); |
1921 | ············LoadRelationInternal(pc, fieldName, hollow); |
1922 | ········} |
1923 | |
1924 | ········ |
1925 | |
1926 | ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow) |
1927 | ········{ |
1928 | ············IList result = null; |
1929 | |
1930 | ············if (pc.NDOObjectState == NDOObjectState.Created) |
1931 | ················return null; |
1932 | |
1933 | ············if (pc.NDOObjectState == NDOObjectState.Hollow) |
1934 | ················LoadData(pc); |
1935 | |
1936 | ············if(r.MappingTable == null) |
1937 | ············{ |
1938 | ················// 1:1 are loaded with LoadData |
1939 | ················if (r.Multiplicity == RelationMultiplicity.List) |
1940 | ················{ |
1941 | ····················// Help GC by clearing lists |
1942 | ····················IList l = mappings.GetRelationContainer(pc, r); |
1943 | ····················if(l != null) |
1944 | ························l.Clear(); |
1945 | ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow); |
1946 | ····················mappings.SetRelationContainer(pc, r, relatedObjects); |
1947 | ····················result = relatedObjects; |
1948 | ················} |
1949 | ············} |
1950 | ············else |
1951 | ············{ |
1952 | ················DataTable dt = null; |
1953 | |
1954 | ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r )) |
1955 | ················{ |
1956 | ····················CheckTransaction( handler, r.MappingTable.Connection ); |
1957 | ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds); |
1958 | ················} |
1959 | |
1960 | ················IList relatedObjects; |
1961 | ················if(r.Multiplicity == RelationMultiplicity.Element) |
1962 | ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count); |
1963 | ················else |
1964 | ················{ |
1965 | ····················relatedObjects = mappings.GetRelationContainer(pc, r); |
1966 | ····················if(relatedObjects != null) |
1967 | ························relatedObjects.Clear();··// Objects will be reread |
1968 | ····················else |
1969 | ························relatedObjects = mappings.CreateRelationContainer(pc, r); |
1970 | ················} |
1971 | ···················· |
1972 | ················foreach(DataRow objRow in dt.Rows) |
1973 | ················{ |
1974 | ····················Type relType; |
1975 | |
1976 | ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························ |
1977 | ····················{ |
1978 | ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName]; |
1979 | ························if (typeCodeObj is System.DBNull) |
1980 | ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) ); |
1981 | ························relType = typeManager[(int)typeCodeObj]; |
1982 | ························if (relType == null) |
1983 | ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName)); |
1984 | ····················}························ |
1985 | ····················else |
1986 | ····················{ |
1987 | ························relType = r.ReferencedType; |
1988 | ····················} |
1989 | |
1990 | ····················//TODO: Generic Types: Exctract the type description from the type name column |
1991 | ····················if (relType.IsGenericTypeDefinition) |
1992 | ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables."); |
1993 | ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager); |
1994 | ····················IPersistenceCapable relObj = FindObject(id); |
1995 | ····················relatedObjects.Add(relObj); |
1996 | ················}···· |
1997 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
1998 | ················{ |
1999 | ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1"); |
2000 | ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null); |
2001 | ················} |
2002 | ················else |
2003 | ················{ |
2004 | ····················mappings.SetRelationContainer(pc, r, relatedObjects); |
2005 | ····················result = relatedObjects; |
2006 | ················} |
2007 | ············} |
2008 | ············// Mark relation as loaded |
2009 | ············pc.NDOSetLoadState(r.Ordinal, true); |
2010 | ············return result; |
2011 | ········} |
2012 | |
2013 | ········/// <summary> |
2014 | ········/// Loads elements of a relation |
2015 | ········/// </summary> |
2016 | ········/// <param name="pc">The object which needs to load the relation</param> |
2017 | ········/// <param name="relationName">The name of the relation</param> |
2018 | ········/// <param name="hollow">Determines, if the related objects should be hollow.</param> |
2019 | ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow) |
2020 | ········{ |
2021 | ············if (pc.NDOObjectState == NDOObjectState.Created) |
2022 | ················return null; |
2023 | ············Class cl = GetClass(pc); |
2024 | |
2025 | ············Relation r = cl.FindRelation(relationName); |
2026 | |
2027 | ············if ( r == null ) |
2028 | ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) ); |
2029 | |
2030 | ············if ( pc.NDOGetLoadState( r.Ordinal ) ) |
2031 | ················return null; |
2032 | |
2033 | ············return LoadRelation(pc, r, hollow); |
2034 | ········} |
2035 | |
2036 | ········/// <summary> |
2037 | ········/// Load the related objects of a parent object. The current value of the relation is replaced by the |
2038 | ········/// a list of objects that has been read from the DB. |
2039 | ········/// </summary> |
2040 | ········/// <param name="pc">The parent object of the relations</param> |
2041 | ········/// <param name="row">A data row containing the state of the object</param> |
2042 | ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row) |
2043 | ········{ |
2044 | ············// Stripped down to only serve 1:1-Relations w/out mapping table |
2045 | ············Class cl = GetClass(pc); |
2046 | ············foreach(Relation r in cl.Relations) |
2047 | ············{ |
2048 | ················if(r.MappingTable == null) |
2049 | ················{ |
2050 | ····················if (r.Multiplicity == RelationMultiplicity.Element) |
2051 | ····················{ |
2052 | ························bool isNull = false; |
2053 | ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns) |
2054 | ························{ |
2055 | ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value); |
2056 | ························} |
2057 | ························if (isNull) |
2058 | ························{ |
2059 | ····························mappings.SetRelationField(pc, r.FieldName, null); |
2060 | ························} |
2061 | ························else |
2062 | ························{ |
2063 | ····························Type relType; |
2064 | ····························if (r.HasSubclasses) |
2065 | ····························{ |
2066 | ································object o = row[r.ForeignKeyTypeColumnName]; |
2067 | ································if (o == DBNull.Value) |
2068 | ····································throw new NDOException(75, String.Format( |
2069 | ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.", |
2070 | ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName)); |
2071 | ································relType = typeManager[(int)o]; |
2072 | ····························} |
2073 | ····························else |
2074 | ····························{ |
2075 | ································relType = r.ReferencedType; |
2076 | ····························} |
2077 | ····························if (relType == null) |
2078 | ····························{ |
2079 | ································throw new NDOException(75, String.Format( |
2080 | ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", |
2081 | ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName)); |
2082 | ····························} |
2083 | ···· |
2084 | ····························int count = r.ForeignKeyColumns.Count(); |
2085 | ····························object[] keydata = new object[count]; |
2086 | ····························int i = 0; |
2087 | ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns) |
2088 | ····························{ |
2089 | ································keydata[i++] = row[fkColumn.Name]; |
2090 | ····························} |
2091 | |
2092 | ····························Type oidType = relType; |
2093 | ····························if (oidType.IsGenericTypeDefinition) |
2094 | ································oidType = mappings.GetRelationFieldType(r); |
2095 | |
2096 | ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager); |
2097 | ····························if(childOid.IsValid()) |
2098 | ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid)); |
2099 | ····························else |
2100 | ································mappings.SetRelationField(pc, r.FieldName, null); |
2101 | |
2102 | ························} |
2103 | ························pc.NDOSetLoadState(r.Ordinal, true); |
2104 | ····················} |
2105 | ················} |
2106 | ············} |
2107 | ········} |
2108 | |
2109 | ········ |
2110 | ········/// <summary> |
2111 | ········/// Creates a new ObjectId with the same Key value as a given ObjectId. |
2112 | ········/// </summary> |
2113 | ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param> |
2114 | ········/// <param name="t">The type of the object, the id will belong to.</param> |
2115 | ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns> |
2116 | ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks> |
2117 | ········public ObjectId NewObjectId(ObjectId oid, Type t) |
2118 | ········{ |
2119 | ············return new ObjectId(oid.Id, t); |
2120 | ········} |
2121 | |
2122 | ········/* |
2123 | ········/// <summary> |
2124 | ········/// Creates a new ObjectId which can be used to retrieve objects from the database. |
2125 | ········/// </summary> |
2126 | ········/// <param name="keyData">The id, which will be used to search for the object in the database</param> |
2127 | ········/// <param name="t">The type of the object.</param> |
2128 | ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns> |
2129 | ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks> |
2130 | ········public ObjectId NewObjectId(object keyData, Type t) |
2131 | ········{ |
2132 | ············if (keyData == null || keyData == DBNull.Value) |
2133 | ················return ObjectId.InvalidId; |
2134 | |
2135 | ············Class cl =··GetClass(t);············ |
2136 | ············ |
2137 | ············if (cl.Oid.FieldType == typeof(int)) |
2138 | ················return new ObjectId(new Int32Key(t, (int)keyData)); |
2139 | ············else if (cl.Oid.FieldType == typeof(string)) |
2140 | ················return new ObjectId(new StringKey(t, (String) keyData)); |
2141 | ············else if (cl.Oid.FieldType == typeof(Guid)) |
2142 | ················if (keyData is string) |
2143 | ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData))); |
2144 | ················else |
2145 | ····················return new ObjectId(new GuidKey(t, (Guid) keyData)); |
2146 | ············else if (cl.Oid.FieldType == typeof(MultiKey)) |
2147 | ················return new ObjectId(new MultiKey(t, (object[]) keyData)); |
2148 | ············else |
2149 | ················return ObjectId.InvalidId; |
2150 | ········} |
2151 | ········*/ |
2152 | |
2153 | |
2154 | ········/* |
2155 | ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds) |
2156 | ····················{ |
2157 | ························// The column, which hold the oid gets overwritten, if |
2158 | ························// Oid.FieldName contains a value. |
2159 | */ |
2160 | ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row) |
2161 | ········{ |
2162 | ············Class cl = GetClass(pc); |
2163 | |
2164 | ············if (cl.Oid.IsDependent) |
2165 | ················return; |
2166 | |
2167 | ············Key key = pc.NDOObjectId.Id; |
2168 | |
2169 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2170 | ············{ |
2171 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2172 | ················if (oidColumn.FieldName != null) |
2173 | ················{ |
2174 | ····················row[oidColumn.Name] = key[i]; |
2175 | ················} |
2176 | ············} |
2177 | ········} |
2178 | |
2179 | ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row) |
2180 | ········{ |
2181 | ············NDO.Mapping.Class cl = GetClass(pc); |
2182 | ············ObjectId oid = pc.NDOObjectId; |
2183 | |
2184 | ············if (cl.TimeStampColumn != null) |
2185 | ················row[cl.TimeStampColumn] = pc.NDOTimeStamp; |
2186 | |
2187 | ············if (cl.Oid.IsDependent)··// Oid data is in relation columns |
2188 | ················return; |
2189 | |
2190 | ············Key key = oid.Id; |
2191 | |
2192 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2193 | ············{ |
2194 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2195 | ················row[oidColumn.Name] = key[i]; |
2196 | ············} |
2197 | ········} |
2198 | |
2199 | ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row) |
2200 | ········{ |
2201 | ············ObjectId oid = pc.NDOObjectId; |
2202 | ············NDO.Mapping.Class cl = GetClass(pc); |
2203 | |
2204 | ············if (cl.Oid.IsDependent)··// Oid data is in relation columns |
2205 | ················return; |
2206 | |
2207 | ············Key key = oid.Id; |
2208 | |
2209 | ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++) |
2210 | ············{ |
2211 | ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i]; |
2212 | ················object o = row[oidColumn.Name]; |
2213 | ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64)) |
2214 | ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName); |
2215 | ················if (oidColumn.SystemType == typeof(Guid) && (o is String)) |
2216 | ····················key[i] = new Guid((string)o); |
2217 | ················else |
2218 | ····················key[i] = o; |
2219 | ············} |
2220 | |
2221 | ········} |
2222 | |
2223 | ········private void ReadId (Cache.Entry e) |
2224 | ········{ |
2225 | ············ReadIdFromRow(e.pc, e.row); |
2226 | ········} |
2227 | |
2228 | ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames) |
2229 | ········{ |
2230 | ············WriteObject(pc, row, fieldNames, 0); |
2231 | ········} |
2232 | |
2233 | ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row) |
2234 | ········{ |
2235 | ············if (cl.TimeStampColumn == null) |
2236 | ················return; |
2237 | ············object col = row[cl.TimeStampColumn]; |
2238 | ············if (col is String) |
2239 | ················pc.NDOTimeStamp = new Guid(col.ToString()); |
2240 | ············else |
2241 | ················pc.NDOTimeStamp = (Guid) col; |
2242 | ········} |
2243 | |
2244 | |
2245 | |
2246 | ········private void ReadTimeStamp(Cache.Entry e) |
2247 | ········{ |
2248 | ············IPersistenceCapable pc = e.pc; |
2249 | ············NDO.Mapping.Class cl = GetClass(pc); |
2250 | ············Debug.Assert(!IsFakeRow(cl, e.row)); |
2251 | ············if (cl.TimeStampColumn == null) |
2252 | ················return; |
2253 | ············if (e.row[cl.TimeStampColumn] is String) |
2254 | ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString()); |
2255 | ············else |
2256 | ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn]; |
2257 | ········} |
2258 | |
2259 | ········/// <summary> |
2260 | ········/// Determines, if any objects are new, changed or deleted. |
2261 | ········/// </summary> |
2262 | ········public virtual bool HasChanges |
2263 | ········{ |
2264 | ············get |
2265 | ············{ |
2266 | ················return cache.LockedObjects.Count > 0; |
2267 | ············} |
2268 | ········} |
2269 | |
2270 | ········/// <summary> |
2271 | ········/// Do the update for all rows in the ds. |
2272 | ········/// </summary> |
2273 | ········/// <param name="types">Types with changes.</param> |
2274 | ········/// <param name="delete">True, if delete operations are to be performed.</param> |
2275 | ········/// <remarks> |
2276 | ········/// Delete and Insert/Update operations are to be separated to maintain the type order. |
2277 | ········/// </remarks> |
2278 | ········private void UpdateTypes(IList types, bool delete) |
2279 | ········{ |
2280 | ············foreach(Type t in types) |
2281 | ············{ |
2282 | ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name); |
2283 | ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t )) |
2284 | ················{ |
2285 | ····················CheckTransaction( handler, t ); |
2286 | ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError); |
2287 | ····················handler.ConcurrencyError += ceh; |
2288 | ····················try |
2289 | ····················{ |
2290 | ························DataTable dt = GetTable(t); |
2291 | ························if (delete) |
2292 | ····························handler.UpdateDeletedObjects( dt ); |
2293 | ························else |
2294 | ····························handler.Update( dt ); |
2295 | ····················} |
2296 | ····················finally |
2297 | ····················{ |
2298 | ························handler.ConcurrencyError -= ceh; |
2299 | ····················} |
2300 | ················} |
2301 | ············} |
2302 | ········} |
2303 | |
2304 | ········internal void UpdateCreatedMappingTableEntries() |
2305 | ········{ |
2306 | ············foreach (MappingTableEntry e in createdMappingTableObjects) |
2307 | ············{ |
2308 | ················if (!e.DeleteEntry) |
2309 | ····················WriteMappingTableEntry(e); |
2310 | ············} |
2311 | ············// Now update all mapping tables |
2312 | ············foreach (IMappingTableHandler handler in mappingHandler.Values) |
2313 | ············{ |
2314 | ················CheckTransaction( handler, handler.Relation.MappingTable.Connection ); |
2315 | ················handler.Update(ds); |
2316 | ············} |
2317 | ········} |
2318 | |
2319 | ········internal void UpdateDeletedMappingTableEntries() |
2320 | ········{ |
2321 | ············foreach (MappingTableEntry e in createdMappingTableObjects) |
2322 | ············{ |
2323 | ················if (e.DeleteEntry) |
2324 | ····················WriteMappingTableEntry(e); |
2325 | ············} |
2326 | ············// Now update all mapping tables |
2327 | ············foreach (IMappingTableHandler handler in mappingHandler.Values) |
2328 | ············{ |
2329 | ················CheckTransaction( handler, handler.Relation.MappingTable.Connection ); |
2330 | ················handler.Update(ds); |
2331 | ············} |
2332 | ········} |
2333 | |
2334 | ········/// <summary> |
2335 | ········/// Save all changed object into the DataSet and update the DB. |
2336 | ········/// When a newly created object is written to DB, the key might change. Therefore, |
2337 | ········/// the id is updated and the object is removed and re-inserted into the cache. |
2338 | ········/// </summary> |
2339 | ········public virtual void Save(bool deferCommit = false) |
2340 | ········{ |
2341 | ············this.DeferredMode = deferCommit; |
2342 | ············var htOnSaving = new HashSet<ObjectId>(); |
2343 | ············for(;;) |
2344 | ············{ |
2345 | ················// We need to work on a copy of the locked objects list, |
2346 | ················// since the handlers might add more objects to the cache |
2347 | ················var lockedObjects = cache.LockedObjects.ToList(); |
2348 | ················int count = lockedObjects.Count; |
2349 | ················foreach(Cache.Entry e in lockedObjects) |
2350 | ················{ |
2351 | ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted) |
2352 | ····················{ |
2353 | ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable; |
2354 | ························if (ipn != null) |
2355 | ························{ |
2356 | ····························if (!htOnSaving.Contains(e.pc.NDOObjectId)) |
2357 | ····························{ |
2358 | ································ipn.OnSaving(); |
2359 | ································htOnSaving.Add(e.pc.NDOObjectId); |
2360 | ····························} |
2361 | ························} |
2362 | ····················} |
2363 | ················} |
2364 | ················// The system is stable, if the count doesn't change |
2365 | ················if (cache.LockedObjects.Count == count) |
2366 | ····················break; |
2367 | ············} |
2368 | |
2369 | ············if (this.OnSavingEvent != null) |
2370 | ············{ |
2371 | ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count); |
2372 | ················foreach(Cache.Entry e in cache.LockedObjects) |
2373 | ····················onSavingObjects.Add(e.pc); |
2374 | ················OnSavingEvent(onSavingObjects); |
2375 | ············} |
2376 | |
2377 | ············List<Type> types = new List<Type>(); |
2378 | ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>(); |
2379 | ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null; |
2380 | ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>(); |
2381 | ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>(); |
2382 | ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>(); |
2383 | |
2384 | ············// Save current state in DataSet |
2385 | ············foreach (Cache.Entry e in cache.LockedObjects) |
2386 | ············{ |
2387 | ················Type objType = e.pc.GetType(); |
2388 | |
2389 | ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition) |
2390 | ····················objType = objType.GetGenericTypeDefinition(); |
2391 | |
2392 | ················Class cl = GetClass(e.pc); |
2393 | ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump()); |
2394 | ················if(!types.Contains(objType)) |
2395 | ················{ |
2396 | ····················//Debug.WriteLine("Added··type " + objType.Name); |
2397 | ····················types.Add(objType); |
2398 | ················} |
2399 | ················NDOObjectState objectState = e.pc.NDOObjectState; |
2400 | ················if(objectState == NDOObjectState.Deleted) |
2401 | ················{ |
2402 | ····················deletedObjects.Add(e.pc); |
2403 | ················} |
2404 | ················else if(objectState == NDOObjectState.Created) |
2405 | ················{ |
2406 | ····················WriteObject(e.pc, e.row, cl.ColumnNames);···················· |
2407 | ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row |
2408 | ····················WriteForeignKeysToRow(e.pc, e.row); |
2409 | ····················//····················Debug.WriteLine(e.pc.GetType().FullName); |
2410 | ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count]; |
2411 | ····················//····················i = 0; |
2412 | ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects) |
2413 | ····················//····················{ |
2414 | ····················//························rows[i++] = e2.row; |
2415 | ····················//····················} |
2416 | ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand"); |
2417 | ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows); |
2418 | |
2419 | ····················addedCacheEntries.Add(e); |
2420 | ····················addedObjects.Add( e.pc ); |
2421 | ····················//····················objectState = NDOObjectState.Persistent; |
2422 | ················} |
2423 | ················else |
2424 | ················{ |
2425 | ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty) |
2426 | ························changedObjects.Add( e.pc ); |
2427 | ····················WriteObject(e.pc, e.row, cl.ColumnNames); |
2428 | ····················WriteForeignKeysToRow(e.pc, e.row); ···················· |
2429 | ················} |
2430 | ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) ) |
2431 | ················{ |
2432 | ····················hollowModeObjects.Add(e.pc); |
2433 | ················} |
2434 | ············} |
2435 | |
2436 | ············// Before we delete any db rows, we have to make sure, to delete mapping |
2437 | ············// table entries first, which might have relations to the db rows to be deleted |
2438 | ············UpdateDeletedMappingTableEntries(); |
2439 | |
2440 | ············// Update DB |
2441 | ············if (ds.HasChanges()) |
2442 | ············{ |
2443 | |
2444 | ················// We need the reversed update order for deletions. |
2445 | ················types.Sort( ( t1, t2 ) => |
2446 | ················{ |
2447 | ····················int i1 = mappings.GetUpdateOrder( t1 ); |
2448 | ····················int i2 = mappings.GetUpdateOrder( t2 ); |
2449 | ····················if (i1 < i2) |
2450 | ····················{ |
2451 | ························if (!addedObjects.Any( pc => pc.GetType() == t1 )) |
2452 | ····························i1 += 100000; |
2453 | ····················} |
2454 | ····················else |
2455 | ····················{ |
2456 | ························if (!addedObjects.Any( pc => pc.GetType() == t2 )) |
2457 | ····························i2 += 100000; |
2458 | ····················} |
2459 | ····················return i2 - i1; |
2460 | ················} ); |
2461 | |
2462 | ················// Delete records first |
2463 | |
2464 | ················UpdateTypes(types, true); |
2465 | |
2466 | ················// Now do all other updates in correct order. |
2467 | ················types.Reverse(); |
2468 | |
2469 | ················UpdateTypes(types, false); |
2470 | ················ |
2471 | ················ds.AcceptChanges(); |
2472 | ················if(createdDirectObjects.Count > 0) |
2473 | ················{ |
2474 | ····················// Rewrite all children that have foreign keys to parents which have just been saved now. |
2475 | ····················// They must be written again to store the correct foreign keys. |
2476 | ····················foreach(IPersistenceCapable pc in createdDirectObjects) |
2477 | ····················{ |
2478 | ························Class cl = GetClass(pc); |
2479 | ························DataRow r = this.cache.GetDataRow(pc); |
2480 | ························string fakeColumnName = GetFakeRowOidColumnName(cl); |
2481 | ························object o = r[fakeColumnName]; |
2482 | ························r[fakeColumnName] = o; |
2483 | ····················} |
2484 | |
2485 | ····················UpdateTypes(types, false); |
2486 | ················} |
2487 | |
2488 | ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache. |
2489 | ················foreach(Cache.Entry e in addedCacheEntries) |
2490 | ················{ |
2491 | ····················cache.DeregisterLockedObject(e.pc); |
2492 | ····················ReadId(e); |
2493 | ····················cache.RegisterLockedObject(e.pc, e.row, e.relations); |
2494 | ················} |
2495 | |
2496 | ················// Now update all mapping tables. Because of possible subclasses, there is no |
2497 | ················// relation between keys in the dataset schema. Therefore, we can update mapping |
2498 | ················// tables only after all other objects have been written to ensure correct foreign keys. |
2499 | ················UpdateCreatedMappingTableEntries(); |
2500 | |
2501 | ················// The rows may contain now new Ids, which should be |
2502 | ················// stored in the lostRowInfo's before the rows get detached |
2503 | ················foreach(Cache.Entry e in cache.LockedObjects) |
2504 | ················{ |
2505 | ····················if (e.row.RowState != DataRowState.Detached) |
2506 | ····················{ |
2507 | ························IPersistenceCapable pc = e.pc; |
2508 | ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row); |
2509 | ····················} |
2510 | ················} |
2511 | |
2512 | ················ds.AcceptChanges(); |
2513 | ············} |
2514 | |
2515 | ············EndSave(!deferCommit); |
2516 | |
2517 | ············foreach(IPersistenceCapable pc in deletedObjects) |
2518 | ············{ |
2519 | ················MakeObjectTransient(pc, false); |
2520 | ············} |
2521 | |
2522 | ············ds.Clear(); |
2523 | ············mappingHandler.Clear(); |
2524 | ············createdDirectObjects.Clear(); |
2525 | ············createdMappingTableObjects.Clear(); |
2526 | ············this.relationChanges.Clear(); |
2527 | |
2528 | ············if(hollowMode) |
2529 | ············{ |
2530 | ················MakeHollow(hollowModeObjects); |
2531 | ············} |
2532 | |
2533 | ············if (this.OnSavedEvent != null) |
2534 | ············{ |
2535 | ················AuditSet auditSet = new AuditSet() |
2536 | ················{ |
2537 | ····················ChangedObjects = changedObjects, |
2538 | ····················CreatedObjects = addedObjects, |
2539 | ····················DeletedObjects = deletedObjects |
2540 | ················}; |
2541 | ················this.OnSavedEvent( auditSet ); |
2542 | ············} |
2543 | ········} |
2544 | |
2545 | ········private void EndSave(bool forceCommit) |
2546 | ········{ |
2547 | ············foreach(Cache.Entry e in cache.LockedObjects) |
2548 | ············{ |
2549 | ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty) |
2550 | ····················this.ReadTimeStamp(e); |
2551 | ················e.pc.NDOObjectState = NDOObjectState.Persistent; |
2552 | ············} |
2553 | |
2554 | ············cache.UnlockAll(); |
2555 | |
2556 | ············CheckEndTransaction(forceCommit); |
2557 | ········} |
2558 | |
2559 | ········/// <summary> |
2560 | ········/// Write all foreign keys for 1:1-relations. |
2561 | ········/// </summary> |
2562 | ········/// <param name="pc">The persistent object.</param> |
2563 | ········/// <param name="pcRow">The DataRow of the pesistent object.</param> |
2564 | ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow) |
2565 | ········{ |
2566 | ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType())) |
2567 | ············{ |
2568 | ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName); |
2569 | ················bool isDependent = GetClass(pc).Oid.IsDependent; |
2570 | |
2571 | ················if ( relObj != null ) |
2572 | ················{ |
2573 | ····················int i = 0; |
2574 | ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2575 | ····················{ |
2576 | ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++]; |
2577 | ····················} |
2578 | ····················if ( r.ForeignKeyTypeColumnName != null ) |
2579 | ····················{ |
2580 | ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId; |
2581 | ····················} |
2582 | ················} |
2583 | ················else |
2584 | ················{ |
2585 | ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2586 | ····················{ |
2587 | ························pcRow[fkColumn.Name] = DBNull.Value; |
2588 | ····················} |
2589 | ····················if ( r.ForeignKeyTypeColumnName != null ) |
2590 | ····················{ |
2591 | ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value; |
2592 | ····················} |
2593 | ················} |
2594 | ············} |
2595 | ········} |
2596 | |
2597 | |
2598 | |
2599 | ········/// <summary> |
2600 | ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys. |
2601 | ········/// </summary> |
2602 | ········/// <param name="e">the mapping table entry</param> |
2603 | ········private void WriteMappingTableEntry(MappingTableEntry e) |
2604 | ········{ |
2605 | ············IPersistenceCapable pc = e.ParentObject; |
2606 | ············IPersistenceCapable relObj = e.RelatedObject; |
2607 | ············Relation r = e.Relation; |
2608 | ············DataTable dt = GetTable(r.MappingTable.TableName); |
2609 | ············DataRow row = dt.NewRow(); |
2610 | ············int i = 0; |
2611 | ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns ) |
2612 | ············{ |
2613 | ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++]; |
2614 | ············} |
2615 | ············i = 0; |
2616 | ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns) |
2617 | ············{ |
2618 | ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++]; |
2619 | ············} |
2620 | |
2621 | ············if (r.ForeignKeyTypeColumnName != null) |
2622 | ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId; |
2623 | ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null) |
2624 | ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId; |
2625 | |
2626 | ············dt.Rows.Add(row); |
2627 | ············if(e.DeleteEntry) |
2628 | ············{ |
2629 | ················row.AcceptChanges(); |
2630 | ················row.Delete(); |
2631 | ············} |
2632 | |
2633 | ············IMappingTableHandler handler; |
2634 | ············if (!mappingHandler.TryGetValue( r, out handler )) |
2635 | ············{ |
2636 | ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ); |
2637 | ············} |
2638 | ········} |
2639 | |
2640 | |
2641 | ········/// <summary> |
2642 | ········/// Undo changes of a certain object |
2643 | ········/// </summary> |
2644 | ········/// <param name="o">Object to undo</param> |
2645 | ········public void Restore(object o) |
2646 | ········{············ |
2647 | ············IPersistenceCapable pc = CheckPc(o); |
2648 | ············Cache.Entry e = null; |
2649 | ············foreach (Cache.Entry entry in cache.LockedObjects) |
2650 | ············{ |
2651 | ················if (entry.pc == pc) |
2652 | ················{ |
2653 | ····················e = entry; |
2654 | ····················break; |
2655 | ················} |
2656 | ············} |
2657 | ············if (e == null) |
2658 | ················return; |
2659 | ············Class cl = GetClass(e.pc); |
2660 | ············switch (pc.NDOObjectState) |
2661 | ············{ |
2662 | ················case NDOObjectState.PersistentDirty: |
2663 | ····················ObjectListManipulator.Remove(createdDirectObjects, pc); |
2664 | ····················foreach(Relation r in cl.Relations) |
2665 | ····················{ |
2666 | ························if (r.Multiplicity == RelationMultiplicity.Element) |
2667 | ························{ |
2668 | ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
2669 | ····························if (subPc != null && cache.IsLocked(subPc)) |
2670 | ································Restore(subPc); |
2671 | ························} |
2672 | ························else |
2673 | ························{ |
2674 | ····························if (!pc.NDOGetLoadState(r.Ordinal)) |
2675 | ································continue; |
2676 | ····························IList subList = (IList) mappings.GetRelationContainer(pc, r); |
2677 | ····························if (subList != null) |
2678 | ····························{ |
2679 | ································foreach(IPersistenceCapable subPc2 in subList) |
2680 | ································{ |
2681 | ····································if (cache.IsLocked(subPc2)) |
2682 | ········································Restore(subPc2); |
2683 | ································} |
2684 | ····························} |
2685 | ························} |
2686 | ····················} |
2687 | ····················RestoreRelatedObjects(pc, e.relations); |
2688 | ····················e.row.RejectChanges(); |
2689 | ····················ReadObject(pc, e.row, cl.ColumnNames, 0); |
2690 | ····················cache.Unlock(pc); |
2691 | ····················pc.NDOObjectState = NDOObjectState.Persistent; |
2692 | ····················break; |
2693 | ················case NDOObjectState.Created: |
2694 | ····················ReadObject(pc, e.row, cl.ColumnNames, 0); |
2695 | ····················cache.Unlock(pc); |
2696 | ····················MakeObjectTransient(pc, true); |
2697 | ····················break; |
2698 | ················case NDOObjectState.Deleted: |
2699 | ····················if (!this.IsFakeRow(cl, e.row)) |
2700 | ····················{ |
2701 | ························e.row.RejectChanges(); |
2702 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2703 | ························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2704 | ····················} |
2705 | ····················else |
2706 | ····················{ |
2707 | ························e.row.RejectChanges(); |
2708 | ························e.pc.NDOObjectState = NDOObjectState.Hollow; |
2709 | ····················} |
2710 | ····················cache.Unlock(pc); |
2711 | ····················break; |
2712 | |
2713 | ············} |
2714 | ········} |
2715 | |
2716 | ········/// <summary> |
2717 | ········/// Aborts a pending transaction without restoring the object state. |
2718 | ········/// </summary> |
2719 | ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks> |
2720 | ········public virtual void AbortTransaction() |
2721 | ········{ |
2722 | ············TransactionScope.Dispose(); |
2723 | ········} |
2724 | |
2725 | ········/// <summary> |
2726 | ········/// Rejects all changes and restores the original object state. Added Objects will be made transient. |
2727 | ········/// </summary> |
2728 | ········public virtual void Abort() |
2729 | ········{ |
2730 | ············// RejectChanges of the DS cannot be called because newly added rows would be deleted, |
2731 | ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each |
2732 | ············// individual row. |
2733 | ············createdDirectObjects.Clear(); |
2734 | ············createdMappingTableObjects.Clear(); |
2735 | ············ArrayList deletedObjects = new ArrayList(); |
2736 | ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null; |
2737 | |
2738 | ············// Read all objects from DataSet |
2739 | ············foreach (Cache.Entry e in cache.LockedObjects) |
2740 | ············{ |
2741 | ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name); |
2742 | |
2743 | ················Class cl = GetClass(e.pc); |
2744 | ················bool isFakeRow = this.IsFakeRow(cl, e.row); |
2745 | ················if (!isFakeRow) |
2746 | ················{ |
2747 | ····················RestoreRelatedObjects(e.pc, e.relations); |
2748 | ················} |
2749 | ················else |
2750 | ················{ |
2751 | ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state"); |
2752 | ················} |
2753 | |
2754 | ················switch(e.pc.NDOObjectState) |
2755 | ················{ |
2756 | ····················case NDOObjectState.Created: |
2757 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2758 | ························deletedObjects.Add(e.pc); |
2759 | ························break; |
2760 | |
2761 | ····················case NDOObjectState.PersistentDirty: |
2762 | ························e.row.RejectChanges(); |
2763 | ························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2764 | ························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2765 | ························break; |
2766 | |
2767 | ····················case NDOObjectState.Deleted: |
2768 | ························if (!isFakeRow) |
2769 | ························{ |
2770 | ····························e.row.RejectChanges(); |
2771 | ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0); |
2772 | ····························e.pc.NDOObjectState = NDOObjectState.Persistent; |
2773 | ························} |
2774 | ························else |
2775 | ························{ |
2776 | ····························e.row.RejectChanges(); |
2777 | ····························e.pc.NDOObjectState = NDOObjectState.Hollow; |
2778 | ························} |
2779 | ························break; |
2780 | |
2781 | ····················default: |
2782 | ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump()); |
2783 | ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState); |
2784 | ························//break; |
2785 | ················} |
2786 | ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent) |
2787 | ················{ |
2788 | ····················hollowModeObjects.Add(e.pc); |
2789 | ················} |
2790 | ············} |
2791 | ············cache.UnlockAll(); |
2792 | ············foreach(IPersistenceCapable pc in deletedObjects) |
2793 | ············{ |
2794 | ················MakeObjectTransient(pc, true); |
2795 | ············} |
2796 | ············ds.Clear(); |
2797 | ············mappingHandler.Clear(); |
2798 | ············if(hollowMode) |
2799 | ············{ |
2800 | ················MakeHollow(hollowModeObjects); |
2801 | ············} |
2802 | |
2803 | ············this.relationChanges.Clear(); |
2804 | |
2805 | |
2806 | ············AbortTransaction(); |
2807 | ········} |
2808 | |
2809 | |
2810 | ········/// <summary> |
2811 | ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to |
2812 | ········/// disable future access. |
2813 | ········/// </summary> |
2814 | ········/// <param name="pc"></param> |
2815 | ········/// <param name="removeId">Indicates if the object id should be nulled</param> |
2816 | ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId) |
2817 | ········{ |
2818 | ············cache.Deregister(pc); |
2819 | ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············ |
2820 | ············if(removeId) |
2821 | ············{ |
2822 | ················pc.NDOObjectId = null; |
2823 | ············} |
2824 | ············pc.NDOObjectState = NDOObjectState.Transient; |
2825 | ············pc.NDOStateManager = null; |
2826 | ········} |
2827 | |
2828 | ········/// <summary> |
2829 | ········/// Makes an object transient. |
2830 | ········/// The object can be used afterwards, but changes will not be saved in the database. |
2831 | ········/// </summary> |
2832 | ········/// <remarks> |
2833 | ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data. |
2834 | ········/// </remarks> |
2835 | ········/// <param name="o">The object to detach.</param> |
2836 | ········public void MakeTransient(object o) |
2837 | ········{ |
2838 | ············IPersistenceCapable pc = CheckPc(o); |
2839 | ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow) |
2840 | ············{ |
2841 | ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation"); |
2842 | ············} |
2843 | |
2844 | ············if(pc.NDOObjectState··== NDOObjectState.Hollow) |
2845 | ············{ |
2846 | ················LoadData(pc); |
2847 | ············} |
2848 | ············MakeObjectTransient(pc, true); |
2849 | ········} |
2850 | |
2851 | |
2852 | ········/// <summary> |
2853 | ········/// Make all objects of a list transient. |
2854 | ········/// </summary> |
2855 | ········/// <param name="list">the list of transient objects</param> |
2856 | ········public void MakeTransient(System.Collections.IList list) |
2857 | ········{ |
2858 | ············foreach (IPersistenceCapable pc in list) |
2859 | ················MakeTransient(pc); |
2860 | ········} |
2861 | |
2862 | ········/// <summary> |
2863 | ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used. |
2864 | ········/// </summary> |
2865 | ········/// <param name="o">The object to remove</param> |
2866 | ········public void Delete(object o) |
2867 | ········{ |
2868 | ············IPersistenceCapable pc = CheckPc(o); |
2869 | ············if (pc.NDOObjectState == NDOObjectState.Transient) |
2870 | ············{ |
2871 | ················throw new NDOException( 120, "Can't delete transient object" ); |
2872 | ············} |
2873 | ············if (pc.NDOObjectState != NDOObjectState.Deleted) |
2874 | ············{ |
2875 | ················Delete(pc, true); |
2876 | ············} |
2877 | ········} |
2878 | |
2879 | |
2880 | ········private void LoadAllRelations(object o) |
2881 | ········{ |
2882 | ············IPersistenceCapable pc = CheckPc(o); |
2883 | ············Class cl = GetClass(pc); |
2884 | ············foreach(Relation r in cl.Relations) |
2885 | ············{ |
2886 | ················if (!pc.NDOGetLoadState(r.Ordinal)) |
2887 | ····················LoadRelation(pc, r, true); |
2888 | ············} |
2889 | ········} |
2890 | |
2891 | |
2892 | ········/// <summary> |
2893 | ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used. |
2894 | ········/// </summary> |
2895 | ········/// <remarks> |
2896 | ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation. |
2897 | ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from |
2898 | ········/// the parent object. |
2899 | ········/// </remarks> |
2900 | ········/// <param name="pc">the object to remove</param> |
2901 | ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param> |
2902 | ········private void Delete(IPersistenceCapable pc, bool checkAssoziations) |
2903 | ········{ |
2904 | ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump()); |
2905 | ············//Debug.Indent(); |
2906 | ············IDeleteNotifiable idn = pc as IDeleteNotifiable; |
2907 | ············if (idn != null) |
2908 | ················idn.OnDelete(); |
2909 | |
2910 | ············LoadAllRelations(pc); |
2911 | ············DeleteRelatedObjects(pc, checkAssoziations); |
2912 | |
2913 | ············switch(pc.NDOObjectState) |
2914 | ············{ |
2915 | ················case NDOObjectState.Transient: |
2916 | ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId); |
2917 | |
2918 | ················case NDOObjectState.Created: |
2919 | ····················DataRow row = cache.GetDataRow(pc); |
2920 | ····················row.Delete(); |
2921 | ····················ArrayList cdosToDelete = new ArrayList(); |
2922 | ····················foreach (IPersistenceCapable cdo in createdDirectObjects) |
2923 | ························if ((object)cdo == (object)pc) |
2924 | ····························cdosToDelete.Add(cdo); |
2925 | ····················foreach (object o in cdosToDelete) |
2926 | ························ObjectListManipulator.Remove(createdDirectObjects, o); |
2927 | ····················MakeObjectTransient(pc, true); |
2928 | ····················break; |
2929 | ················case NDOObjectState.Persistent: |
2930 | ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden |
2931 | ························SaveObjectState(pc, true); |
2932 | ····················row = cache.GetDataRow(pc); |
2933 | ····················row.Delete(); |
2934 | ····················pc.NDOObjectState = NDOObjectState.Deleted; |
2935 | ····················break; |
2936 | ················case NDOObjectState.Hollow: |
2937 | ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden |
2938 | ························SaveFakeRow(pc); |
2939 | ····················row = cache.GetDataRow(pc); |
2940 | ····················row.Delete(); |
2941 | ····················pc.NDOObjectState = NDOObjectState.Deleted; |
2942 | ····················break; |
2943 | |
2944 | ················case NDOObjectState.PersistentDirty: |
2945 | ····················row = cache.GetDataRow(pc); |
2946 | ····················row.Delete(); |
2947 | ····················pc.NDOObjectState··= NDOObjectState.Deleted; |
2948 | ····················break; |
2949 | |
2950 | ················case NDOObjectState.Deleted: |
2951 | ····················break; |
2952 | ············} |
2953 | |
2954 | ············//Debug.Unindent(); |
2955 | ········} |
2956 | |
2957 | |
2958 | ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
2959 | ········{ |
2960 | ············MappingTableEntry mte = null; |
2961 | ············foreach(MappingTableEntry e in createdMappingTableObjects) |
2962 | ············{ |
2963 | ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r) |
2964 | ················{ |
2965 | ····················mte = e; |
2966 | ····················break; |
2967 | ················} |
2968 | ············} |
2969 | |
2970 | ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created) |
2971 | ············{ |
2972 | ················if (mte != null) |
2973 | ····················createdMappingTableObjects.Remove(mte); |
2974 | ············} |
2975 | ············else |
2976 | ············{ |
2977 | ················if (mte == null) |
2978 | ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true)); |
2979 | ············} |
2980 | ········} |
2981 | |
2982 | ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
2983 | ········{ |
2984 | ············// Two tasks: a) Null the foreign key |
2985 | ············//··············b) remove the element from the foreign container |
2986 | |
2987 | ············if (!r.Bidirectional) |
2988 | ················return; |
2989 | |
2990 | ············// this keeps the oid valid |
2991 | ············if (GetClass(child.GetType()).Oid.IsDependent) |
2992 | ················return; |
2993 | |
2994 | ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element) |
2995 | ············{ |
2996 | ················LoadAndMarkDirty(child); |
2997 | ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null); |
2998 | ············} |
2999 | ············else //if(r.Multiplicity == RelationMultiplicity.List && |
3000 | ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)·· |
3001 | ············{ |
3002 | ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal)) |
3003 | ····················LoadRelation(child, r.ForeignRelation, true); |
3004 | ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation); |
3005 | ················if (l == null) |
3006 | ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null."); |
3007 | ················ObjectListManipulator.Remove(l, pc); |
3008 | ················// Don't need to delete the mapping table entry, because that was done |
3009 | ················// through the parent. |
3010 | ············} |
3011 | ········} |
3012 | |
3013 | ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child) |
3014 | ········{ |
3015 | ············// 1) Element····nomap····ass |
3016 | ············// 2) Element····nomap····comp |
3017 | ············// 3) Element····map········ass |
3018 | ············// 4) Element····map········comp |
3019 | ············// 5) List········nomap····ass |
3020 | ············// 6) List········nomap····comp |
3021 | ············// 7) List········map········ass |
3022 | ············// 8) List········map········comp |
3023 | |
3024 | ············// Two tasks: Null foreign key and, if Composition, delete the child |
3025 | |
3026 | ············// If Mapping Table, delete the entry - 3,7 |
3027 | ············// If List and assoziation, null the foreign key in the foreign class - 5 |
3028 | ············// If Element, null the foreign key in the own class 1,2,3,4 |
3029 | ············// If composition, delete the child 2,4,6,8 |
3030 | |
3031 | ············// If the relObj is newly created |
3032 | ············ObjectListManipulator.Remove(createdDirectObjects, child); |
3033 | ············Class childClass = GetClass(child); |
3034 | |
3035 | ············if (r.MappingTable != null)··// 3,7 |
3036 | ············{················ |
3037 | ················DeleteMappingTableEntry(pc, r, child); |
3038 | ············} |
3039 | ············else if (r.Multiplicity == RelationMultiplicity.List |
3040 | ················&& !r.Composition && !childClass.Oid.IsDependent) // 5 |
3041 | ············{················ |
3042 | ················LoadAndMarkDirty(child); |
3043 | ················DataRow row = this.cache.GetDataRow(child); |
3044 | ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns) |
3045 | ················{ |
3046 | ····················row[fkColumnn.Name] = DBNull.Value; |
3047 | ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value); |
3048 | ················} |
3049 | ············} |
3050 | ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4 |
3051 | ············{ |
3052 | ················LoadAndMarkDirty(pc); |
3053 | ············} |
3054 | ············if (r.Composition || childClass.Oid.IsDependent) |
3055 | ············{ |
3056 | #if DEBUG |
3057 | ················if (child.NDOObjectState == NDOObjectState.Transient) |
3058 | ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName); |
3059 | #endif |
3060 | ················// Deletes the foreign key in case of List multiplicity |
3061 | ················// In case of Element multiplicity, the parent is either deleted, |
3062 | ················// or RemoveRelatedObject is called because the relation has been nulled. |
3063 | ················Delete(child);·· |
3064 | ············} |
3065 | ········} |
3066 | |
3067 | ········/// <summary> |
3068 | ········/// Remove a related object |
3069 | ········/// </summary> |
3070 | ········/// <param name="pc"></param> |
3071 | ········/// <param name="r"></param> |
3072 | ········/// <param name="child"></param> |
3073 | ········/// <param name="calledFromStateManager"></param> |
3074 | ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager) |
3075 | ········{ |
3076 | ············//TODO: We need a relation management, which is independent of |
3077 | ············//the state management of an object. At the moment the relation |
3078 | ············//lists or elements are cached for restore, if an object is marked dirty. |
3079 | ············//Thus we have to mark dirty our parent object in any case at the moment. |
3080 | ············if (calledFromStateManager) |
3081 | ················MarkDirty(pc); |
3082 | |
3083 | ············// Object can be deleted in an OnDelete-Handler |
3084 | ············if (child.NDOObjectState == NDOObjectState.Deleted) |
3085 | ················return; |
3086 | ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType()); |
3087 | ············// Preconditions |
3088 | ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject |
3089 | ············// The Object state is checked there |
3090 | |
3091 | ············// If there is a composition in the opposite direction |
3092 | ············// && the other direction hasn't been processed |
3093 | ············// throw an exception. |
3094 | ············// If an exception is thrown at this point, have a look at IsLocked.... |
3095 | ············if (r.Bidirectional && r.ForeignRelation.Composition |
3096 | ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc)) |
3097 | ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent."); |
3098 | |
3099 | ············if (!removeLock.GetLock(pc, r, child)) |
3100 | ················return; |
3101 | |
3102 | ············try |
3103 | ············{ |
3104 | ················// must be in this order, since the child |
3105 | ················// can be deleted in DeleteOrNullRelation |
3106 | ················//if (changeForeignRelations) |
3107 | ················DeleteOrNullForeignRelation(pc, r, child); |
3108 | ················DeleteOrNullRelation(pc, r, child); |
3109 | ············} |
3110 | ············finally |
3111 | ············{ |
3112 | ················removeLock.Unlock(pc, r, child); |
3113 | ············} |
3114 | |
3115 | ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) ); |
3116 | ········} |
3117 | |
3118 | ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r) |
3119 | ········{ |
3120 | ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName); |
3121 | ············//············Debug.Indent(); |
3122 | ············if (r.Multiplicity == RelationMultiplicity.Element) |
3123 | ············{ |
3124 | ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
3125 | ················if(child != null) |
3126 | ················{ |
3127 | ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition) |
3128 | ····················//····················{ |
3129 | ····················//························if (!r.ForeignRelation.Composition) |
3130 | ····················//························{ |
3131 | ····················//····························mappings.SetRelationField(pc, r.FieldName, null); |
3132 | ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null); |
3133 | ····················//························} |
3134 | ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name); |
3135 | ····················//························else |
3136 | ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName); |
3137 | ····················//····················} |
3138 | ····················InternalRemoveRelatedObject(pc, r, child, false); |
3139 | ················} |
3140 | ············} |
3141 | ············else |
3142 | ············{ |
3143 | ················IList list = mappings.GetRelationContainer(pc, r); |
3144 | ················if(list != null && list.Count > 0) |
3145 | ················{ |
3146 | ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition) |
3147 | ····················//····················{ |
3148 | ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first."); |
3149 | ····················//····················} |
3150 | ····················// Since RemoveRelatedObjects probably changes the list, |
3151 | ····················// we iterate through a copy of the list. |
3152 | ····················ArrayList al = new ArrayList(list); |
3153 | ····················foreach(IPersistenceCapable relObj in al) |
3154 | ····················{ |
3155 | ························InternalRemoveRelatedObject(pc, r, relObj, false); |
3156 | ····················} |
3157 | ················} |
3158 | ············} |
3159 | ············//············Debug.Unindent(); |
3160 | ········} |
3161 | |
3162 | ········/// <summary> |
3163 | ········/// Remove all related objects from a parent. |
3164 | ········/// </summary> |
3165 | ········/// <param name="pc">the parent object</param> |
3166 | ········/// <param name="checkAssoziations"></param> |
3167 | ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations) |
3168 | ········{ |
3169 | ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump()); |
3170 | ············//············Debug.Indent(); |
3171 | ············// delete all related objects: |
3172 | ············Class parentClass = GetClass(pc); |
3173 | ············// Remove Assoziations first |
3174 | ············foreach(Relation r in parentClass.Relations) |
3175 | ············{ |
3176 | ················if (!r.Composition) |
3177 | ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r); |
3178 | ············} |
3179 | ············foreach(Relation r in parentClass.Relations) |
3180 | ············{ |
3181 | ················if (r.Composition) |
3182 | ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r); |
3183 | ············} |
3184 | |
3185 | ············//············Debug.Unindent(); |
3186 | ········} |
3187 | |
3188 | ········/// <summary> |
3189 | ········/// Delete a list of objects |
3190 | ········/// </summary> |
3191 | ········/// <param name="list">the list of objects to remove</param> |
3192 | ········public void Delete(IList list) |
3193 | ········{ |
3194 | ············for (int i = 0; i < list.Count; i++) |
3195 | ············{ |
3196 | ················IPersistenceCapable pc = (IPersistenceCapable) list[i]; |
3197 | ················Delete(pc); |
3198 | ············} |
3199 | ········} |
3200 | |
3201 | ········/// <summary> |
3202 | ········/// Make object hollow. All relations will be unloaded and object data will be |
3203 | ········/// newly fetched during the next touch of a persistent field. |
3204 | ········/// </summary> |
3205 | ········/// <param name="o"></param> |
3206 | ········public virtual void MakeHollow(object o) |
3207 | ········{ |
3208 | ············IPersistenceCapable pc = CheckPc(o); |
3209 | ············MakeHollow(pc, false); |
3210 | ········} |
3211 | |
3212 | ········/// <summary> |
3213 | ········/// Make the object hollow if it is persistent. Unload all complex data. |
3214 | ········/// </summary> |
3215 | ········/// <param name="o"></param> |
3216 | ········/// <param name="recursive">if true then unload related objects as well</param> |
3217 | ········public virtual void MakeHollow(object o, bool recursive) |
3218 | ········{ |
3219 | ············IPersistenceCapable pc = CheckPc(o); |
3220 | ············if(pc.NDOObjectState == NDOObjectState.Hollow) |
3221 | ················return; |
3222 | ············if(pc.NDOObjectState != NDOObjectState.Persistent) |
3223 | ············{ |
3224 | ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")"); |
3225 | ············} |
3226 | ············pc.NDOObjectState = NDOObjectState.Hollow; |
3227 | ············MakeRelationsHollow(pc, recursive); |
3228 | ········} |
3229 | |
3230 | ········/// <summary> |
3231 | ········/// Make all objects of a list hollow. |
3232 | ········/// </summary> |
3233 | ········/// <param name="list">the list of objects that should be made hollow</param> |
3234 | ········public virtual void MakeHollow(System.Collections.IList list) |
3235 | ········{ |
3236 | ············MakeHollow(list, false); |
3237 | ········} |
3238 | |
3239 | ········/// <summary> |
3240 | ········/// Make all objects of a list hollow. |
3241 | ········/// </summary> |
3242 | ········/// <param name="list">the list of objects that should be made hollow</param> |
3243 | ········/// <param name="recursive">if true then unload related objects as well</param> |
3244 | ········public void MakeHollow(System.Collections.IList list, bool recursive) |
3245 | ········{ |
3246 | ············foreach (IPersistenceCapable pc in list) |
3247 | ················MakeHollow(pc, recursive);················ |
3248 | ········} |
3249 | |
3250 | ········/// <summary> |
3251 | ········/// Make all unlocked objects in the cache hollow. |
3252 | ········/// </summary> |
3253 | ········public virtual void MakeAllHollow() |
3254 | ········{ |
3255 | ············foreach(var pc in cache.UnlockedObjects) |
3256 | ············{ |
3257 | ················MakeHollow(pc, false); |
3258 | ············} |
3259 | ········} |
3260 | |
3261 | ········/// <summary> |
3262 | ········/// Make relations hollow. |
3263 | ········/// </summary> |
3264 | ········/// <param name="pc">The parent object</param> |
3265 | ········/// <param name="recursive">If true, the function unloads related objects as well.</param> |
3266 | ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive) |
3267 | ········{ |
3268 | ············Class c = GetClass(pc); |
3269 | ············foreach(Relation r in c.Relations) |
3270 | ············{ |
3271 | ················if (r.Multiplicity == RelationMultiplicity.Element) |
3272 | ················{ |
3273 | ····················mappings.SetRelationField(pc, r.FieldName, null); |
3274 | ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName); |
3275 | ····················//····················if((null != child) && recursive) |
3276 | ····················//····················{ |
3277 | ····················//························MakeHollow(child, true); |
3278 | ····················//····················} |
3279 | ················} |
3280 | ················else |
3281 | ················{ |
3282 | ····················if (!pc.NDOGetLoadState(r.Ordinal)) |
3283 | ························continue; |
3284 | ····················// Help GC by clearing lists |
3285 | ····················IList l = mappings.GetRelationContainer(pc, r); |
3286 | ····················if(l != null) |
3287 | ····················{ |
3288 | ························if(recursive) |
3289 | ························{ |
3290 | ····························MakeHollow(l, true); |
3291 | ························} |
3292 | ························l.Clear(); |
3293 | ····················} |
3294 | ····················// Hollow relation |
3295 | ····················mappings.SetRelationContainer(pc, r, null); |
3296 | ················} |
3297 | ············} |
3298 | ············ClearRelationState(pc); |
3299 | ········} |
3300 | |
3301 | ········private void ClearRelationState(IPersistenceCapable pc) |
3302 | ········{ |
3303 | ············Class cl = GetClass(pc); |
3304 | ············foreach(Relation r in cl.Relations) |
3305 | ················pc.NDOSetLoadState(r.Ordinal, false); |
3306 | ········} |
3307 | |
3308 | ········private void SetRelationState(IPersistenceCapable pc) |
3309 | ········{ |
3310 | ············Class cl = GetClass(pc); |
3311 | ············// Due to a bug in the enhancer the constructors are not always patched right, |
3312 | ············// so NDOLoadState might be uninitialized |
3313 | ············if (pc.NDOLoadState == null) |
3314 | ············{ |
3315 | ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic); |
3316 | ················if (fi == null) |
3317 | ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState"); |
3318 | ················fi.SetValue(pc, new LoadState()); |
3319 | ············} |
3320 | |
3321 | ············// After serialization the relation load state is null |
3322 | ············if (pc.NDOLoadState.RelationLoadState == null) |
3323 | ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize); |
3324 | ············foreach(Relation r in cl.Relations) |
3325 | ················pc.NDOSetLoadState(r.Ordinal, true); |
3326 | ········} |
3327 | |
3328 | ········/// <summary> |
3329 | ········/// Creates an object of a given type and resolves constructor parameters using the container. |
3330 | ········/// </summary> |
3331 | ········/// <param name="t">The type of the persistent object</param> |
3332 | ········/// <returns></returns> |
3333 | ········public IPersistenceCapable CreateObject(Type t) |
3334 | ········{ |
3335 | ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t ); |
3336 | ········} |
3337 | |
3338 | ········/// <summary> |
3339 | ········/// Creates an object of a given type and resolves constructor parameters using the container. |
3340 | ········/// </summary> |
3341 | ········/// <typeparam name="T">The type of the object to create.</typeparam> |
3342 | ········/// <returns></returns> |
3343 | ········public T CreateObject<T>() |
3344 | ········{ |
3345 | ············return (T)CreateObject( typeof( T ) ); |
3346 | ········} |
3347 | |
3348 | ········/// <summary> |
3349 | ········/// Gets the requested object. It first builds an ObjectId using the type and the |
3350 | ········/// key data. Then it uses FindObject to retrieve the object. No database access |
3351 | ········/// is performed. |
3352 | ········/// </summary> |
3353 | ········/// <param name="t">The type of the object to retrieve.</param> |
3354 | ········/// <param name="keyData">The key value to build the object id.</param> |
3355 | ········/// <returns>A hollow object</returns> |
3356 | ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks> |
3357 | ········public IPersistenceCapable FindObject(Type t, object keyData) |
3358 | ········{ |
3359 | ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager); |
3360 | ············return FindObject(oid); |
3361 | ········} |
3362 | |
3363 | ········/// <summary> |
3364 | ········/// Finds an object using a short id. |
3365 | ········/// </summary> |
3366 | ········/// <param name="encodedShortId"></param> |
3367 | ········/// <returns></returns> |
3368 | ········public IPersistenceCapable FindObject(string encodedShortId) |
3369 | ········{ |
3370 | ············string shortId = encodedShortId.Decode(); |
3371 | ············string[] arr = shortId.Split( '-' ); |
3372 | ············if (arr.Length != 3) |
3373 | ················throw new ArgumentException( "The format of the string is not valid", "shortId" ); |
3374 | ············Type t = shortId.GetObjectType(this);··// try readable format |
3375 | ············if (t == null) |
3376 | ············{ |
3377 | ················int typeCode = 0; |
3378 | ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode )) |
3379 | ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" ); |
3380 | ················t = this.typeManager[typeCode]; |
3381 | ················if (t == null) |
3382 | ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" ); |
3383 | ············} |
3384 | |
3385 | ············Class cls = GetClass( t ); |
3386 | ············if (cls == null) |
3387 | ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" ); |
3388 | |
3389 | ············object[] keydata = new object[cls.Oid.OidColumns.Count]; |
3390 | ············string[] oidValues = arr[2].Split( ' ' ); |
3391 | |
3392 | ············int i = 0; |
3393 | ············foreach (var oidValue in oidValues) |
3394 | ············{ |
3395 | ················Type oidType = cls.Oid.OidColumns[i].SystemType; |
3396 | ················if (oidType == typeof( int )) |
3397 | ················{ |
3398 | ····················int key; |
3399 | ····················if (!int.TryParse( oidValue, out key )) |
3400 | ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) ); |
3401 | ····················if (key > (int.MaxValue >> 1)) |
3402 | ························key = -(int.MaxValue - key); |
3403 | ····················keydata[i] = key; |
3404 | ················} |
3405 | ················else if (oidType == typeof( Guid )) |
3406 | ················{ |
3407 | ····················Guid key; |
3408 | ····················if (!Guid.TryParse( oidValue, out key )) |
3409 | ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) ); |
3410 | ····················keydata[i] = key; |
3411 | ················} |
3412 | ················else if (oidType == typeof( string )) |
3413 | ················{ |
3414 | ····················keydata[i] = oidValue; |
3415 | ················} |
3416 | ················else |
3417 | ················{ |
3418 | ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) ); |
3419 | ················} |
3420 | |
3421 | ················i++; |
3422 | ············} |
3423 | |
3424 | ············if (keydata.Length == 1) |
3425 | ················return FindObject( t, keydata[0] ); |
3426 | |
3427 | ············return FindObject( t, keydata );············ |
3428 | ········} |
3429 | |
3430 | ········/// <summary> |
3431 | ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow) |
3432 | ········/// instance of the object is returned. In either case, the DB is not accessed! |
3433 | ········/// </summary> |
3434 | ········/// <param name="id">Object id</param> |
3435 | ········/// <returns>The object to retrieve in hollow state</returns>········ |
3436 | ········public IPersistenceCapable FindObject(ObjectId id) |
3437 | ········{ |
3438 | ············if(id == null) |
3439 | ············{ |
3440 | ················throw new ArgumentNullException("id"); |
3441 | ············} |
3442 | |
3443 | ············if(!id.IsValid()) |
3444 | ············{ |
3445 | ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist"); |
3446 | ············} |
3447 | |
3448 | ············IPersistenceCapable pc = cache.GetObject(id); |
3449 | ············if(pc == null) |
3450 | ············{ |
3451 | ················pc = CreateObject(id.Id.Type); |
3452 | ················pc.NDOObjectId = id; |
3453 | ················pc.NDOStateManager = sm; |
3454 | ················pc.NDOObjectState = NDOObjectState.Hollow; |
3455 | ················cache.UpdateCache(pc); |
3456 | ············} |
3457 | ············return pc; |
3458 | ········} |
3459 | |
3460 | |
3461 | ········/// <summary> |
3462 | ········/// Reload an object from the database. |
3463 | ········/// </summary> |
3464 | ········/// <param name="o">The object to be reloaded.</param> |
3465 | ········public virtual void Refresh(object o) |
3466 | ········{ |
3467 | ············IPersistenceCapable pc = CheckPc(o); |
3468 | ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted) |
3469 | ············{ |
3470 | ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation"); |
3471 | ············} |
3472 | |
3473 | ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty) |
3474 | ················return; // Cannot update objects in current transaction |
3475 | |
3476 | ············MakeHollow(pc); |
3477 | ············LoadData(pc); |
3478 | ········} |
3479 | |
3480 | ········/// <summary> |
3481 | ········/// Refresh a list of objects. |
3482 | ········/// </summary> |
3483 | ········/// <param name="list">The list of objects to be refreshed.</param> |
3484 | ········public virtual void Refresh(IList list) |
3485 | ········{ |
3486 | ············foreach (IPersistenceCapable pc in list) |
3487 | ················Refresh(pc);························ |
3488 | ········} |
3489 | |
3490 | ········/// <summary> |
3491 | ········/// Refreshes all unlocked objects in the cache. |
3492 | ········/// </summary> |
3493 | ········public virtual void RefreshAll() |
3494 | ········{ |
3495 | ············Refresh( cache.UnlockedObjects.ToList() ); |
3496 | ········} |
3497 | |
3498 | ········/// <summary> |
3499 | ········/// Closes the PersistenceManager and releases all resources. |
3500 | ········/// </summary> |
3501 | ········public override void Close() |
3502 | ········{ |
3503 | ············if (this.isClosing) |
3504 | ················return; |
3505 | ············this.isClosing = true; |
3506 | ············TransactionScope.Dispose(); |
3507 | ············UnloadCache(); |
3508 | ············base.Close(); |
3509 | ········} |
3510 | |
3511 | ········internal void LogIfVerbose( string msg ) |
3512 | ········{ |
3513 | ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug )) |
3514 | ················Logger.LogDebug( msg ); |
3515 | ········} |
3516 | |
3517 | |
3518 | ········#endregion |
3519 | |
3520 | |
3521 | #region Class extent |
3522 | ········/// <summary> |
3523 | ········/// Gets all objects of a given class. |
3524 | ········/// </summary> |
3525 | ········/// <param name="t">the type of the class</param> |
3526 | ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns> |
3527 | ········public virtual IList GetClassExtent(Type t) |
3528 | ········{ |
3529 | ············return GetClassExtent(t, true); |
3530 | ········} |
3531 | |
3532 | ········/// <summary> |
3533 | ········/// Gets all objects of a given class. |
3534 | ········/// </summary> |
3535 | ········/// <param name="t">The type of the class.</param> |
3536 | ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param> |
3537 | ········/// <returns>A list of all persistent objects of the given class.</returns> |
3538 | ········/// <remarks>Subclasses of the given type are not fetched.</remarks> |
3539 | ········public virtual IList GetClassExtent(Type t, bool hollow) |
3540 | ········{ |
3541 | ············IQuery q = NewQuery( t, null, hollow ); |
3542 | ············return q.Execute(); |
3543 | ········} |
3544 | |
3545 | #endregion |
3546 | |
3547 | #region Query engine |
3548 | |
3549 | |
3550 | ········/// <summary> |
3551 | ········/// Returns a virtual table for Linq queries. |
3552 | ········/// </summary> |
3553 | ········/// <typeparam name="T"></typeparam> |
3554 | ········/// <returns></returns> |
3555 | ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable |
3556 | ········{ |
3557 | ············return new VirtualTable<T>( this ); |
3558 | ········} |
3559 | |
3560 | ········ |
3561 | ········/// <summary> |
3562 | ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B, |
3563 | ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have |
3564 | ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To |
3565 | ········/// not lose the foreign key it will be stored as part of the object. |
3566 | ········/// </summary> |
3567 | ········/// <param name="cl"></param> |
3568 | ········/// <param name="pc"></param> |
3569 | ········/// <param name="row"></param> |
3570 | ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row) |
3571 | ········{ |
3572 | ············if (cl.FKColumnNames != null && pc.NDOLoadState != null) |
3573 | ············{ |
3574 | ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump()); |
3575 | ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count()); |
3576 | ················foreach(string colName in cl.FKColumnNames) |
3577 | ····················kvl.Add(new KeyValuePair(colName, row[colName])); |
3578 | ················pc.NDOLoadState.LostRowInfo = kvl;················ |
3579 | ············} |
3580 | ········} |
3581 | |
3582 | ········/// <summary> |
3583 | ········/// Writes information into the data row, which cannot be stored in the object. |
3584 | ········/// </summary> |
3585 | ········/// <param name="cl"></param> |
3586 | ········/// <param name="pc"></param> |
3587 | ········/// <param name="row"></param> |
3588 | ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row) |
3589 | ········{ |
3590 | ············if (cl.FKColumnNames != null) |
3591 | ············{ |
3592 | ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump()); |
3593 | ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo; |
3594 | ················if (kvl == null) |
3595 | ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump()); |
3596 | ················foreach (KeyValuePair pair in kvl) |
3597 | ····················row[(string) pair.Key] = pair.Value; |
3598 | ············} |
3599 | ········} |
3600 | |
3601 | ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row) |
3602 | ········{ |
3603 | ············ReadObject(pc, row, cl.ColumnNames, 0); |
3604 | ············ReadTimeStamp(cl, pc, row); |
3605 | ············ReadLostForeignKeysFromRow(cl, pc, row); |
3606 | ············LoadRelated1To1Objects(pc, row); |
3607 | ············pc.NDOObjectState = NDOObjectState.Persistent; |
3608 | ········} |
3609 | |
3610 | |
3611 | ········/// <summary> |
3612 | ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type. |
3613 | ········/// </summary> |
3614 | ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow) |
3615 | ········{ |
3616 | ············IList queryResult = GenericListReflector.CreateList(t, rows.Count); |
3617 | ············if (rows.Count == 0) |
3618 | ················return queryResult; |
3619 | |
3620 | ············IList callbackObjects = new ArrayList(); |
3621 | ············Class cl = GetClass(t); |
3622 | ············if (t.IsGenericTypeDefinition) |
3623 | ············{ |
3624 | ················if (cl.TypeNameColumn == null) |
3625 | ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file."); |
3626 | ················IEnumerator en = rows.GetEnumerator(); |
3627 | ················en.MoveNext();··// Move to the first element |
3628 | ················DataRow testrow = (DataRow)en.Current; |
3629 | ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null) |
3630 | ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema."); |
3631 | ············} |
3632 | |
3633 | ············foreach(DataRow row in rows) |
3634 | ············{ |
3635 | ················Type concreteType = t; |
3636 | ················if (t.IsGenericTypeDefinition)··// type information is in the row |
3637 | ················{ |
3638 | ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value) |
3639 | ····················{ |
3640 | ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager); |
3641 | ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString()); |
3642 | ····················} |
3643 | ····················string typeStr = (string)row[cl.TypeNameColumn.Name]; |
3644 | ····················concreteType = Type.GetType(typeStr); |
3645 | ····················if (concreteType == null) |
3646 | ························throw new NDOException(106, "Can't load generic type " + typeStr); |
3647 | ················} |
3648 | ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager); |
3649 | ················IPersistenceCapable pc = cache.GetObject(id);················ |
3650 | ················if(pc == null) |
3651 | ················{ |
3652 | ····················pc = CreateObject( concreteType ); |
3653 | ····················pc.NDOObjectId = id; |
3654 | ····················pc.NDOStateManager = sm; |
3655 | ····················// If the object shouldn't be hollow, this will be overwritten later. |
3656 | ····················pc.NDOObjectState = NDOObjectState.Hollow; |
3657 | ················} |
3658 | ················// If we have found a non hollow object, the time stamp remains the old one. |
3659 | ················// In every other case we use the new time stamp. |
3660 | ················// Note, that there could be a hollow object in the cache. |
3661 | ················// We need the time stamp in hollow objects in order to correctly |
3662 | ················// delete objects using fake rows. |
3663 | ················if (pc.NDOObjectState == NDOObjectState.Hollow) |
3664 | ················{ |
3665 | ····················ReadTimeStamp(cl, pc, row); |
3666 | ················} |
3667 | ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty) |
3668 | ················{ |
3669 | ····················Row2Object(cl, pc, row); |
3670 | ····················if ((pc as IPersistenceNotifiable) != null) |
3671 | ························callbackObjects.Add(pc); |
3672 | ················} |
3673 | |
3674 | ················cache.UpdateCache(pc); |
3675 | ················queryResult.Add(pc); |
3676 | ············} |
3677 | ············// Make shure this is the last statement before returning |
3678 | ············// to the caller, so the user can recursively use persistent |
3679 | ············// objects |
3680 | ············foreach(IPersistenceNotifiable ipn in callbackObjects) |
3681 | ················ipn.OnLoaded(); |
3682 | |
3683 | ············return queryResult; |
3684 | ········} |
3685 | |
3686 | #endregion |
3687 | |
3688 | #region Cache Management |
3689 | ········/// <summary> |
3690 | ········/// Remove all unused entries from the cache. |
3691 | ········/// </summary> |
3692 | ········public void CleanupCache() |
3693 | ········{ |
3694 | ············GC.Collect(GC.MaxGeneration); |
3695 | ············GC.WaitForPendingFinalizers(); |
3696 | ············cache.Cleanup(); |
3697 | ········} |
3698 | |
3699 | ········/// <summary> |
3700 | ········/// Remove all unlocked objects from the cache. Use with care! |
3701 | ········/// </summary> |
3702 | ········public void UnloadCache() |
3703 | ········{ |
3704 | ············cache.Unload(); |
3705 | ········} |
3706 | #endregion |
3707 | |
3708 | ········/// <summary> |
3709 | ········/// Default encryption key for NDO |
3710 | ········/// </summary> |
3711 | ········/// <remarks> |
3712 | ········/// We recommend strongly to use an own encryption key, which can be set with this property. |
3713 | ········/// </remarks> |
3714 | ········public virtual byte[] EncryptionKey |
3715 | ········{ |
3716 | ············get |
3717 | ············{ |
3718 | ················if (this.encryptionKey == null) |
3719 | ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66, |
3720 | ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D }; |
3721 | ················return this.encryptionKey; |
3722 | ············} |
3723 | ············set { this.encryptionKey = value; } |
3724 | ········} |
3725 | |
3726 | ········/// <summary> |
3727 | ········/// Hollow mode: If true all objects are made hollow after each transaction. |
3728 | ········/// </summary> |
3729 | ········public virtual bool HollowMode |
3730 | ········{ |
3731 | ············get { return hollowMode; } |
3732 | ············set { hollowMode = value; } |
3733 | ········} |
3734 | |
3735 | ········internal TypeManager TypeManager |
3736 | ········{ |
3737 | ············get { return typeManager; } |
3738 | ········} |
3739 | |
3740 | ········/// <summary> |
3741 | ········/// Sets or gets transaction mode. Uses TransactionMode enum. |
3742 | ········/// </summary> |
3743 | ········/// <remarks> |
3744 | ········/// Set this value before you start any transactions. |
3745 | ········/// </remarks> |
3746 | ········public TransactionMode TransactionMode |
3747 | ········{ |
3748 | ············get { return TransactionScope.TransactionMode; } |
3749 | ············set { TransactionScope.TransactionMode = value; } |
3750 | ········} |
3751 | |
3752 | ········/// <summary> |
3753 | ········/// Sets or gets the Isolation Level. |
3754 | ········/// </summary> |
3755 | ········/// <remarks> |
3756 | ········/// Set this value before you start any transactions. |
3757 | ········/// </remarks> |
3758 | ········public IsolationLevel IsolationLevel |
3759 | ········{ |
3760 | ············get { return TransactionScope.IsolationLevel; } |
3761 | ············set { TransactionScope.IsolationLevel = value; } |
3762 | ········} |
3763 | |
3764 | ········internal class MappingTableEntry |
3765 | ········{ |
3766 | ············private IPersistenceCapable parentObject; |
3767 | ············private IPersistenceCapable relatedObject; |
3768 | ············private Relation relation; |
3769 | ············private bool deleteEntry; |
3770 | ············ |
3771 | ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false) |
3772 | ············{ |
3773 | ············} |
3774 | ············ |
3775 | ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry) |
3776 | ············{ |
3777 | ················parentObject = pc; |
3778 | ················relatedObject = relObj; |
3779 | ················relation = r; |
3780 | ················this.deleteEntry = deleteEntry; |
3781 | ············} |
3782 | |
3783 | ············public bool DeleteEntry |
3784 | ············{ |
3785 | ················get |
3786 | ················{ |
3787 | ····················return deleteEntry; |
3788 | ················} |
3789 | ············} |
3790 | |
3791 | ············public IPersistenceCapable ParentObject |
3792 | ············{ |
3793 | ················get |
3794 | ················{ |
3795 | ····················return parentObject; |
3796 | ················} |
3797 | ············} |
3798 | |
3799 | ············public IPersistenceCapable RelatedObject |
3800 | ············{ |
3801 | ················get |
3802 | ················{ |
3803 | ····················return relatedObject; |
3804 | ················} |
3805 | ············} |
3806 | |
3807 | ············public Relation Relation |
3808 | ············{ |
3809 | ················get |
3810 | ················{ |
3811 | ····················return relation; |
3812 | ················} |
3813 | ············} |
3814 | ········} |
3815 | |
3816 | ········/// <summary> |
3817 | ········/// Get a DataRow representing the given object. |
3818 | ········/// </summary> |
3819 | ········/// <param name="o"></param> |
3820 | ········/// <returns></returns> |
3821 | ········public DataRow GetClonedDataRow( object o ) |
3822 | ········{ |
3823 | ············IPersistenceCapable pc = CheckPc( o ); |
3824 | |
3825 | ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient) |
3826 | ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." ); |
3827 | |
3828 | ············DataRow row = cache.GetDataRow( pc ); |
3829 | ············DataTable newTable = row.Table.Clone(); |
3830 | ············newTable.ImportRow( row ); |
3831 | ············row = newTable.Rows[0]; |
3832 | |
3833 | ············Class cls = mappings.FindClass(o.GetType()); |
3834 | ············WriteObject( pc, row, cls.ColumnNames ); |
3835 | ············WriteForeignKeysToRow( pc, row ); |
3836 | |
3837 | ············return row; |
3838 | ········} |
3839 | |
3840 | ········/// <summary> |
3841 | ········/// Gets an object, which shows all changes applied to the given object. |
3842 | ········/// This function can be used to build an audit system. |
3843 | ········/// </summary> |
3844 | ········/// <param name="o"></param> |
3845 | ········/// <returns>An ExpandoObject</returns> |
3846 | ········public ChangeLog GetChangeSet( object o ) |
3847 | ········{ |
3848 | ············var changeLog = new ChangeLog(this); |
3849 | ············changeLog.Initialize( o ); |
3850 | ············return changeLog; |
3851 | ········} |
3852 | |
3853 | ········/// <summary> |
3854 | ········/// Outputs a revision number representing the assembly version. |
3855 | ········/// </summary> |
3856 | ········/// <remarks>This can be used for debugging purposes</remarks> |
3857 | ········public int Revision |
3858 | ········{ |
3859 | ············get |
3860 | ············{ |
3861 | ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version; |
3862 | ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision ); |
3863 | ················return int.Parse( vstring ); |
3864 | ············} |
3865 | ········} |
3866 | ····} |
3867 | } |
3868 |