Datei: NDODLL/PersistenceManager.cs

Last Commit (a99de3b)
1 //
2 // Copyright (c) 2002-2016 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.Dynamic;
32 using System.Text.RegularExpressions;
33 using System.Linq;
34 using System.Xml.Linq;
35
36 using NDO.Mapping;
37 using NDOInterfaces;
38 using NDO.ShortId;
39 using System.Globalization;
40 using NDO.Linq;
41 using NDO.Query;
42 using ST = System.Transactions;
43 using NDO.Configuration;
44 using NDO.ChangeLogging;
45
46 namespace NDO
47 {
48 ····/// <summary>
49 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
50 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
51 ····/// </summary>
52 ····public delegate void CollisionHandler(object o);
53 ····/// <summary>
54 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
55 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
56 ····/// </summary>
57 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
58 ····/// <summary>
59 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
60 ····/// </summary>
61 ····public delegate void OnSavingHandler(ICollection l);
62 ····/// <summary>
63 ····/// Delegate type for the OnSavedEvent.
64 ····/// </summary>
65 ····/// <param name="auditSet"></param>
66 ····public delegate void OnSavedHandler(AuditSet auditSet);
67
68 ····/// <summary>
69 ····/// 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.
70 ····/// </summary>
71 ····/// <param name="pc"></param>
72 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
73 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
74 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
75
76 ····/// <summary>
77 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
78 ····/// 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.
79 ····/// </summary>
80 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
81 ····{········
82 ········private bool hollowMode = false;
83 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
84
85 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
86 ········private ObjectLock removeLock = new ObjectLock();
87 ········private ObjectLock addLock = new ObjectLock();
88 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
89 ········// List of created objects that use mapping table and need to be stored in mapping table
90 ········// after they have been stored to the database to update foreign keys.
91 ········private ArrayList createdMappingTableObjects = new ArrayList();··
92 ········private TypeManager typeManager;
93 ········internal bool DeferredMode { get; private set; }
94 ········private INDOTransactionScope transactionScope;
95 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ConfigContainer.Resolve<INDOTransactionScope>());········
96
97 ········private OpenConnectionListener openConnectionListener;
98
99 ········/// <summary>
100 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
101 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
102 ········/// </summary>
103 ········public event CollisionHandler CollisionEvent;
104
105 ········/// <summary>
106 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
107 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
108 ········/// </summary>
109 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
110
111 ········/// <summary>
112 ········/// Register a listener to this event, if you want to be notified about the end
113 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
114 ········/// during the transaction and are to be saved or deleted.
115 ········/// </summary>
116 ········public event OnSavingHandler OnSavingEvent;
117 ········/// <summary>
118 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
119 ········/// </summary>
120 ········public event OnSavedHandler OnSavedEvent;
121 ········
122 ········private const string hollowMarker = "Hollow";
123 ········private byte[] encryptionKey;
124 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
125 ········private bool isClosing = false;
126
127 ········/// <summary>
128 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
129 ········/// </summary>
130 ········protected internal List<RelationChangeRecord> RelationChanges
131 ········{
132 ············get { return this.relationChanges; }
133 ········}
134
135 ········/// <summary>
136 ········/// Initializes a new PersistenceManager instance.
137 ········/// </summary>
138 ········/// <param name="mappingFileName"></param>
139 ········protected override void Init(string mappingFileName)
140 ········{
141 ············try
142 ············{
143 ················base.Init(mappingFileName);
144 ············}
145 ············catch (Exception ex)
146 ············{
147 ················if (ex is NDOException)
148 ····················throw ex;
149 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
150 ············}
151
152 ········}
153
154 ········/// <summary>
155 ········/// Initializes the persistence manager
156 ········/// </summary>
157 ········/// <remarks>
158 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
159 ········/// </remarks>
160 ········/// <param name="mapping"></param>
161 ········internal override void Init( Mappings mapping )
162 ········{
163 ············ConfigContainer.RegisterType<INDOTransactionScope, NDOTransactionScope>();
164
165 ············base.Init( mapping );
166
167 ············string dir = Path.GetDirectoryName( mapping.FileName );
168
169 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
170 ············typeManager = new TypeManager( typesFile, this.mappings );
171
172 ············sm = new StateManager( this );
173
174 ············InitClasses();
175 ········}
176
177
178 ········/// <summary>
179 ········/// Standard Constructor.
180 ········/// </summary>
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() : base()
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 ········/// <remarks>Only the Professional and Enterprise
197 ········/// Editions can handle more than one mapping file.</remarks>
198 ········public PersistenceManager(string mappingFile) : base (mappingFile)
199 ········{
200 ········}
201
202 ········/// <summary>
203 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
204 ········/// </summary>
205 ········/// <param name="mapping">The cached mapping object</param>
206 ········public PersistenceManager(NDOMapping mapping) : base (mapping)
207 ········{
208 ········}
209
210 ········#region Object Container Stuff
211 ········/// <summary>
212 ········/// Gets a container of all loaded objects and tries to load all child objects,
213 ········/// which are reachable through composite relations.
214 ········/// </summary>
215 ········/// <returns>An ObjectContainer object.</returns>
216 ········/// <remarks>
217 ········/// It is not recommended, to transfer objects with a state other than Hollow,
218 ········/// Persistent, or Transient.
219 ········/// The transfer format is binary.
220 ········/// </remarks>
221 ········public ObjectContainer GetObjectContainer()
222 ········{
223 ············IList l = this.cache.AllObjects;
224 ············foreach(IPersistenceCapable pc in l)
225 ············{
226 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
227 ················{
228 ····················if (this.VerboseMode)
229 ························this.LogAdapter.Warn("Call to GetObjectContainer returns changed objects.");
230 ····················System.Diagnostics.Trace.WriteLine("NDO warning: Call to GetObjectContainer returns changed objects.");
231 ················}
232 ············}
233 ············ObjectContainer oc = new ObjectContainer();
234 ············oc.AddList(l);
235 ············return oc;
236 ········}
237
238 ········/// <summary>
239 ········/// Returns a container of all objects provided in the objects list and searches for
240 ········/// child objects according to the serFlags.
241 ········/// </summary>
242 ········/// <param name="objects">The list of the root objects to add to the container.</param>
243 ········/// <returns>An ObjectContainer object.</returns>
244 ········/// <remarks>
245 ········/// It is not recommended, to transfer objects with a state other than Hollow,
246 ········/// Persistent, or Transient.
247 ········/// </remarks>
248 ········public ObjectContainer GetObjectContainer(IList objects)
249 ········{
250 ············foreach(object o in objects)
251 ············{
252 ················CheckPc(o);
253 ················IPersistenceCapable pc = o as IPersistenceCapable;
254 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
255 ····················LoadData(pc);
256 ············}
257 ············ObjectContainer oc = new ObjectContainer();
258 ············oc.AddList(objects);
259 ············return oc;
260 ········}
261
262
263 ········/// <summary>
264 ········/// Returns a container containing the provided object
265 ········/// and tries to load all child objects
266 ········/// reachable through composite relations.
267 ········/// </summary>
268 ········/// <param name="obj">The object to be added to the container.</param>
269 ········/// <returns>An ObjectContainer object.</returns>
270 ········/// <remarks>
271 ········/// It is not recommended, to transfer objects with a state other than Hollow,
272 ········/// Persistent, or Transient.
273 ········/// The transfer format is binary.
274 ········/// </remarks>
275 ········public ObjectContainer GetObjectContainer(Object obj)
276 ········{
277 ············CheckPc(obj);
278 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
279 ················LoadData(obj);
280 ············ObjectContainer oc = new ObjectContainer();
281 ············oc.AddObject(obj);
282 ············return oc;
283 ········}
284
285 ········/// <summary>
286 ········/// Merges an object container to the active objects in the pm. All changes and the state
287 ········/// of the objects will be taken over by the pm.
288 ········/// </summary>
289 ········/// <remarks>
290 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
291 ········/// The flag MarkAsTransient can be used to perform a kind
292 ········/// of object based replication using the ObjectContainer class.
293 ········/// Objects, which are persistent at one machine, can be transfered
294 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
295 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
296 ········/// transient object tree.
297 ········/// There is one difference to freshly created objects: If an object id exists, it will be
298 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
299 ········/// oids will be reused by the receiving PersistenceManager.
300 ········/// </remarks>
301 ········/// <param name="ocb">The object container to be merged.</param>
302 ········public void MergeObjectContainer(ObjectContainerBase ocb)
303 ········{
304 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
305 ············if (csc != null)
306 ············{
307 ················MergeChangeSet(csc);
308 ················return;
309 ············}
310 ············ObjectContainer oc = ocb as ObjectContainer;
311 ············if (oc != null)
312 ············{
313 ················InternalMergeObjectContainer(oc);
314 ················return;
315 ············}
316 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
317 ········}
318
319
320 ········void InternalMergeObjectContainer(ObjectContainer oc)
321 ········{
322 ············// TODO: Check, if other states are useful. Find use scenarios.
323 ············foreach(IPersistenceCapable pc in oc.RootObjects)
324 ············{
325 ················if (pc.NDOObjectState == NDOObjectState.Transient)
326 ····················MakePersistent(pc);
327 ············}
328 ············foreach(IPersistenceCapable pc in oc.RootObjects)
329 ············{
330 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
331 ············}
332 ········}
333
334 ········void MergeChangeSet(ChangeSetContainer cs)
335 ········{
336 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
337 ············{
338 ················InternalMakePersistent(pc, false);
339 ············}
340 ············foreach(ObjectId oid in cs.DeletedObjects)
341 ············{
342 ················IPersistenceCapable pc2 = FindObject(oid);
343 ················Delete(pc2);
344 ············}
345 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
346 ············{
347 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
348 ················Class pcClass = GetClass(pc);
349 ················// Make sure, the object is loaded.
350 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
351 ····················LoadData(pc2);
352 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
353 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
354 ················DataRow row = entry.row;
355 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
356 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
357 ············}
358 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
359 ············{
360 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
361 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
362 ················Class pcClass = GetClass(parent);
363 ················Relation r = pcClass.FindRelation(rcr.RelationName);
364 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
365 ····················LoadRelation(parent, r, true);
366 ················if (rcr.IsAdded)
367 ················{
368 ····················InternalAddRelatedObject(parent, r, child, true);
369 ····················if (r.Multiplicity == RelationMultiplicity.Element)
370 ····················{
371 ························mappings.SetRelationField(parent, r.FieldName, child);
372 ····················}
373 ····················else
374 ····················{
375 ························IList l = mappings.GetRelationContainer(parent, r);
376 ························l.Add(child);
377 ····················}
378 ················}
379 ················else
380 ················{
381 ····················RemoveRelatedObject(parent, r.FieldName, child);
382 ····················if (r.Multiplicity == RelationMultiplicity.Element)
383 ····················{
384 ························mappings.SetRelationField(parent, r.FieldName, null);
385 ····················}
386 ····················else
387 ····················{
388 ························IList l = mappings.GetRelationContainer(parent, r);
389 ························try
390 ························{
391 ····························ObjectListManipulator.Remove(l, child);
392 ························}
393 ························catch
394 ························{
395 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
396 ························}
397 ····················}
398 ················}
399 ············}
400
401 ········}
402 ········#endregion
403
404 ········#region Implementation of IPersistenceManager
405
406 ········// Complete documentation can be found in IPersistenceManager
407
408
409 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
410 ········{
411 ············if (!cl.Oid.IsDependent)
412 ················return;
413 ············WriteForeignKeysToRow(pc, row);
414 ········}
415
416 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
417 ········{
418 ············// Object is now under control of the state manager
419 ············pc.NDOStateManager = sm;
420
421 ············Type pcType = pc.GetType();
422 ············Class pcClass = GetClass(pc);
423
424 ············// Create new object
425 ············DataTable dt = GetTable(pcType);
426 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
427
428 ············// In case of a Guid oid the value will be computed now.
429 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
430 ············{
431 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
432 ················{
433 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
434 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
435 ····················else
436 ························row[oidColumn.Name] = Guid.NewGuid();
437 ················}
438 ············}
439
440 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
441
442 ············// If the object is merged from an ObjectContainer, the id should be reused,
443 ············// if the id is client generated (not Autoincremented).
444 ············// In every other case, the oid is set to null, to force generating a new oid.
445 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
446 ············if ((object)pc.NDOObjectId != null)
447 ············{
448 ················bool hasAutoincrement = false;
449 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
450 ················{
451 ····················if (oidColumn.AutoIncremented)
452 ····················{
453 ························hasAutoincrement = true;
454 ························break;
455 ····················}
456 ················}
457 ················if (hasAutoincrement) // can't store existing id
458 ················{
459 ····················pc.NDOObjectId = null;
460 ····················fireIdGeneration = true;
461 ················}
462 ············}
463
464 ············// In case of a dependent class the oid has to be read from the fields according to the relations
465 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
466
467 ············if ((object)pc.NDOObjectId == null)
468 ············{
469 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
470 ············}
471
472 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
473 ············{
474 ················if (fireIdGeneration)
475 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
476 ················// At this place the oid might have been
477 ················// - deserialized (MergeObjectContainer)
478 ················// - created using NewObjectId
479 ················// - defined by the IdGenerationEvent
480
481 ················// At this point we have a valid oid.
482 ················// If the object has a field mapped to the oid we have
483 ················// to write back the oid to the field
484 ················int i = 0;
485 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
486 ················{
487 ····················if (oidColumn.FieldName != null)
488 ····················{
489 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
490 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
491 ····················}
492 ····················i++;
493 ················});
494
495
496
497 ················// Now write back the data into the row
498 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
499 ············}
500
501 ············
502 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
503 ············dt.Rows.Add(row);
504
505 ············cache.Register(pc);
506
507 ············// new object that has never been written to the DS
508 ············pc.NDOObjectState = NDOObjectState.Created;
509 ············// Mark all Relations as loaded
510 ············SetRelationState(pc);
511
512 ············if (checkRelations)
513 ············{
514 ················// Handle related objects:
515 ················foreach(Relation r in pcClass.Relations)
516 ················{
517 ····················if (r.Multiplicity == RelationMultiplicity.Element)
518 ····················{
519 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
520 ························if(child != null)
521 ························{
522 ····························AddRelatedObject(pc, r, child);
523 ························}
524 ····················}
525 ····················else
526 ····················{
527 ························IList list = mappings.GetRelationContainer(pc, r);
528 ························if(list != null)
529 ························{
530 ····························foreach(IPersistenceCapable relObj in list)
531 ····························{
532 ································if (relObj != null)
533 ····································AddRelatedObject(pc, r, relObj);
534 ····························}
535 ························}
536 ····················}
537 ················}
538 ············}
539
540 ············var relations··= CollectRelationStates(pc);
541 ············cache.Lock(pc, row, relations);
542 ········}
543
544
545 ········/// <summary>
546 ········/// Make an object persistent.
547 ········/// </summary>
548 ········/// <param name="o">the transient object that should be made persistent</param>
549 ········public void MakePersistent(object o)
550 ········{
551 ············IPersistenceCapable pc = CheckPc(o);
552
553 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
554 ············//Debug.Indent();
555
556 ············if (pc.NDOObjectState != NDOObjectState.Transient)
557 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
558
559 ············InternalMakePersistent(pc, true);
560
561 ········}
562
563
564
565 ········//········/// <summary>
566 ········//········/// Checks, if an object has a valid id, which was created by the database
567 ········//········/// </summary>
568 ········//········/// <param name="pc"></param>
569 ········//········/// <returns></returns>
570 ········//········private bool HasValidId(IPersistenceCapable pc)
571 ········//········{
572 ········//············if (this.IdGenerationEvent != null)
573 ········//················return true;
574 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
575 ········//········}
576
577
578 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
579 ········{
580 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
581 ············if(r.HasSubclasses)
582 ············{
583 ················// we don't support 1:n with foreign fields in subclasses because we would have to
584 ················// search for objects in all subclasses! Instead use a mapping table.
585 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
586 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
587 ············}
588
589 ············if (!makeRelObjPersistent)
590 ················MarkDirty(relObj);
591 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
592 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
593
594 ············if (relObjRow == null)
595 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
596
597 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
598
599 ············if (relObj.NDOLoadState.LostRowInfo == null)
600 ············{
601 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
602 ············}
603 ············else
604 ············{
605 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
606 ············}
607 ········}
608
609 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
610 ········{
611 ············switch(relObj.NDOObjectState)
612 ············{
613 ················case NDOObjectState.Persistent:
614 ····················MarkDirty(relObj);
615 ····················break;
616 ················case NDOObjectState.Hollow:
617 ····················LoadData(relObj);
618 ····················MarkDirty(relObj);
619 ····················break;
620 ············}
621
622 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
623 ············{
624 ················IPersistenceCapable newpc;
625 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
626 ················{
627 ····················if (newpc != pc)
628 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
629 ················}
630 ················else
631 ················{
632 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
633 ················}
634 ············}
635 ············else
636 ············{
637 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
638 ····················LoadRelation(relObj, r.ForeignRelation, true);
639 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
640 ················if(l == null)
641 ················{
642 ····················try
643 ····················{
644 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
645 ····················}
646 ····················catch
647 ····················{
648 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
649 ····················}
650 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
651 ················}
652 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
653 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
654 ················if (!ObjectListManipulator.Contains(l, pc))
655 ····················l.Add(pc);
656 ············}
657 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
658 ········}
659
660
661 ········/// <summary>
662 ········/// Add a related object to the specified object.
663 ········/// </summary>
664 ········/// <param name="pc">the parent object</param>
665 ········/// <param name="fieldName">the field name of the relation</param>
666 ········/// <param name="relObj">the related object that should be added</param>
667 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
668 ········{
669 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
670 ············Relation r = mappings.FindRelation(pc, fieldName);
671 ············AddRelatedObject(pc, r, relObj);
672 ········}
673
674 ········/// <summary>
675 ········/// Core functionality to add an object to a relation container or relation field.
676 ········/// </summary>
677 ········/// <param name="pc"></param>
678 ········/// <param name="r"></param>
679 ········/// <param name="relObj"></param>
680 ········/// <param name="isMerging"></param>
681 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
682 ········{
683 ············
684 ············// avoid recursion
685 ············if (!addLock.GetLock(relObj))
686 ················return;
687
688 ············try
689 ············{
690 ················//TODO: We need a relation management, which is independent of
691 ················//the state management of an object. Currently the relation
692 ················//lists or elements are cached for restore, if an object is marked dirty.
693 ················//Thus we have to mark dirty our parent object in any case at the moment.
694 ················MarkDirty(pc);
695
696 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
697 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
698 ················//The latter happens in CreateAddedObjectRow
699
700 ················Class relClass = GetClass(relObj);
701
702 ················if (r.Multiplicity == RelationMultiplicity.Element
703 ····················&& r.HasSubclasses
704 ····················&& r.MappingTable == null················
705 ····················&& !this.HasOwnerCreatedIds
706 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
707 ····················&& !relClass.HasGuidOid)
708 ················{
709 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
710 ························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.");
711 ····················if (r.Composition)
712 ························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.");
713 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
714 ························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." );
715 ················}
716
717 ················bool isDependent = relClass.Oid.IsDependent;
718
719 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
720 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
721
722 ················// Need to patch pc into the relation relObj->pc, because
723 ················// the oid is built on base of this information
724 ················if (isDependent)
725 ················{
726 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
727 ················}
728
729 ················if (r.Composition || isDependent)
730 ················{
731 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
732 ························MakePersistent(relObj);
733 ················}
734
735 ················if(r.MappingTable == null)
736 ················{
737 ····················if (r.Bidirectional)
738 ····················{
739 ························// This object hasn't been saved yet, so the key is wrong.
740 ························// Therefore, the child must be written twice to update the foreign key.
741 #if trace
742 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
743 ························if (r.Multiplicity == RelationMultiplicity.Element)
744 ····························sb.Append("1");
745 ························else
746 ····························sb.Append("n");
747 ························sb.Append(":");
748 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
749 ····························sb.Append("1");
750 ························else
751 ····························sb.Append("n");
752 ························sb.Append ("OwnCreatedOther");
753 ························sb.Append(relObj.NDOObjectState.ToString());
754 ························sb.Append(' ');
755
756 ························sb.Append(types[0].ToString());
757 ························sb.Append(' ');
758 ························sb.Append(types[1].ToString());
759 ························Debug.WriteLine(sb.ToString());
760 #endif
761 ························//························if (r.Multiplicity == RelationMultiplicity.Element
762 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
763 ························//························{
764 ························// Element means:
765 ························// pc is keyholder
766 ························// -> relObj is saved first
767 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
768 ························// Both are Created - use type sort order
769 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
770 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
771 ························{
772 ····························if (mappings.GetUpdateOrder(pc.GetType())
773 ································< mappings.GetUpdateOrder(relObj.GetType()))
774 ································createdDirectObjects.Add(pc);
775 ····························else
776 ································createdDirectObjects.Add( relObj );
777 ························}
778 ····················}
779 ····················if (r.Multiplicity == RelationMultiplicity.List)
780 ····················{
781 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
782 ····················}
783 ················}
784 ················else
785 ················{
786 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
787 ················}
788 ················if(r.Bidirectional)
789 ················{
790 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
791 ····················{
792 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
793 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
794 ····················}
795 ····················else if ( !addLock.IsLocked( pc ) )
796 ····················{
797 ························PatchForeignRelation( pc, r, relObj );
798 ····················}
799 ················}
800
801 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
802 ············}
803 ············finally
804 ············{
805 ················addLock.Unlock(relObj);
806 ················//Debug.Unindent();
807 ············}
808 ········}
809
810 ········/// <summary>
811 ········/// Returns an integer value which determines the rank of the given type in the update order list.
812 ········/// </summary>
813 ········/// <param name="t">The type to determine the update order.</param>
814 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
815 ········/// <remarks>
816 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
817 ········/// </remarks>
818 ········public int GetUpdateRank(Type t)
819 ········{
820 ············return mappings.GetUpdateOrder(t);
821 ········}
822
823 ········/// <summary>
824 ········/// Add a related object to the specified object.
825 ········/// </summary>
826 ········/// <param name="pc">the parent object</param>
827 ········/// <param name="r">the relation mapping info</param>
828 ········/// <param name="relObj">the related object that should be added</param>
829 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
830 ········{
831 ············//············string idstr;
832 ············//············if (relObj.NDOObjectId == null)
833 ············//················idstr = relObj.GetType().ToString();
834 ············//············else
835 ············//················idstr = relObj.NDOObjectId.Dump();
836 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
837 ············//Debug.Indent();
838
839 ············Class relClass = GetClass(relObj);
840 ············bool isDependent = relClass.Oid.IsDependent;
841
842 ············// Do some checks to guarantee that the assignment is correct
843 ············if(r.Composition)
844 ············{
845 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
846 ················{
847 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
848 ················}
849 ············}
850 ············else
851 ············{
852 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
853 ················{
854 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
855 ················}
856 ············}
857
858 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
859 ············{
860 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
861 ············}
862
863 ············InternalAddRelatedObject(pc, r, relObj, false);
864
865 ········}
866
867 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
868 ········{
869 ············// Need to patch pc into the relation relObj->pc, because
870 ············// the oid is built on base of this information
871 ············// The second relation has to be set before adding relObj
872 ············// to the relation list.
873 ············PatchForeignRelation(pc, r, relObj);
874 ············IPersistenceCapable parent;
875 ············foreach (Relation oidRelation in relClass.Oid.Relations)
876 ············{
877 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
878 ················if (parent == null)
879 ····················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.");
880 ················if (parent.NDOObjectState == NDOObjectState.Transient)
881 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
882
883 ············}
884 ········}
885
886
887 ········/// <summary>
888 ········/// Remove a related object from the specified object.
889 ········/// </summary>
890 ········/// <param name="pc">the parent object</param>
891 ········/// <param name="fieldName">Field name of the relation</param>
892 ········/// <param name="relObj">the related object that should be removed</param>
893 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
894 ········{
895 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
896 ············Relation r = mappings.FindRelation(pc, fieldName);
897 ············InternalRemoveRelatedObject(pc, r, relObj, true);
898 ········}
899
900 ········/// <summary>
901 ········/// Registers a listener which will be notified, if a new connection is opened.
902 ········/// </summary>
903 ········/// <param name="listener">Delegate of a listener function</param>
904 ········/// <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>
905 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
906 ········{
907 ············this.openConnectionListener = listener;
908 ········}
909
910 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
911 ········{
912 ············if (openConnectionListener != null)
913 ················return openConnectionListener(conn);
914 ············return conn.Name;
915 ········}
916
917
918 ········/*
919 ········doCommit should be:
920 ········
921 ····················Query····Save····Save(true)
922 ········Optimistic····1········1········0
923 ········Pessimistic····0········1········0
924 ············
925 ········Deferred Mode············
926 ····················Query····Save····Save(true)
927 ········Optimistic····0········1········0
928 ········Pessimistic····0········1········0
929 ········ */
930
931 ········internal void CheckEndTransaction(bool doCommit)
932 ········{
933 ············if (doCommit)
934 ············{
935 ················TransactionScope.Complete();
936 ············}
937 ········}
938
939 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
940 ········{
941 ············CheckTransaction(handler, this.GetClass(t).Connection);
942 ········}
943
944 ········/// <summary>
945 ········/// Each and every database operation has to be preceded by a call to this function.
946 ········/// </summary>
947 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
948 ········{
949 ············TransactionScope.CheckTransaction();
950 ············
951 ············if (handler.Connection == null)
952 ············{
953 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
954 ················{
955 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
956 ····················string connStr = this.OnNewConnection( ndoConn );
957 ····················var connection = p.NewConnection( connStr );
958 ····················if (connection == null)
959 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
960 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
961 ····················return connection;
962 ················} );
963 ············}
964
965 ············if (TransactionMode != TransactionMode.None)
966 ············{
967 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
968 ············}
969
970 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
971 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
972 ············{
973 ················handler.Connection.Open();
974 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
975 ············}
976 ········}
977
978 ········/// <summary>
979 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
980 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
981 ········/// This is possible if there is a listener for the CollisionEvent.
982 ········/// Else we throw an exception.
983 ········/// </summary>
984 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
985 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
986 ········{
987 ············DataRow row = ex.Row;
988 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
989 ················throw(ex);
990 ············if (row.RowState == DataRowState.Detached)
991 ················return;
992 ············foreach (Cache.Entry e in cache.LockedObjects)
993 ············{
994 ················if (e.row == row)
995 ················{
996 ····················CollisionEvent(e.pc);
997 ····················return;
998 ················}
999 ············}
1000 ············throw ex;
1001 ········}
1002
1003
1004 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1005 ········{
1006 ············Class cl = GetClass(pc);
1007 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1008 ············Dictionary<string,MemberInfo> persistentFields = null;
1009 ············if (etypes.Length > 0)
1010 ············{
1011 ················FieldMap fm = new FieldMap(cl);
1012 ················persistentFields = fm.PersistentFields;
1013 ············}
1014 ············foreach(string s in etypes)
1015 ············{
1016 ················try
1017 ················{
1018 ····················NDO.Mapping.Field f = cl.FindField(s);
1019 ····················if (f == null)
1020 ························continue;
1021 ····················object o = row[f.Column.Name];
1022 ····················string[] arr = s.Split('.');
1023 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1024 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1025 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1026 ····················// Hole das Embedded Object
1027 ····················object parentOb = parentFi.GetValue(pc);
1028
1029 ····················if (parentOb == null)
1030 ························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]));
1031
1032 ····················// Suche darin das Feld mit Namen Arr[1]
1033
1034 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1035 ····················Type childType = childFi.FieldType;
1036
1037 ····················// Don't initialize value types, if DBNull is stored in the field.
1038 ····················// Exception: DateTime and Guid.
1039 ····················if (o == DBNull.Value && childType.IsValueType
1040 ························&& childType != typeof(Guid)
1041 ························&& childType != typeof(DateTime))
1042 ························continue;
1043
1044 ····················if (childType == typeof(DateTime))
1045 ····················{
1046 ························if (o == DBNull.Value)
1047 ····························o = DateTime.MinValue;
1048 ····················}
1049 ····················if (childType.IsClass)
1050 ····················{
1051 ························if (o == DBNull.Value)
1052 ····························o = null;
1053 ····················}
1054
1055 ····················if (childType == typeof (Guid))
1056 ····················{
1057 ························if (o == DBNull.Value)
1058 ····························o = Guid.Empty;
1059 ························if (o is string)
1060 ························{
1061 ····························childFi.SetValue(parentOb, new Guid((string)o));
1062 ························}
1063 ························else if (o is Guid)
1064 ························{
1065 ····························childFi.SetValue(parentOb, o);
1066 ························}
1067 ························else if (o is byte[])
1068 ························{
1069 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1070 ························}
1071 ························else
1072 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1073 ····················}
1074 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1075 ····················{
1076 ························object childOb = childFi.GetValue(parentOb);
1077 ························FieldInfo valueFi = childType.GetField("value__");
1078 ························valueFi.SetValue(childOb, o);
1079 ························childFi.SetValue(parentOb, childOb);
1080 ····················}
1081 ····················else
1082 ····················{
1083 ························childFi.SetValue(parentOb, o);
1084 ····················}
1085 ················}
1086 ················catch (Exception ex)
1087 ················{
1088 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1089
1090 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1091 ················}
1092
1093 ············}
1094 ············
1095 ············try
1096 ············{
1097 ················if (cl.HasEncryptedFields)
1098 ················{
1099 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1100 ····················{
1101 ························string name = field.Column.Name;
1102 ························string s = (string) row[name];
1103 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1104 ························row[name] = es;
1105 ····················}
1106 ················}
1107 ················pc.NDORead(row, fieldNames, startIndex);
1108 ············}
1109 ············catch (Exception ex)
1110 ············{
1111 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1112 ····················+ ex.Message);
1113 ············}
1114 ········}
1115
1116 ········/// <summary>
1117 ········/// Executes a sql script to generate the database tables.
1118 ········/// The function will execute any sql statements in the script
1119 ········/// which are valid according to the
1120 ········/// rules of the underlying database. Result sets are ignored.
1121 ········/// </summary>
1122 ········/// <param name="scriptFile">The script file to execute.</param>
1123 ········/// <param name="conn">A connection object, containing the connection
1124 ········/// string to the database, which should be altered.</param>
1125 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1126 ········/// <remarks>
1127 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1128 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1129 ········/// Their message property will appear in the result array.
1130 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1131 ········/// </remarks>
1132 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1133 ········{
1134 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1135 ········}
1136
1137 ········/// <summary>
1138 ········/// Executes a sql script to generate the database tables.
1139 ········/// The function will execute any sql statements in the script
1140 ········/// which are valid according to the
1141 ········/// rules of the underlying database. Result sets are ignored.
1142 ········/// </summary>
1143 ········/// <param name="scriptFile">The script file to execute.</param>
1144 ········/// <param name="conn">A connection object, containing the connection
1145 ········/// string to the database, which should be altered.</param>
1146 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1147 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1148 ········/// <remarks>
1149 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1150 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1151 ········/// Their message property will appear in the result array.
1152 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1153 ········/// </remarks>
1154 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1155 ········{
1156 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1157 ············string s = sr.ReadToEnd();
1158 ············sr.Close();
1159 ············string[] arr = s.Split(';');
1160 ············string last = arr[arr.Length - 1];
1161 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1162 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1163 ············using (var handler = GetSqlPassThroughHandler())
1164 ············{
1165 ················int i = 0;
1166 ················string ok = "OK";
1167 ················foreach (string statement in arr)
1168 ················{
1169 ····················if (statement != null && statement.Trim() != string.Empty)
1170 ····················{
1171 ························try
1172 ························{
1173 ····························handler.Execute(statement);
1174 ····························result[i] = ok;
1175 ························}
1176 ························catch (Exception ex)
1177 ························{
1178 ····························result[i] = ex.Message;
1179 ························}
1180 ····················}
1181 ····················i++;
1182 ················}
1183 ················CheckEndTransaction(true);
1184 ············}
1185 ············return result;
1186 ········}
1187
1188 ········/// <summary>
1189 ········/// Executes a sql script to generate the database tables.
1190 ········/// The function will execute any sql statements in the script
1191 ········/// which are valid according to the
1192 ········/// rules of the underlying database. Result sets are ignored.
1193 ········/// </summary>
1194 ········/// <param name="scriptFile">The script file to execute.</param>
1195 ········/// <returns></returns>
1196 ········/// <remarks>
1197 ········/// This function takes the first Connection object in the Connections list
1198 ········/// of the Mapping file und executes the script using that connection.
1199 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1200 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1201 ········/// Their message property will appear in the result array.
1202 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1203 ········/// </remarks>
1204 ········public string[] BuildDatabase(string scriptFile)
1205 ········{
1206 ············if (!File.Exists(scriptFile))
1207 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1208 ············if (!this.mappings.Connections.Any())
1209 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1210 ············Connection conn = new Connection( this.mappings );
1211 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1212 ············conn.Name = OnNewConnection( originalConnection );
1213 ············conn.Type = originalConnection.Type;
1214 ············//Connection conn = (Connection) this.mappings.Connections[0];
1215 ············return BuildDatabase(scriptFile, conn);
1216 ········}
1217
1218 ········/// <summary>
1219 ········/// Executes a sql script to generate the database tables.
1220 ········/// The function will execute any sql statements in the script
1221 ········/// which are valid according to the
1222 ········/// rules of the underlying database. Result sets are ignored.
1223 ········/// </summary>
1224 ········/// <returns>
1225 ········/// A string array, containing the error messages produced by the statements
1226 ········/// contained in the script.
1227 ········/// </returns>
1228 ········/// <remarks>
1229 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1230 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1231 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1232 ········/// This function takes the first Connection object in the Connections list
1233 ········/// of the Mapping file und executes the script using that connection.
1234 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1235 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1236 ········/// Their message property will appear in the result array.
1237 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1238 ········/// </remarks>
1239 ········public string[] BuildDatabase()
1240 ········{
1241 ············Assembly ass = Assembly.GetEntryAssembly();
1242 ············if (ass == null)
1243 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1244 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1245 ············return BuildDatabase(file);
1246 ········}
1247
1248 ········/// <summary>
1249 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1250 ········/// </summary>
1251 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1252 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1253 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1254 ········{
1255 ············if (!this.mappings.Connections.Any())
1256 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1257 ············if (conn == null)
1258 ············{
1259 ················conn = new Connection( this.mappings );
1260 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1261 ················conn.Name = OnNewConnection( originalConnection );
1262 ················conn.Type = originalConnection.Type;
1263 ············}
1264
1265 ············return new SqlPassThroughHandler( this, conn );
1266 ········}
1267
1268 ········/// <summary>
1269 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1270 ········/// </summary>
1271 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1272 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1273 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1274 ········{
1275 ············if (!this.mappings.Connections.Any())
1276 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1277 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1278 ············if (conn == null)
1279 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1280 ············return GetSqlPassThroughHandler( conn );
1281 ········}
1282
1283 ········/// <summary>
1284 ········/// Executes a xml script to generate the database tables.
1285 ········/// The function will generate and execute sql statements to perform
1286 ········/// the changes described by the xml.
1287 ········/// </summary>
1288 ········/// <returns></returns>
1289 ········/// <remarks>
1290 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1291 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1292 ········/// This function takes the first Connection object in the Connections list
1293 ········/// of the Mapping file und executes the script using that connection.
1294 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1295 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1296 ········/// Their message property will appear in the result string array.
1297 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1298 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1299 ········/// </remarks>
1300 ········public string[] PerformSchemaTransitions()
1301 ········{
1302 ············Assembly ass = Assembly.GetEntryAssembly();
1303 ············if (ass == null)
1304 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1305 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1306 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1307 ············if (fileNames.Count == 0)
1308 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1309 ············if (fileNames.Count > 1)
1310 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1311 ············return PerformSchemaTransitions( fileNames[0] );
1312 ········}
1313
1314
1315 ········/// <summary>
1316 ········/// Executes a xml script to generate the database tables.
1317 ········/// The function will generate and execute sql statements to perform
1318 ········/// the changes described by the xml.
1319 ········/// </summary>
1320 ········/// <param name="scriptFile">The script file to execute.</param>
1321 ········/// <returns></returns>
1322 ········/// <remarks>
1323 ········/// This function takes the first Connection object in the Connections list
1324 ········/// of the Mapping file und executes the script using that connection.
1325 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1326 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1327 ········/// Their message property will appear in the result string array.
1328 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1329 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1330 ········/// </remarks>
1331 ········public string[] PerformSchemaTransitions(string scriptFile)
1332 ········{
1333 ············if (!File.Exists(scriptFile))
1334 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1335
1336 ············if (!this.mappings.Connections.Any())
1337 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1338 ············Connection conn = new Connection( mappings );
1339 ············Connection originalConnection = mappings.Connections.First();
1340 ············conn.Name = OnNewConnection( originalConnection );
1341 ············conn.Type = originalConnection.Type;
1342 ············return PerformSchemaTransitions(scriptFile, conn);
1343 ········}
1344
1345
1346 ········int CompareFileName( string fn1, string fn2)
1347 ········{
1348 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1349 ············Match match = regex.Match( fn1 );
1350 ············string v1 = match.Groups[1].Value;
1351 ············match = regex.Match( fn2 );
1352 ············string v2 = match.Groups[1].Value;
1353 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1354 ········}
1355
1356 ········string GetSchemaVersion(Connection ndoConn, string schemaName)
1357 ········{
1358 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1359 ············string version = "0.0";··// Initial value
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············using (var handler = GetSqlPassThroughHandler())
1362 ············{
1363 ················string[] TableNames = provider.GetTableNames( connection );
1364 ················if (TableNames.Any(t=>t=="NDOSchemaVersion"))
1365 ················{
1366 ····················string sql = "SELECT Version from NDOSchemaVersion WHERE SchemaName ";
1367 ····················if (schemaName == null)
1368 ························sql += "IS NULL;";
1369 ····················else
1370 ························sql += "LIKE '" + schemaName + "'";
1371 ····················using(IDataReader dr = handler.Execute(sql, true))
1372 ····················{
1373 ························if (dr.Read())
1374 ····························version = dr.GetString( 0 );
1375 ····················}
1376 ················}
1377 ················else
1378 ················{
1379 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( NDOProviderFactory.Instance.Generators[ndoConn.Type], this.mappings );
1380 ····················string transition = @"<NdoSchemaTransition>
1381 ····<CreateTable name=""NDOSchemaVersion"">
1382 ······<CreateColumn name=""SchemaName"" type=""System.String,mscorlib"" allowNull=""True"" />
1383 ······<CreateColumn name=""Version"" type=""System.String,mscorlib"" size=""50"" />
1384 ····</CreateTable>
1385 </NdoSchemaTransition>";
1386 ····················XElement transitionElement = XElement.Parse(transition);
1387
1388 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1389 ····················handler.Execute(sql);
1390 ····················sql = String.Format( "INSERT INTO NDOSchemaVersion([SchemaName],[Version]) VALUES({0},'0')", schemaName == null ? "NULL" : provider.GetSqlLiteral( schemaName ) );
1391 ····················handler.Execute( sql );
1392 ····················handler.CommitTransaction();
1393 ················}
1394 ············}
1395
1396 ············return version;
1397 ········}
1398
1399 ········/// <summary>
1400 ········/// Executes a xml script to generate the database tables.
1401 ········/// The function will generate and execute sql statements to perform
1402 ········/// the changes described by the xml.
1403 ········/// </summary>
1404 ········/// <param name="scriptFile">The xml script file.</param>
1405 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1406 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1407 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1408 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1409 ········{
1410 ············string schemaName = null;
1411 ············// Gespeicherte Version ermitteln.
1412 ············XElement transitionElements = XElement.Load( scriptFile );
1413 ············if (transitionElements.Attribute( "schemaName" ) != null)
1414 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1415 ············Version version = new Version( GetSchemaVersion( ndoConn, schemaName ) );
1416 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( NDOProviderFactory.Instance.Generators[ndoConn.Type], this.mappings );
1417 ············MemoryStream ms = new MemoryStream();
1418 ············StreamWriter sw = new StreamWriter(ms, System.Text.Encoding.UTF8);
1419 ············bool hasChanges = false;
1420
1421 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition").Where(e=>new Version(e.Attribute("schemaVersion").Value).CompareTo(version) > 0))
1422 ············{
1423 ················hasChanges = true;
1424 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1425 ············}
1426
1427 ············if (!hasChanges)
1428 ················return new string[] { };
1429
1430 ············sw.Write( "UPDATE NDOSchemaVersion SET Version = '" );
1431 ············sw.Write( transitionElements.Attribute( "schemaVersion" ).Value );
1432 ············sw.Write( "' WHERE SchemaName " );
1433 ············if (schemaName == null)
1434 ················sw.WriteLine( "IS NULL;" );
1435 ············else
1436 ················sw.WriteLine( "LIKE '" + schemaName + "'" );············
1437
1438 ············sw.Flush();
1439 ············ms.Position = 0L;
1440
1441 ············StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);
1442 ············string s = sr.ReadToEnd();
1443 ············sr.Close();
1444
1445 ············return InternalPerformSchemaTransitions( ndoConn, s );
1446 ········}
1447
1448 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1449 ········{
1450 ············string[] arr = sql.Split( ';' );
1451 ············string last = arr[arr.Length - 1];
1452 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1453 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1454 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1455 ············//TransactionInfo ti = transactionTable[conn];
1456 ············//IDbConnection cn = ti.Connection;
1457 ············IDbConnection cn = provider.NewConnection( ndoConn.Name );
1458 ············cn.Open();
1459 ············IDbCommand cmd = provider.NewSqlCommand( cn );
1460 ············int i = 0;
1461 ············string ok = "OK";
1462 ············foreach (string statement in arr)
1463 ············{
1464 ················if (statement != null && statement.Trim() != string.Empty)
1465 ················{
1466 ····················try
1467 ····················{
1468 ························cmd.CommandText = statement;
1469 ························cmd.ExecuteNonQuery();
1470 ························result[i] = ok;
1471 ····················}
1472 ····················catch (Exception ex)
1473 ····················{
1474 ························result[i] = ex.Message;
1475 ····················}
1476 ················}
1477 ················i++;
1478 ············}
1479 ············cn.Close();
1480 ············return result;
1481 ········}
1482 ········
1483 ········/// <summary>
1484 ········/// Transfers Data from the object to the DataRow
1485 ········/// </summary>
1486 ········/// <param name="pc"></param>
1487 ········/// <param name="row"></param>
1488 ········/// <param name="fieldNames"></param>
1489 ········/// <param name="startIndex"></param>
1490 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1491 ········{
1492 ············Class cl = GetClass( pc );
1493 ············try
1494 ············{
1495 ················pc.NDOWrite(row, fieldNames, startIndex);
1496 ················if (cl.HasEncryptedFields)
1497 ················{
1498 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1499 ····················{
1500 ························string name = field.Column.Name;
1501 ························string s = (string) row[name];
1502 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1503 ························row[name] = es;
1504 ····················}
1505 ················}
1506 ············}
1507 ············catch (Exception ex)
1508 ············{
1509 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1510 ····················+ ex.Message);
1511 ············}
1512
1513 ············if (cl.TypeNameColumn != null)
1514 ············{
1515 ················Type t = pc.GetType();
1516 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1517 ············}
1518
1519 ············var etypes = cl.EmbeddedTypes;
1520 ············foreach(string s in etypes)
1521 ············{
1522 ················try
1523 ················{
1524 ····················NDO.Mapping.Field f = cl.FindField(s);
1525 ····················if (f == null)
1526 ························continue;
1527 ····················string[] arr = s.Split('.');
1528 ····················// Suche Feld mit Namen arr[0] als object
1529 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1530 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1531 ····················Object parentOb = parentFi.GetValue(pc);
1532 ····················if (parentOb == null)
1533 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1534 ····················// Suche darin das Feld mit Namen Arr[1]
1535 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1536 ····················object o = childFi.GetValue(parentOb);
1537 ····················if (o == null
1538 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1539 ························|| o is Guid && (Guid) o == Guid.Empty)
1540 ························o = DBNull.Value;
1541 ····················row[f.Column.Name] = o;
1542 ················}
1543 ················catch (Exception ex)
1544 ················{
1545 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1546
1547 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1548 ················}
1549 ············}
1550 ········}
1551
1552 ········/// <summary>
1553 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1554 ········/// </summary>
1555 ········/// <param name="o">The parent object.</param>
1556 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1557 ········public virtual void LoadField(object o, int fieldOrdinal)
1558 ········{
1559 ············IPersistenceCapable pc = CheckPc(o);
1560 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1561 ············{
1562 ················LoadState ls = pc.NDOLoadState;
1563 ················if (ls.FieldLoadState != null)
1564 ················{
1565 ····················if (ls.FieldLoadState[fieldOrdinal])
1566 ························return;
1567 ················}
1568 ················else
1569 ················{
1570 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1571 ················}
1572 ················LoadData(o);
1573 ········}
1574 ········}
1575
1576 #pragma warning disable 419
1577 ········/// <summary>
1578 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1579 ········/// </summary>
1580 ········/// <param name="o">The hollow object.</param>
1581 ········/// <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>
1582 #pragma warning restore 419
1583 ········public virtual void LoadData( object o )
1584 ········{
1585 ············IPersistenceCapable pc = CheckPc(o);
1586 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1587 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1588 ················return;
1589 ············Class cl = GetClass(pc);
1590 ············IQuery q;
1591 ············q = CreateOidQuery(pc, cl);
1592 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1593
1594 ············var objects = q.Execute();
1595 ············var count = objects.Count;
1596
1597 ············if (count > 1)
1598 ············{
1599 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1600 ············}
1601 ············else if (count == 0)
1602 ············{
1603 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1604 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1605 ············}
1606 ········}
1607
1608 ········/// <summary>
1609 ········/// Creates a new IQuery object for the given type
1610 ········/// </summary>
1611 ········/// <param name="t"></param>
1612 ········/// <param name="oql"></param>
1613 ········/// <param name="hollow"></param>
1614 ········/// <param name="queryLanguage"></param>
1615 ········/// <returns></returns>
1616 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1617 ········{
1618 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1619 ············Type qt = template.MakeGenericType( t );
1620 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1621 ········}
1622
1623 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1624 ········{
1625 ············ArrayList parameters = new ArrayList();
1626 ············string oql = "oid = {0}";
1627 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1628 ············q.Parameters.Add( pc.NDOObjectId );
1629 ············q.AllowSubclasses = false;
1630 ············return q;
1631 ········}
1632
1633 ········/// <summary>
1634 ········/// Mark the object dirty. The current state is
1635 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1636 ········/// object is locked in the cache.
1637 ········/// </summary>
1638 ········/// <param name="pc"></param>
1639 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1640 ········{
1641 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1642 ················return;
1643 ············SaveObjectState(pc);
1644 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1645 ········}
1646
1647 ········/// <summary>
1648 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1649 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1650 ········/// This is done, to allow easy rollback later. Also, the
1651 ········/// object is locked in the cache.
1652 ········/// </summary>
1653 ········/// <param name="pc"></param>
1654 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1655 ········{
1656 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1657
1658 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1659 ············{
1660 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1661 ············}
1662
1663 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1664 ················LoadData(pc);
1665
1666 ············// state is either (Created), Persistent, PersistentDirty
1667 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1668 ············{
1669 ················MarkDirty(pc);
1670 ············}
1671 ········}
1672
1673
1674
1675 ········/// <summary>
1676 ········/// Save current object state in DS and lock the object in the cache.
1677 ········/// The saved state can be used later to retrieve the original object value if the
1678 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1679 ········/// </summary>
1680 ········/// <param name="pc">The object that should be saved</param>
1681 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1682 ········/// <remarks>
1683 ········/// In a data row there are the following things:
1684 ········/// Item································Responsible for writing
1685 ········/// State (own, inherited, embedded)····WriteObject
1686 ········/// TimeStamp····························NDOPersistenceHandler
1687 ········/// Oid····································WriteId
1688 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1689 ········/// </remarks>
1690 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1691 ········{
1692 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1693 ············
1694 ············DataTable table = GetTable(pc);
1695 ············DataRow row = table.NewRow();
1696 ············Class cl = GetClass(pc);
1697 ············WriteObject(pc, row, cl.ColumnNames, 0);
1698 ············WriteIdToRow(pc, row);
1699 ············if (!isDeleting)
1700 ················WriteLostForeignKeysToRow(cl, pc, row);
1701 ············table.Rows.Add(row);
1702 ············row.AcceptChanges();
1703 ············
1704 ············var relations = CollectRelationStates(pc);
1705 ············cache.Lock(pc, row, relations);
1706 ········}
1707
1708 ········private void SaveFakeRow(IPersistenceCapable pc)
1709 ········{
1710 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1711 ············
1712 ············DataTable table = GetTable(pc);
1713 ············DataRow row = table.NewRow();
1714 ············Class pcClass = GetClass(pc);
1715 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1716 ············Class cl = GetClass(pc);
1717 ············//WriteObject(pc, row, cl.FieldNames, 0);
1718 ············WriteIdToRow(pc, row);
1719 ············table.Rows.Add(row);
1720 ············row.AcceptChanges();
1721 ············
1722 ············cache.Lock(pc, row, null);
1723 ········}
1724
1725 ········/// <summary>
1726 ········/// This defines one column of the row, in which we use the
1727 ········/// ColumnError property to determine, if the row is a fake row.
1728 ········/// </summary>
1729 ········/// <param name="pcClass"></param>
1730 ········/// <returns></returns>
1731 ········private string GetFakeRowOidColumnName(Class pcClass)
1732 ········{
1733 ············// In case of several OidColumns the first column defined in the mapping
1734 ············// will be the one, holding the fake row info.
1735 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1736 ········}
1737
1738 ········private bool IsFakeRow(Class cl, DataRow row)
1739 ········{
1740 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1741 ········}
1742
1743 ········/// <summary>
1744 ········/// Make a list of objects persistent.
1745 ········/// </summary>
1746 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1747 ········public void MakePersistent(System.Collections.IList list)
1748 ········{
1749 ············foreach (IPersistenceCapable pc in list)
1750 ············{
1751 ················MakePersistent(pc);
1752 ············}
1753 ········}
1754
1755 ········/// <summary>
1756 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1757 ········/// not duplicated.
1758 ········/// </summary>
1759 ········/// <param name="pc">the parent object of all relations</param>
1760 ········/// <returns></returns>
1761 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1762 ········{
1763 ············// Save state of relations
1764 ············Class c = GetClass(pc);
1765 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1766 ············foreach(Relation r in c.Relations)
1767 ············{
1768 ················if (r.Multiplicity == RelationMultiplicity.Element)
1769 ················{
1770 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1771 ················}
1772 ················else
1773 ················{
1774 ····················IList l = mappings.GetRelationContainer(pc, r);
1775 ····················if(l != null)
1776 ····················{
1777 ························l = (IList) ListCloner.CloneList(l);
1778 ····················}
1779 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1780 ················}
1781 ············}
1782
1783 ············return relations;
1784 ········}
1785
1786
1787 ········/// <summary>
1788 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1789 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1790 ········/// the list had at the beginning of the transaction.
1791 ········/// </summary>
1792 ········/// <param name="pc"></param>
1793 ········/// <param name="relations"></param>
1794 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1795 ········{
1796 ············Class c = GetClass(pc);
1797
1798 ············foreach(var entry in relations)
1799 ············{
1800 ················var r = entry.Key;
1801 ················if (r.Multiplicity == RelationMultiplicity.Element)
1802 ················{
1803 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1804 ················}
1805 ················else
1806 ················{
1807 ····················if (pc.NDOGetLoadState(r.Ordinal))
1808 ····················{
1809 ························// Help GC by clearing lists
1810 ························IList l = mappings.GetRelationContainer(pc, r);
1811 ························if(l != null)
1812 ························{
1813 ····························l.Clear();
1814 ························}
1815 ························// Restore relation
1816 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1817 ····················}
1818 ················}
1819 ············}
1820 ········}
1821
1822
1823 ········/// <summary>
1824 ········/// Generates a query for related objects without mapping table.
1825 ········/// Note: this function can't be called in polymorphic scenarios,
1826 ········/// since they need a mapping table.
1827 ········/// </summary>
1828 ········/// <returns></returns>
1829 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1830 ········{
1831 ············// At this point of execution we know,
1832 ············// that the target type is not polymorphic and is not 1:1.
1833
1834 ············// We can't fetch these objects with an NDOql query
1835 ············// since this would require a relation in the opposite direction
1836
1837 ············IList relatedObjects;
1838 ············if (l != null)
1839 ················relatedObjects = l;
1840 ············else
1841 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1842
1843 ············Type t = r.ReferencedType;
1844 ············Class cl = GetClass( t );
1845 ············var provider = cl.Provider;
1846
1847 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1848 ············var relClass = GetClass( r.ReferencedType );
1849 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1850 ············sb.Append( " WHERE " );
1851 ············int i = 0;
1852 ············List<object> parameters = new List<object>();
1853 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1854 ·············· {
1855 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1856 ·················· sb.Append( " = {" );
1857 ·················· sb.Append(i);
1858 ·················· sb.Append( '}' );
1859 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1860 ·················· if (!isLastElement)
1861 ······················ sb.Append( " AND " );
1862 ·················· i++;
1863 ·············· } );
1864
1865 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1866 ············{
1867 ················sb.Append( " AND " );
1868 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1869 ················sb.Append( " = " );
1870 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1871 ············}
1872
1873 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1874
1875 ············foreach (var p in parameters)
1876 ············{
1877 ················q.Parameters.Add( p );
1878 ············}
1879
1880 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1881
1882 ············IList l2 = q.Execute();
1883
1884 ············foreach (object o in l2)
1885 ················relatedObjects.Add( o );
1886
1887 ············return relatedObjects;
1888 ········}
1889
1890
1891 ········/// <summary>
1892 ········/// Resolves an relation. The loaded objects will be hollow.
1893 ········/// </summary>
1894 ········/// <param name="o">The parent object.</param>
1895 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1896 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1897 ········/// <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>
1898 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1899 ········{
1900 ············IPersistenceCapable pc = CheckPc(o);
1901 ············LoadRelationInternal(pc, fieldName, hollow);
1902 ········}
1903
1904 ········
1905
1906 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1907 ········{
1908 ············IList result = null;
1909
1910 ············if (pc.NDOObjectState == NDOObjectState.Created)
1911 ················return null;
1912
1913 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1914 ················LoadData(pc);
1915
1916 ············if(r.MappingTable == null)
1917 ············{
1918 ················// 1:1 are loaded with LoadData
1919 ················if (r.Multiplicity == RelationMultiplicity.List)
1920 ················{
1921 ····················// Help GC by clearing lists
1922 ····················IList l = mappings.GetRelationContainer(pc, r);
1923 ····················if(l != null)
1924 ························l.Clear();
1925 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1926 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1927 ····················result = relatedObjects;
1928 ················}
1929 ············}
1930 ············else
1931 ············{
1932 ················DataTable dt = null;
1933
1934 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1935 ················{
1936 ····················handler.VerboseMode = VerboseMode;
1937 ····················handler.LogAdapter = LogAdapter;
1938 ····················CheckTransaction( handler, r.MappingTable.Connection );
1939 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1940 ················}
1941
1942 ················IList relatedObjects;
1943 ················if(r.Multiplicity == RelationMultiplicity.Element)
1944 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1945 ················else
1946 ················{
1947 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1948 ····················if(relatedObjects != null)
1949 ························relatedObjects.Clear();··// Objects will be reread
1950 ····················else
1951 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1952 ················}
1953 ····················
1954 ················foreach(DataRow objRow in dt.Rows)
1955 ················{
1956 ····················Type relType;
1957
1958 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1959 ····················{
1960 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1961 ························if (typeCodeObj is System.DBNull)
1962 ····························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() ) );
1963 ························relType = typeManager[(int)typeCodeObj];
1964 ························if (relType == null)
1965 ····························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));
1966 ····················}························
1967 ····················else
1968 ····················{
1969 ························relType = r.ReferencedType;
1970 ····················}
1971
1972 ····················//TODO: Generic Types: Exctract the type description from the type name column
1973 ····················if (relType.IsGenericTypeDefinition)
1974 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1975 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1976 ····················IPersistenceCapable relObj = FindObject(id);
1977 ····················relatedObjects.Add(relObj);
1978 ················}····
1979 ················if (r.Multiplicity == RelationMultiplicity.Element)
1980 ················{
1981 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
1982 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
1983 ················}
1984 ················else
1985 ················{
1986 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1987 ····················result = relatedObjects;
1988 ················}
1989 ············}
1990 ············// Mark relation as loaded
1991 ············pc.NDOSetLoadState(r.Ordinal, true);
1992 ············return result;
1993 ········}
1994
1995 ········/// <summary>
1996 ········/// Loads elements of a relation
1997 ········/// </summary>
1998 ········/// <param name="pc">The object which needs to load the relation</param>
1999 ········/// <param name="relationName">The name of the relation</param>
2000 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2001 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2002 ········{
2003 ············if (pc.NDOObjectState == NDOObjectState.Created)
2004 ················return null;
2005 ············Class cl = GetClass(pc);
2006
2007 ············Relation r = cl.FindRelation(relationName);
2008
2009 ············if ( r == null )
2010 ················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 ) );
2011
2012 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2013 ················return null;
2014
2015 ············return LoadRelation(pc, r, hollow);
2016 ········}
2017
2018 ········/// <summary>
2019 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2020 ········/// a list of objects that has been read from the DB.
2021 ········/// </summary>
2022 ········/// <param name="pc">The parent object of the relations</param>
2023 ········/// <param name="row">A data row containing the state of the object</param>
2024 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2025 ········{
2026 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2027 ············Class cl = GetClass(pc);
2028 ············foreach(Relation r in cl.Relations)
2029 ············{
2030 ················if(r.MappingTable == null)
2031 ················{
2032 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2033 ····················{
2034 ························bool isNull = false;
2035 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2036 ························{
2037 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2038 ························}
2039 ························if (isNull)
2040 ························{
2041 ····························mappings.SetRelationField(pc, r.FieldName, null);
2042 ························}
2043 ························else
2044 ························{
2045 ····························Type relType;
2046 ····························if (r.HasSubclasses)
2047 ····························{
2048 ································object o = row[r.ForeignKeyTypeColumnName];
2049 ································if (o == DBNull.Value)
2050 ····································throw new NDOException(75, String.Format(
2051 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2052 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2053 ································relType = typeManager[(int)o];
2054 ····························}
2055 ····························else
2056 ····························{
2057 ································relType = r.ReferencedType;
2058 ····························}
2059 ····························if (relType == null)
2060 ····························{
2061 ································throw new NDOException(75, String.Format(
2062 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2063 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2064 ····························}
2065 ····
2066 ····························int count = r.ForeignKeyColumns.Count();
2067 ····························object[] keydata = new object[count];
2068 ····························int i = 0;
2069 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2070 ····························{
2071 ································keydata[i++] = row[fkColumn.Name];
2072 ····························}
2073
2074 ····························Type oidType = relType;
2075 ····························if (oidType.IsGenericTypeDefinition)
2076 ································oidType = mappings.GetRelationFieldType(r);
2077
2078 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2079 ····························if(childOid.IsValid())
2080 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2081 ····························else
2082 ································mappings.SetRelationField(pc, r.FieldName, null);
2083
2084 ························}
2085 ························pc.NDOSetLoadState(r.Ordinal, true);
2086 ····················}
2087 ················}
2088 ············}
2089 ········}
2090
2091 ········
2092 ········/// <summary>
2093 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2094 ········/// </summary>
2095 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2096 ········/// <param name="t">The type of the object, the id will belong to.</param>
2097 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2098 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2099 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2100 ········{
2101 ············return new ObjectId(oid.Id, t);
2102 ········}
2103
2104 ········/*
2105 ········/// <summary>
2106 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2107 ········/// </summary>
2108 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2109 ········/// <param name="t">The type of the object.</param>
2110 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2111 ········/// <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>
2112 ········public ObjectId NewObjectId(object keyData, Type t)
2113 ········{
2114 ············if (keyData == null || keyData == DBNull.Value)
2115 ················return ObjectId.InvalidId;
2116
2117 ············Class cl =··GetClass(t);············
2118 ············
2119 ············if (cl.Oid.FieldType == typeof(int))
2120 ················return new ObjectId(new Int32Key(t, (int)keyData));
2121 ············else if (cl.Oid.FieldType == typeof(string))
2122 ················return new ObjectId(new StringKey(t, (String) keyData));
2123 ············else if (cl.Oid.FieldType == typeof(Guid))
2124 ················if (keyData is string)
2125 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2126 ················else
2127 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2128 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2129 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2130 ············else
2131 ················return ObjectId.InvalidId;
2132 ········}
2133 ········*/
2134
2135
2136 ········/*
2137 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2138 ····················{
2139 ························// The column, which hold the oid gets overwritten, if
2140 ························// Oid.FieldName contains a value.
2141 */
2142 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2143 ········{
2144 ············Class cl = GetClass(pc);
2145
2146 ············if (cl.Oid.IsDependent)
2147 ················return;
2148
2149 ············Key key = pc.NDOObjectId.Id;
2150
2151 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2152 ············{
2153 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2154 ················if (oidColumn.FieldName != null)
2155 ················{
2156 ····················row[oidColumn.Name] = key[i];
2157 ················}
2158 ············}
2159 ········}
2160
2161 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2162 ········{
2163 ············NDO.Mapping.Class cl = GetClass(pc);
2164 ············ObjectId oid = pc.NDOObjectId;
2165
2166 ············if (cl.TimeStampColumn != null)
2167 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2168
2169 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2170 ················return;
2171
2172 ············Key key = oid.Id;
2173
2174 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2175 ············{
2176 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2177 ················row[oidColumn.Name] = key[i];
2178 ············}
2179 ········}
2180
2181 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2182 ········{
2183 ············ObjectId oid = pc.NDOObjectId;
2184 ············NDO.Mapping.Class cl = GetClass(pc);
2185
2186 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2187 ················return;
2188
2189 ············Key key = oid.Id;
2190
2191 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2192 ············{
2193 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2194 ················object o = row[oidColumn.Name];
2195 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2196 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2197 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2198 ····················key[i] = new Guid((string)o);
2199 ················else
2200 ····················key[i] = o;
2201 ············}
2202
2203 ········}
2204
2205 ········private void ReadId (Cache.Entry e)
2206 ········{
2207 ············ReadIdFromRow(e.pc, e.row);
2208 ········}
2209
2210 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2211 ········{
2212 ············WriteObject(pc, row, fieldNames, 0);
2213 ········}
2214
2215 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2216 ········{
2217 ············if (cl.TimeStampColumn == null)
2218 ················return;
2219 ············object col = row[cl.TimeStampColumn];
2220 ············if (col is String)
2221 ················pc.NDOTimeStamp = new Guid(col.ToString());
2222 ············else
2223 ················pc.NDOTimeStamp = (Guid) col;
2224 ········}
2225
2226
2227
2228 ········private void ReadTimeStamp(Cache.Entry e)
2229 ········{
2230 ············IPersistenceCapable pc = e.pc;
2231 ············NDO.Mapping.Class cl = GetClass(pc);
2232 ············Debug.Assert(!IsFakeRow(cl, e.row));
2233 ············if (cl.TimeStampColumn == null)
2234 ················return;
2235 ············if (e.row[cl.TimeStampColumn] is String)
2236 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2237 ············else
2238 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2239 ········}
2240
2241 ········/// <summary>
2242 ········/// Determines, if any objects are new, changed or deleted.
2243 ········/// </summary>
2244 ········public virtual bool HasChanges
2245 ········{
2246 ············get
2247 ············{
2248 ················return cache.LockedObjects.Count > 0;
2249 ············}
2250 ········}
2251
2252 ········/// <summary>
2253 ········/// Do the update for all rows in the ds.
2254 ········/// </summary>
2255 ········/// <param name="types">Types with changes.</param>
2256 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2257 ········/// <remarks>
2258 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2259 ········/// </remarks>
2260 ········private void UpdateTypes(IList types, bool delete)
2261 ········{
2262 ············foreach(Type t in types)
2263 ············{
2264 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2265 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2266 ················{
2267 ····················handler.VerboseMode = VerboseMode;
2268 ····················handler.LogAdapter = LogAdapter;
2269 ····················CheckTransaction( handler, t );
2270 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2271 ····················handler.ConcurrencyError += ceh;
2272 ····················try
2273 ····················{
2274 ························DataTable dt = GetTable(t);
2275 ························if (delete)
2276 ····························handler.UpdateDeletedObjects( dt );
2277 ························else
2278 ····························handler.Update( dt );
2279 ····················}
2280 ····················finally
2281 ····················{
2282 ························handler.ConcurrencyError -= ceh;
2283 ····················}
2284 ················}
2285 ············}
2286 ········}
2287
2288 ········internal void UpdateCreatedMappingTableEntries()
2289 ········{
2290 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2291 ············{
2292 ················if (!e.DeleteEntry)
2293 ····················WriteMappingTableEntry(e);
2294 ············}
2295 ············// Now update all mapping tables
2296 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2297 ············{
2298 ················handler.LogAdapter = this.LogAdapter;
2299 ················handler.VerboseMode = this.VerboseMode;
2300 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2301 ················handler.Update(ds);
2302 ············}
2303 ········}
2304
2305 ········internal void UpdateDeletedMappingTableEntries()
2306 ········{
2307 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2308 ············{
2309 ················if (e.DeleteEntry)
2310 ····················WriteMappingTableEntry(e);
2311 ············}
2312 ············// Now update all mapping tables
2313 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2314 ············{
2315 ················handler.LogAdapter = this.LogAdapter;
2316 ················handler.VerboseMode = this.VerboseMode;
2317 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2318 ················handler.Update(ds);
2319 ············}
2320 ········}
2321
2322 ········/// <summary>
2323 ········/// Save all changed object into the DataSet and update the DB.
2324 ········/// When a newly created object is written to DB, the key might change. Therefore,
2325 ········/// the id is updated and the object is removed and re-inserted into the cache.
2326 ········/// </summary>
2327 ········public virtual void Save(bool deferCommit = false)
2328 ········{
2329 ············this.DeferredMode = deferCommit;
2330 ············var htOnSaving = new HashSet<ObjectId>();
2331 ············for(;;)
2332 ············{
2333 ················// We need to work on a copy of the locked objects list,
2334 ················// since the handlers might add more objects to the cache
2335 ················var lockedObjects = cache.LockedObjects.ToList();
2336 ················int count = lockedObjects.Count;
2337 ················foreach(Cache.Entry e in lockedObjects)
2338 ················{
2339 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2340 ····················{
2341 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2342 ························if (ipn != null)
2343 ························{
2344 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2345 ····························{
2346 ································ipn.OnSaving();
2347 ································htOnSaving.Add(e.pc.NDOObjectId);
2348 ····························}
2349 ························}
2350 ····················}
2351 ················}
2352 ················// The system is stable, if the count doesn't change
2353 ················if (cache.LockedObjects.Count == count)
2354 ····················break;
2355 ············}
2356
2357 ············if (this.OnSavingEvent != null)
2358 ············{
2359 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2360 ················foreach(Cache.Entry e in cache.LockedObjects)
2361 ····················onSavingObjects.Add(e.pc);
2362 ················OnSavingEvent(onSavingObjects);
2363 ············}
2364
2365 ············List<Type> types = new List<Type>();
2366 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2367 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2368 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2369 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2370 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2371
2372 ············// Save current state in DataSet
2373 ············foreach (Cache.Entry e in cache.LockedObjects)
2374 ············{
2375 ················Type objType = e.pc.GetType();
2376
2377 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2378 ····················objType = objType.GetGenericTypeDefinition();
2379
2380 ················Class cl = GetClass(e.pc);
2381 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2382 ················if(!types.Contains(objType))
2383 ················{
2384 ····················//Debug.WriteLine("Added··type " + objType.Name);
2385 ····················types.Add(objType);
2386 ················}
2387 ················NDOObjectState objectState = e.pc.NDOObjectState;
2388 ················if(objectState == NDOObjectState.Deleted)
2389 ················{
2390 ····················deletedObjects.Add(e.pc);
2391 ················}
2392 ················else if(objectState == NDOObjectState.Created)
2393 ················{
2394 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2395 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2396 ····················WriteForeignKeysToRow(e.pc, e.row);
2397 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2398 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2399 ····················//····················i = 0;
2400 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2401 ····················//····················{
2402 ····················//························rows[i++] = e2.row;
2403 ····················//····················}
2404 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2405 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2406
2407 ····················addedCacheEntries.Add(e);
2408 ····················addedObjects.Add( e.pc );
2409 ····················//····················objectState = NDOObjectState.Persistent;
2410 ················}
2411 ················else
2412 ················{
2413 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2414 ························changedObjects.Add( e.pc );
2415 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2416 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2417 ················}
2418 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2419 ················{
2420 ····················hollowModeObjects.Add(e.pc);
2421 ················}
2422 ············}
2423
2424 ············// Before we delete any db rows, we have to make sure, to delete mapping
2425 ············// table entries first, which might have relations to the db rows to be deleted
2426 ············UpdateDeletedMappingTableEntries();
2427
2428 ············// Update DB
2429 ············if (ds.HasChanges())
2430 ············{
2431
2432 ················// We need the reversed update order for deletions.
2433 ················types.Sort( ( t1, t2 ) =>
2434 ················{
2435 ····················int i1 = mappings.GetUpdateOrder( t1 );
2436 ····················int i2 = mappings.GetUpdateOrder( t2 );
2437 ····················if (i1 < i2)
2438 ····················{
2439 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2440 ····························i1 += 100000;
2441 ····················}
2442 ····················else
2443 ····················{
2444 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2445 ····························i2 += 100000;
2446 ····················}
2447 ····················return i2 - i1;
2448 ················} );
2449
2450 ················// Delete records first
2451
2452 ················UpdateTypes(types, true);
2453
2454 ················// Now do all other updates in correct order.
2455 ················types.Reverse();
2456
2457 ················UpdateTypes(types, false);
2458 ················
2459 ················ds.AcceptChanges();
2460 ················if(createdDirectObjects.Count > 0)
2461 ················{
2462 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2463 ····················// They must be written again to store the correct foreign keys.
2464 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2465 ····················{
2466 ························Class cl = GetClass(pc);
2467 ························DataRow r = this.cache.GetDataRow(pc);
2468 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2469 ························object o = r[fakeColumnName];
2470 ························r[fakeColumnName] = o;
2471 ····················}
2472
2473 ····················UpdateTypes(types, false);
2474 ················}
2475
2476 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2477 ················foreach(Cache.Entry e in addedCacheEntries)
2478 ················{
2479 ····················cache.DeregisterLockedObject(e.pc);
2480 ····················ReadId(e);
2481 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2482 ················}
2483
2484 ················// Now update all mapping tables. Because of possible subclasses, there is no
2485 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2486 ················// tables only after all other objects have been written to ensure correct foreign keys.
2487 ················UpdateCreatedMappingTableEntries();
2488
2489 ················// The rows may contain now new Ids, which should be
2490 ················// stored in the lostRowInfo's before the rows get detached
2491 ················foreach(Cache.Entry e in cache.LockedObjects)
2492 ················{
2493 ····················if (e.row.RowState != DataRowState.Detached)
2494 ····················{
2495 ························IPersistenceCapable pc = e.pc;
2496 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2497 ····················}
2498 ················}
2499
2500 ················ds.AcceptChanges();
2501 ············}
2502
2503 ············EndSave(!deferCommit);
2504
2505 ············foreach(IPersistenceCapable pc in deletedObjects)
2506 ············{
2507 ················MakeObjectTransient(pc, false);
2508 ············}
2509
2510 ············ds.Clear();
2511 ············mappingHandler.Clear();
2512 ············createdDirectObjects.Clear();
2513 ············createdMappingTableObjects.Clear();
2514 ············this.relationChanges.Clear();
2515
2516 ············if(hollowMode)
2517 ············{
2518 ················MakeHollow(hollowModeObjects);
2519 ············}
2520
2521 ············if (this.OnSavedEvent != null)
2522 ············{
2523 ················AuditSet auditSet = new AuditSet()
2524 ················{
2525 ····················ChangedObjects = changedObjects,
2526 ····················CreatedObjects = addedObjects,
2527 ····················DeletedObjects = deletedObjects
2528 ················};
2529 ················this.OnSavedEvent( auditSet );
2530 ············}
2531 ········}
2532
2533 ········private void EndSave(bool forceCommit)
2534 ········{
2535 ············foreach(Cache.Entry e in cache.LockedObjects)
2536 ············{
2537 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2538 ····················this.ReadTimeStamp(e);
2539 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2540 ············}
2541
2542 ············cache.UnlockAll();
2543
2544 ············CheckEndTransaction(forceCommit);
2545 ········}
2546
2547 ········/// <summary>
2548 ········/// Write all foreign keys for 1:1-relations.
2549 ········/// </summary>
2550 ········/// <param name="pc">The persistent object.</param>
2551 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2552 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2553 ········{
2554 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2555 ············{
2556 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2557 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2558
2559 ················if ( relObj != null )
2560 ················{
2561 ····················int i = 0;
2562 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2563 ····················{
2564 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2565 ····················}
2566 ····················if ( r.ForeignKeyTypeColumnName != null )
2567 ····················{
2568 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2569 ····················}
2570 ················}
2571 ················else
2572 ················{
2573 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2574 ····················{
2575 ························pcRow[fkColumn.Name] = DBNull.Value;
2576 ····················}
2577 ····················if ( r.ForeignKeyTypeColumnName != null )
2578 ····················{
2579 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2580 ····················}
2581 ················}
2582 ············}
2583 ········}
2584
2585
2586
2587 ········/// <summary>
2588 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2589 ········/// </summary>
2590 ········/// <param name="e">the mapping table entry</param>
2591 ········private void WriteMappingTableEntry(MappingTableEntry e)
2592 ········{
2593 ············IPersistenceCapable pc = e.ParentObject;
2594 ············IPersistenceCapable relObj = e.RelatedObject;
2595 ············Relation r = e.Relation;
2596 ············DataTable dt = GetTable(r.MappingTable.TableName);
2597 ············DataRow row = dt.NewRow();
2598 ············int i = 0;
2599 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2600 ············{
2601 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2602 ············}
2603 ············i = 0;
2604 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2605 ············{
2606 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2607 ············}
2608
2609 ············if (r.ForeignKeyTypeColumnName != null)
2610 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2611 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2612 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2613
2614 ············dt.Rows.Add(row);
2615 ············if(e.DeleteEntry)
2616 ············{
2617 ················row.AcceptChanges();
2618 ················row.Delete();
2619 ············}
2620
2621 ············IMappingTableHandler handler;
2622 ············if (!mappingHandler.TryGetValue( r, out handler ))
2623 ············{
2624 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2625 ············}
2626 ········}
2627
2628
2629 ········/// <summary>
2630 ········/// Undo changes of a certain object
2631 ········/// </summary>
2632 ········/// <param name="o">Object to undo</param>
2633 ········public void Restore(object o)
2634 ········{············
2635 ············IPersistenceCapable pc = CheckPc(o);
2636 ············Cache.Entry e = null;
2637 ············foreach (Cache.Entry entry in cache.LockedObjects)
2638 ············{
2639 ················if (entry.pc == pc)
2640 ················{
2641 ····················e = entry;
2642 ····················break;
2643 ················}
2644 ············}
2645 ············if (e == null)
2646 ················return;
2647 ············Class cl = GetClass(e.pc);
2648 ············switch (pc.NDOObjectState)
2649 ············{
2650 ················case NDOObjectState.PersistentDirty:
2651 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2652 ····················foreach(Relation r in cl.Relations)
2653 ····················{
2654 ························if (r.Multiplicity == RelationMultiplicity.Element)
2655 ························{
2656 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2657 ····························if (subPc != null && cache.IsLocked(subPc))
2658 ································Restore(subPc);
2659 ························}
2660 ························else
2661 ························{
2662 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2663 ································continue;
2664 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2665 ····························if (subList != null)
2666 ····························{
2667 ································foreach(IPersistenceCapable subPc2 in subList)
2668 ································{
2669 ····································if (cache.IsLocked(subPc2))
2670 ········································Restore(subPc2);
2671 ································}
2672 ····························}
2673 ························}
2674 ····················}
2675 ····················RestoreRelatedObjects(pc, e.relations);
2676 ····················e.row.RejectChanges();
2677 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2678 ····················cache.Unlock(pc);
2679 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2680 ····················break;
2681 ················case NDOObjectState.Created:
2682 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2683 ····················cache.Unlock(pc);
2684 ····················MakeObjectTransient(pc, true);
2685 ····················break;
2686 ················case NDOObjectState.Deleted:
2687 ····················if (!this.IsFakeRow(cl, e.row))
2688 ····················{
2689 ························e.row.RejectChanges();
2690 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2691 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2692 ····················}
2693 ····················else
2694 ····················{
2695 ························e.row.RejectChanges();
2696 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2697 ····················}
2698 ····················cache.Unlock(pc);
2699 ····················break;
2700
2701 ············}
2702 ········}
2703
2704 ········/// <summary>
2705 ········/// Aborts a pending transaction without restoring the object state.
2706 ········/// </summary>
2707 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2708 ········public virtual void AbortTransaction()
2709 ········{
2710 ············TransactionScope.Dispose();
2711 ········}
2712
2713 ········/// <summary>
2714 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2715 ········/// </summary>
2716 ········public virtual void Abort()
2717 ········{
2718 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2719 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2720 ············// individual row.
2721 ············createdDirectObjects.Clear();
2722 ············createdMappingTableObjects.Clear();
2723 ············ArrayList deletedObjects = new ArrayList();
2724 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2725
2726 ············// Read all objects from DataSet
2727 ············foreach (Cache.Entry e in cache.LockedObjects)
2728 ············{
2729 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2730
2731 ················Class cl = GetClass(e.pc);
2732 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2733 ················if (!isFakeRow)
2734 ················{
2735 ····················RestoreRelatedObjects(e.pc, e.relations);
2736 ················}
2737 ················else
2738 ················{
2739 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2740 ················}
2741
2742 ················switch(e.pc.NDOObjectState)
2743 ················{
2744 ····················case NDOObjectState.Created:
2745 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2746 ························deletedObjects.Add(e.pc);
2747 ························break;
2748
2749 ····················case NDOObjectState.PersistentDirty:
2750 ························e.row.RejectChanges();
2751 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2752 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2753 ························break;
2754
2755 ····················case NDOObjectState.Deleted:
2756 ························if (!isFakeRow)
2757 ························{
2758 ····························e.row.RejectChanges();
2759 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2760 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2761 ························}
2762 ························else
2763 ························{
2764 ····························e.row.RejectChanges();
2765 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2766 ························}
2767 ························break;
2768
2769 ····················default:
2770 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2771 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2772 ························//break;
2773 ················}
2774 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2775 ················{
2776 ····················hollowModeObjects.Add(e.pc);
2777 ················}
2778 ············}
2779 ············cache.UnlockAll();
2780 ············foreach(IPersistenceCapable pc in deletedObjects)
2781 ············{
2782 ················MakeObjectTransient(pc, true);
2783 ············}
2784 ············ds.Clear();
2785 ············mappingHandler.Clear();
2786 ············if(hollowMode)
2787 ············{
2788 ················MakeHollow(hollowModeObjects);
2789 ············}
2790
2791 ············this.relationChanges.Clear();
2792
2793
2794 ············AbortTransaction();
2795 ········}
2796
2797
2798 ········/// <summary>
2799 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2800 ········/// disable future access.
2801 ········/// </summary>
2802 ········/// <param name="pc"></param>
2803 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2804 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2805 ········{
2806 ············cache.Deregister(pc);
2807 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2808 ············if(removeId)
2809 ············{
2810 ················pc.NDOObjectId = null;
2811 ············}
2812 ············pc.NDOObjectState = NDOObjectState.Transient;
2813 ············pc.NDOStateManager = null;
2814 ········}
2815
2816 ········/// <summary>
2817 ········/// Makes an object transient.
2818 ········/// The object can be used afterwards, but changes will not be saved in the database.
2819 ········/// </summary>
2820 ········/// <remarks>
2821 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2822 ········/// </remarks>
2823 ········/// <param name="o">The object to detach.</param>
2824 ········public void MakeTransient(object o)
2825 ········{
2826 ············IPersistenceCapable pc = CheckPc(o);
2827 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2828 ············{
2829 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2830 ············}
2831
2832 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2833 ············{
2834 ················LoadData(pc);
2835 ············}
2836 ············MakeObjectTransient(pc, true);
2837 ········}
2838
2839
2840 ········/// <summary>
2841 ········/// Make all objects of a list transient.
2842 ········/// </summary>
2843 ········/// <param name="list">the list of transient objects</param>
2844 ········public void MakeTransient(System.Collections.IList list)
2845 ········{
2846 ············foreach (IPersistenceCapable pc in list)
2847 ················MakeTransient(pc);
2848 ········}
2849
2850 ········/// <summary>
2851 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2852 ········/// </summary>
2853 ········/// <param name="o">The object to remove</param>
2854 ········public void Delete(object o)
2855 ········{
2856 ············IPersistenceCapable pc = CheckPc(o);
2857 if( pc. NDOObjectState != NDOObjectState. Deleted)
 
 
 
 
2858 ············{
2859 ················Delete(pc, true);
2860 ············}
2861 ········}
2862
2863
2864 ········private void LoadAllRelations(object o)
2865 ········{
2866 ············IPersistenceCapable pc = CheckPc(o);
2867 ············Class cl = GetClass(pc);
2868 ············foreach(Relation r in cl.Relations)
2869 ············{
2870 ················if (!pc.NDOGetLoadState(r.Ordinal))
2871 ····················LoadRelation(pc, r, true);
2872 ············}
2873 ········}
2874
2875
2876 ········/// <summary>
2877 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2878 ········/// </summary>
2879 ········/// <remarks>
2880 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2881 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2882 ········/// the parent object.
2883 ········/// </remarks>
2884 ········/// <param name="pc">the object to remove</param>
2885 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2886 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2887 ········{
2888 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2889 ············//Debug.Indent();
2890 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2891 ············if (idn != null)
2892 ················idn.OnDelete();
2893
2894 ············LoadAllRelations(pc);
2895 ············DeleteRelatedObjects(pc, checkAssoziations);
2896
2897 ············switch(pc.NDOObjectState)
2898 ············{
2899 ················case NDOObjectState.Transient:
2900 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2901
2902 ················case NDOObjectState.Created:
2903 ····················DataRow row = cache.GetDataRow(pc);
2904 ····················row.Delete();
2905 ····················ArrayList cdosToDelete = new ArrayList();
2906 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2907 ························if ((object)cdo == (object)pc)
2908 ····························cdosToDelete.Add(cdo);
2909 ····················foreach (object o in cdosToDelete)
2910 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2911 ····················MakeObjectTransient(pc, true);
2912 ····················break;
2913 ················case NDOObjectState.Persistent:
2914 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2915 ························SaveObjectState(pc, true);
2916 ····················row = cache.GetDataRow(pc);
2917 ····················row.Delete();
2918 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2919 ····················break;
2920 ················case NDOObjectState.Hollow:
2921 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2922 ························SaveFakeRow(pc);
2923 ····················row = cache.GetDataRow(pc);
2924 ····················row.Delete();
2925 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2926 ····················break;
2927
2928 ················case NDOObjectState.PersistentDirty:
2929 ····················row = cache.GetDataRow(pc);
2930 ····················row.Delete();
2931 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2932 ····················break;
2933
2934 ················case NDOObjectState.Deleted:
2935 ····················break;
2936 ············}
2937
2938 ············//Debug.Unindent();
2939 ········}
2940
2941
2942 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2943 ········{
2944 ············MappingTableEntry mte = null;
2945 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2946 ············{
2947 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2948 ················{
2949 ····················mte = e;
2950 ····················break;
2951 ················}
2952 ············}
2953
2954 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2955 ············{
2956 ················if (mte != null)
2957 ····················createdMappingTableObjects.Remove(mte);
2958 ············}
2959 ············else
2960 ············{
2961 ················if (mte == null)
2962 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2963 ············}
2964 ········}
2965
2966 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2967 ········{
2968 ············// Two tasks: a) Null the foreign key
2969 ············//··············b) remove the element from the foreign container
2970
2971 ············if (!r.Bidirectional)
2972 ················return;
2973
2974 ············// this keeps the oid valid
2975 ············if (GetClass(child.GetType()).Oid.IsDependent)
2976 ················return;
2977
2978 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2979 ············{
2980 ················LoadAndMarkDirty(child);
2981 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2982 ············}
2983 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
2984 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
2985 ············{
2986 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
2987 ····················LoadRelation(child, r.ForeignRelation, true);
2988 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
2989 ················if (l == null)
2990 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
2991 ················ObjectListManipulator.Remove(l, pc);
2992 ················// Don't need to delete the mapping table entry, because that was done
2993 ················// through the parent.
2994 ············}
2995 ········}
2996
2997 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2998 ········{
2999 ············// 1) Element····nomap····ass
3000 ············// 2) Element····nomap····comp
3001 ············// 3) Element····map········ass
3002 ············// 4) Element····map········comp
3003 ············// 5) List········nomap····ass
3004 ············// 6) List········nomap····comp
3005 ············// 7) List········map········ass
3006 ············// 8) List········map········comp
3007
3008 ············// Two tasks: Null foreign key and, if Composition, delete the child
3009
3010 ············// If Mapping Table, delete the entry - 3,7
3011 ············// If List and assoziation, null the foreign key in the foreign class - 5
3012 ············// If Element, null the foreign key in the own class 1,2,3,4
3013 ············// If composition, delete the child 2,4,6,8
3014
3015 ············// If the relObj is newly created
3016 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3017 ············Class childClass = GetClass(child);
3018
3019 ············if (r.MappingTable != null)··// 3,7
3020 ············{················
3021 ················DeleteMappingTableEntry(pc, r, child);
3022 ············}
3023 ············else if (r.Multiplicity == RelationMultiplicity.List
3024 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3025 ············{················
3026 ················LoadAndMarkDirty(child);
3027 ················DataRow row = this.cache.GetDataRow(child);
3028 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3029 ················{
3030 ····················row[fkColumnn.Name] = DBNull.Value;
3031 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3032 ················}
3033 ············}
3034 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3035 ············{
3036 ················LoadAndMarkDirty(pc);
3037 ············}
3038 ············if (r.Composition || childClass.Oid.IsDependent)
3039 ············{
3040 #if DEBUG
3041 ················if (child.NDOObjectState == NDOObjectState.Transient)
3042 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3043 #endif
3044 ················// Deletes the foreign key in case of List multiplicity
3045 ················// In case of Element multiplicity, the parent is either deleted,
3046 ················// or RemoveRelatedObject is called because the relation has been nulled.
3047 ················Delete(child);··
3048 ············}
3049 ········}
3050
3051 ········/// <summary>
3052 ········/// Remove a related object
3053 ········/// </summary>
3054 ········/// <param name="pc"></param>
3055 ········/// <param name="r"></param>
3056 ········/// <param name="child"></param>
3057 ········/// <param name="calledFromStateManager"></param>
3058 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3059 ········{
3060 ············//TODO: We need a relation management, which is independent of
3061 ············//the state management of an object. At the moment the relation
3062 ············//lists or elements are cached for restore, if an object is marked dirty.
3063 ············//Thus we have to mark dirty our parent object in any case at the moment.
3064 ············if (calledFromStateManager)
3065 ················MarkDirty(pc);
3066
3067 ············// Object can be deleted in an OnDelete-Handler
3068 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3069 ················return;
3070 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3071 ············// Preconditions
3072 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3073 ············// The Object state is checked there
3074
3075 ············// If there is a composition in the opposite direction
3076 ············// && the other direction hasn't been processed
3077 ············// throw an exception.
3078 ············// If an exception is thrown at this point, have a look at IsLocked....
3079 ············if (r.Bidirectional && r.ForeignRelation.Composition
3080 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3081 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3082
3083 ············if (!removeLock.GetLock(pc, r, child))
3084 ················return;
3085
3086 ············try
3087 ············{
3088 ················// must be in this order, since the child
3089 ················// can be deleted in DeleteOrNullRelation
3090 ················//if (changeForeignRelations)
3091 ················DeleteOrNullForeignRelation(pc, r, child);
3092 ················DeleteOrNullRelation(pc, r, child);
3093 ············}
3094 ············finally
3095 ············{
3096 ················removeLock.Unlock(pc, r, child);
3097 ············}
3098
3099 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3100 ········}
3101
3102 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3103 ········{
3104 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3105 ············//············Debug.Indent();
3106 ············if (r.Multiplicity == RelationMultiplicity.Element)
3107 ············{
3108 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3109 ················if(child != null)
3110 ················{
3111 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3112 ····················//····················{
3113 ····················//························if (!r.ForeignRelation.Composition)
3114 ····················//························{
3115 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3116 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3117 ····················//························}
3118 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3119 ····················//························else
3120 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3121 ····················//····················}
3122 ····················InternalRemoveRelatedObject(pc, r, child, false);
3123 ················}
3124 ············}
3125 ············else
3126 ············{
3127 ················IList list = mappings.GetRelationContainer(pc, r);
3128 ················if(list != null && list.Count > 0)
3129 ················{
3130 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3131 ····················//····················{
3132 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3133 ····················//····················}
3134 ····················// Since RemoveRelatedObjects probably changes the list,
3135 ····················// we iterate through a copy of the list.
3136 ····················ArrayList al = new ArrayList(list);
3137 ····················foreach(IPersistenceCapable relObj in al)
3138 ····················{
3139 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3140 ····················}
3141 ················}
3142 ············}
3143 ············//············Debug.Unindent();
3144 ········}
3145
3146 ········/// <summary>
3147 ········/// Remove all related objects from a parent.
3148 ········/// </summary>
3149 ········/// <param name="pc">the parent object</param>
3150 ········/// <param name="checkAssoziations"></param>
3151 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3152 ········{
3153 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3154 ············//············Debug.Indent();
3155 ············// delete all related objects:
3156 ············Class parentClass = GetClass(pc);
3157 ············// Remove Assoziations first
3158 ············foreach(Relation r in parentClass.Relations)
3159 ············{
3160 ················if (!r.Composition)
3161 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3162 ············}
3163 ············foreach(Relation r in parentClass.Relations)
3164 ············{
3165 ················if (r.Composition)
3166 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3167 ············}
3168
3169 ············//············Debug.Unindent();
3170 ········}
3171
3172 ········/// <summary>
3173 ········/// Delete a list of objects
3174 ········/// </summary>
3175 ········/// <param name="list">the list of objects to remove</param>
3176 ········public void Delete(IList list)
3177 ········{
3178 ············for (int i = 0; i < list.Count; i++)
3179 ············{
3180 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3181 ················Delete(pc);
3182 ············}
3183 ········}
3184
3185 ········/// <summary>
3186 ········/// Make object hollow. All relations will be unloaded and object data will be
3187 ········/// newly fetched during the next touch of a persistent field.
3188 ········/// </summary>
3189 ········/// <param name="o"></param>
3190 ········public virtual void MakeHollow(object o)
3191 ········{
3192 ············IPersistenceCapable pc = CheckPc(o);
3193 ············MakeHollow(pc, false);
3194 ········}
3195
3196 ········/// <summary>
3197 ········/// Make the object hollow if it is persistent. Unload all complex data.
3198 ········/// </summary>
3199 ········/// <param name="o"></param>
3200 ········/// <param name="recursive">if true then unload related objects as well</param>
3201 ········public virtual void MakeHollow(object o, bool recursive)
3202 ········{
3203 ············IPersistenceCapable pc = CheckPc(o);
3204 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3205 ················return;
3206 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3207 ············{
3208 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3209 ············}
3210 ············pc.NDOObjectState = NDOObjectState.Hollow;
3211 ············MakeRelationsHollow(pc, recursive);
3212 ········}
3213
3214 ········/// <summary>
3215 ········/// Make all objects of a list hollow.
3216 ········/// </summary>
3217 ········/// <param name="list">the list of objects that should be made hollow</param>
3218 ········public virtual void MakeHollow(System.Collections.IList list)
3219 ········{
3220 ············MakeHollow(list, false);
3221 ········}
3222
3223 ········/// <summary>
3224 ········/// Make all objects of a list hollow.
3225 ········/// </summary>
3226 ········/// <param name="list">the list of objects that should be made hollow</param>
3227 ········/// <param name="recursive">if true then unload related objects as well</param>
3228 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3229 ········{
3230 ············foreach (IPersistenceCapable pc in list)
3231 ················MakeHollow(pc, recursive);················
3232 ········}
3233
3234 ········/// <summary>
3235 ········/// Make all unlocked objects in the cache hollow.
3236 ········/// </summary>
3237 ········public virtual void MakeAllHollow()
3238 ········{
3239 ············foreach(var pc in cache.UnlockedObjects)
3240 ············{
3241 ················MakeHollow(pc, false);
3242 ············}
3243 ········}
3244
3245 ········/// <summary>
3246 ········/// Make relations hollow.
3247 ········/// </summary>
3248 ········/// <param name="pc">The parent object</param>
3249 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3250 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3251 ········{
3252 ············Class c = GetClass(pc);
3253 ············foreach(Relation r in c.Relations)
3254 ············{
3255 ················if (r.Multiplicity == RelationMultiplicity.Element)
3256 ················{
3257 ····················mappings.SetRelationField(pc, r.FieldName, null);
3258 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3259 ····················//····················if((null != child) && recursive)
3260 ····················//····················{
3261 ····················//························MakeHollow(child, true);
3262 ····················//····················}
3263 ················}
3264 ················else
3265 ················{
3266 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3267 ························continue;
3268 ····················// Help GC by clearing lists
3269 ····················IList l = mappings.GetRelationContainer(pc, r);
3270 ····················if(l != null)
3271 ····················{
3272 ························if(recursive)
3273 ························{
3274 ····························MakeHollow(l, true);
3275 ························}
3276 ························l.Clear();
3277 ····················}
3278 ····················// Hollow relation
3279 ····················mappings.SetRelationContainer(pc, r, null);
3280 ················}
3281 ············}
3282 ············ClearRelationState(pc);
3283 ········}
3284
3285 ········private void ClearRelationState(IPersistenceCapable pc)
3286 ········{
3287 ············Class cl = GetClass(pc);
3288 ············foreach(Relation r in cl.Relations)
3289 ················pc.NDOSetLoadState(r.Ordinal, false);
3290 ········}
3291
3292 ········private void SetRelationState(IPersistenceCapable pc)
3293 ········{
3294 ············Class cl = GetClass(pc);
3295 ············// Due to a bug in the enhancer the constructors are not always patched right,
3296 ············// so NDOLoadState might be uninitialized
3297 ············if (pc.NDOLoadState == null)
3298 ············{
3299 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3300 ················if (fi == null)
3301 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3302 ················fi.SetValue(pc, new LoadState());
3303 ············}
3304
3305 ············// After serialization the relation load state is null
3306 ············if (pc.NDOLoadState.RelationLoadState == null)
3307 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3308 ············foreach(Relation r in cl.Relations)
3309 ················pc.NDOSetLoadState(r.Ordinal, true);
3310 ········}
3311
3312 ········/// <summary>
3313 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3314 ········/// </summary>
3315 ········/// <param name="t">The type of the persistent object</param>
3316 ········/// <returns></returns>
3317 ········public IPersistenceCapable CreateObject(Type t)
3318 ········{
3319 ············return Metaclasses.GetClass( t ).CreateObject( this.ConfigContainer );
3320 ········}
3321
3322 ········/// <summary>
3323 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3324 ········/// </summary>
3325 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3326 ········/// <returns></returns>
3327 ········public T CreateObject<T>()
3328 ········{
3329 ············return (T)CreateObject( typeof( T ) );
3330 ········}
3331
3332 ········/// <summary>
3333 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3334 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3335 ········/// is performed.
3336 ········/// </summary>
3337 ········/// <param name="t">The type of the object to retrieve.</param>
3338 ········/// <param name="keyData">The key value to build the object id.</param>
3339 ········/// <returns>A hollow object</returns>
3340 ········/// <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>
3341 ········public IPersistenceCapable FindObject(Type t, object keyData)
3342 ········{
3343 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3344 ············return FindObject(oid);
3345 ········}
3346
3347 ········/// <summary>
3348 ········/// Finds an object using a short id.
3349 ········/// </summary>
3350 ········/// <param name="encodedShortId"></param>
3351 ········/// <returns></returns>
3352 ········public IPersistenceCapable FindObject(string encodedShortId)
3353 ········{
3354 ············string shortId = encodedShortId.Decode();
3355 ············string[] arr = shortId.Split( '-' );
3356 ············if (arr.Length != 3)
3357 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3358 ············Type t = shortId.GetObjectType(this);··// try readable format
3359 ············if (t == null)
3360 ············{
3361 ················int typeCode = 0;
3362 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3363 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3364 ················t = this.typeManager[typeCode];
3365 ················if (t == null)
3366 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3367 ············}
3368
3369 ············Class cls = GetClass( t );
3370 ············if (cls == null)
3371 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3372
3373 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3374 ············string[] oidValues = arr[2].Split( ' ' );
3375
3376 ············int i = 0;
3377 ············foreach (var oidValue in oidValues)
3378 ············{
3379 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3380 ················if (oidType == typeof( int ))
3381 ················{
3382 ····················int key;
3383 ····················if (!int.TryParse( oidValue, out key ))
3384 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3385 ····················if (key > (int.MaxValue >> 1))
3386 ························key = -(int.MaxValue - key);
3387 ····················keydata[i] = key;
3388 ················}
3389 ················else if (oidType == typeof( Guid ))
3390 ················{
3391 ····················Guid key;
3392 ····················if (!Guid.TryParse( oidValue, out key ))
3393 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3394 ····················keydata[i] = key;
3395 ················}
3396 ················else if (oidType == typeof( string ))
3397 ················{
3398 ····················keydata[i] = oidValue;
3399 ················}
3400 ················else
3401 ················{
3402 ····················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 ) );
3403 ················}
3404
3405 ················i++;
3406 ············}
3407
3408 ············if (keydata.Length == 1)
3409 ················return FindObject( t, keydata[0] );
3410
3411 ············return FindObject( t, keydata );············
3412 ········}
3413
3414 ········/// <summary>
3415 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3416 ········/// instance of the object is returned. In either case, the DB is not accessed!
3417 ········/// </summary>
3418 ········/// <param name="id">Object id</param>
3419 ········/// <returns>The object to retrieve in hollow state</returns>········
3420 ········public IPersistenceCapable FindObject(ObjectId id)
3421 ········{
3422 ············if(id == null)
3423 ············{
3424 ················throw new ArgumentNullException("id");
3425 ············}
3426
3427 ············if(!id.IsValid())
3428 ············{
3429 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3430 ············}
3431
3432 ············IPersistenceCapable pc = cache.GetObject(id);
3433 ············if(pc == null)
3434 ············{
3435 ················pc = CreateObject(id.Id.Type);
3436 ················pc.NDOObjectId = id;
3437 ················pc.NDOStateManager = sm;
3438 ················pc.NDOObjectState = NDOObjectState.Hollow;
3439 ················cache.UpdateCache(pc);
3440 ············}
3441 ············return pc;
3442 ········}
3443
3444
3445 ········/// <summary>
3446 ········/// Reload an object from the database.
3447 ········/// </summary>
3448 ········/// <param name="o">The object to be reloaded.</param>
3449 ········public virtual void Refresh(object o)
3450 ········{
3451 ············IPersistenceCapable pc = CheckPc(o);
3452 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3453 ············{
3454 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3455 ············}
3456
3457 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3458 ················return; // Cannot update objects in current transaction
3459
3460 ············MakeHollow(pc);
3461 ············LoadData(pc);
3462 ········}
3463
3464 ········/// <summary>
3465 ········/// Refresh a list of objects.
3466 ········/// </summary>
3467 ········/// <param name="list">The list of objects to be refreshed.</param>
3468 ········public virtual void Refresh(IList list)
3469 ········{
3470 ············foreach (IPersistenceCapable pc in list)
3471 ················Refresh(pc);························
3472 ········}
3473
3474 ········/// <summary>
3475 ········/// Refreshes all unlocked objects in the cache.
3476 ········/// </summary>
3477 ········public virtual void RefreshAll()
3478 ········{
3479 ············Refresh( cache.UnlockedObjects.ToList() );
3480 ········}
3481
3482 ········/// <summary>
3483 ········/// Closes the PersistenceManager and releases all resources.
3484 ········/// </summary>
3485 ········public override void Close()
3486 ········{
3487 ············if (this.isClosing)
3488 ················return;
3489 ············this.isClosing = true;
3490 ············TransactionScope.Dispose();
3491 ············UnloadCache();
3492 ············base.Close();
3493 ········}
3494
3495 ········internal void LogIfVerbose(string msg)
3496 ········{
3497 ············if (VerboseMode && LogAdapter != null)
3498 ············{
3499 ················this.LogAdapter.Info( msg );
3500 ············}
3501 ········}
3502
3503
3504 ········#endregion
3505
3506
3507 #region Class extent
3508 ········/// <summary>
3509 ········/// Gets all objects of a given class.
3510 ········/// </summary>
3511 ········/// <param name="t">the type of the class</param>
3512 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3513 ········public virtual IList GetClassExtent(Type t)
3514 ········{
3515 ············return GetClassExtent(t, true);
3516 ········}
3517
3518 ········/// <summary>
3519 ········/// Gets all objects of a given class.
3520 ········/// </summary>
3521 ········/// <param name="t">The type of the class.</param>
3522 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3523 ········/// <returns>A list of all persistent objects of the given class.</returns>
3524 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3525 ········public virtual IList GetClassExtent(Type t, bool hollow)
3526 ········{
3527 ············IQuery q = NewQuery( t, null, hollow );
3528 ············return q.Execute();
3529 ········}
3530
3531 #endregion
3532
3533 #region Query engine
3534
3535
3536 ········/// <summary>
3537 ········/// Returns a virtual table for Linq queries.
3538 ········/// </summary>
3539 ········/// <typeparam name="T"></typeparam>
3540 ········/// <returns></returns>
3541 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3542 ········{
3543 ············return new VirtualTable<T>( this );
3544 ········}
3545
3546 ········
3547 ········/// <summary>
3548 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3549 ········/// 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
3550 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3551 ········/// not lose the foreign key it will be stored as part of the object.
3552 ········/// </summary>
3553 ········/// <param name="cl"></param>
3554 ········/// <param name="pc"></param>
3555 ········/// <param name="row"></param>
3556 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3557 ········{
3558 ············if (cl.FKColumnNames != null)
3559 ············{
3560 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3561 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3562 ················foreach(string colName in cl.FKColumnNames)
3563 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3564 ················pc.NDOLoadState.LostRowInfo = kvl;················
3565 ············}
3566 ········}
3567
3568 ········/// <summary>
3569 ········/// Writes information into the data row, which cannot be stored in the object.
3570 ········/// </summary>
3571 ········/// <param name="cl"></param>
3572 ········/// <param name="pc"></param>
3573 ········/// <param name="row"></param>
3574 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3575 ········{
3576 ············if (cl.FKColumnNames != null)
3577 ············{
3578 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3579 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3580 ················if (kvl == null)
3581 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3582 ················foreach (KeyValuePair pair in kvl)
3583 ····················row[(string) pair.Key] = pair.Value;
3584 ············}
3585 ········}
3586
3587 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3588 ········{
3589 ············ReadObject(pc, row, cl.ColumnNames, 0);
3590 ············ReadTimeStamp(cl, pc, row);
3591 ············ReadLostForeignKeysFromRow(cl, pc, row);
3592 ············LoadRelated1To1Objects(pc, row);
3593 ············pc.NDOObjectState = NDOObjectState.Persistent;
3594 ········}
3595
3596
3597 ········/// <summary>
3598 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3599 ········/// </summary>
3600 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3601 ········{
3602 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3603 ············if (rows.Count == 0)
3604 ················return queryResult;
3605
3606 ············IList callbackObjects = new ArrayList();
3607 ············Class cl = GetClass(t);
3608 ············if (t.IsGenericTypeDefinition)
3609 ············{
3610 ················if (cl.TypeNameColumn == null)
3611 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3612 ················IEnumerator en = rows.GetEnumerator();
3613 ················en.MoveNext();··// Move to the first element
3614 ················DataRow testrow = (DataRow)en.Current;
3615 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3616 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3617 ············}
3618
3619 ············foreach(DataRow row in rows)
3620 ············{
3621 ················Type concreteType = t;
3622 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3623 ················{
3624 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3625 ····················{
3626 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3627 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3628 ····················}
3629 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3630 ····················concreteType = Type.GetType(typeStr);
3631 ····················if (concreteType == null)
3632 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3633 ················}
3634 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3635 ················IPersistenceCapable pc = cache.GetObject(id);················
3636 ················if(pc == null)
3637 ················{
3638 ····················var mc = Metaclasses.GetClass(concreteType);
3639 ····················pc = mc.CreateObject( this.ConfigContainer );
3640 ····················pc.NDOObjectId = id;
3641 ····················pc.NDOStateManager = sm;
3642 ····················// If the object shouldn't be hollow, this will be overwritten later.
3643 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3644 ················}
3645 ················// If we have found a non hollow object, the time stamp remains the old one.
3646 ················// In every other case we use the new time stamp.
3647 ················// Note, that there could be a hollow object in the cache.
3648 ················// We need the time stamp in hollow objects in order to correctly
3649 ················// delete objects using fake rows.
3650 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3651 ················{
3652 ····················ReadTimeStamp(cl, pc, row);
3653 ················}
3654 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3655 ················{
3656 ····················Row2Object(cl, pc, row);
3657 ····················if ((pc as IPersistenceNotifiable) != null)
3658 ························callbackObjects.Add(pc);
3659 ················}
3660
3661 ················cache.UpdateCache(pc);
3662 ················queryResult.Add(pc);
3663 ············}
3664 ············// Make shure this is the last statement before returning
3665 ············// to the caller, so the user can recursively use persistent
3666 ············// objects
3667 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3668 ················ipn.OnLoaded();
3669
3670 ············return queryResult;
3671 ········}
3672
3673 #endregion
3674
3675 #region Cache Management
3676 ········/// <summary>
3677 ········/// Remove all unused entries from the cache.
3678 ········/// </summary>
3679 ········public void CleanupCache()
3680 ········{
3681 ············GC.Collect(GC.MaxGeneration);
3682 ············GC.WaitForPendingFinalizers();
3683 ············cache.Cleanup();
3684 ········}
3685
3686 ········/// <summary>
3687 ········/// Remove all unlocked objects from the cache. Use with care!
3688 ········/// </summary>
3689 ········public void UnloadCache()
3690 ········{
3691 ············cache.Unload();
3692 ········}
3693 #endregion
3694
3695 ········/// <summary>
3696 ········/// Default encryption key for NDO
3697 ········/// </summary>
3698 ········/// <remarks>
3699 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3700 ········/// </remarks>
3701 ········public virtual byte[] EncryptionKey
3702 ········{
3703 ············get
3704 ············{
3705 ················if (this.encryptionKey == null)
3706 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3707 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3708 ················return this.encryptionKey;
3709 ············}
3710 ············set { this.encryptionKey = value; }
3711 ········}
3712
3713 ········/// <summary>
3714 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3715 ········/// </summary>
3716 ········public virtual bool HollowMode
3717 ········{
3718 ············get { return hollowMode; }
3719 ············set { hollowMode = value; }
3720 ········}
3721
3722 ········internal TypeManager TypeManager
3723 ········{
3724 ············get { return typeManager; }
3725 ········}
3726
3727 ········/// <summary>
3728 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3729 ········/// </summary>
3730 ········/// <remarks>
3731 ········/// Set this value before you start any transactions.
3732 ········/// </remarks>
3733 ········public TransactionMode TransactionMode
3734 ········{
3735 ············get { return TransactionScope.TransactionMode; }
3736 ············set { TransactionScope.TransactionMode = value; }
3737 ········}
3738
3739 ········/// <summary>
3740 ········/// Sets or gets the Isolation Level.
3741 ········/// </summary>
3742 ········/// <remarks>
3743 ········/// Set this value before you start any transactions.
3744 ········/// </remarks>
3745 ········public IsolationLevel IsolationLevel
3746 ········{
3747 ············get { return TransactionScope.IsolationLevel; }
3748 ············set { TransactionScope.IsolationLevel = value; }
3749 ········}
3750
3751 ········internal class MappingTableEntry
3752 ········{
3753 ············private IPersistenceCapable parentObject;
3754 ············private IPersistenceCapable relatedObject;
3755 ············private Relation relation;
3756 ············private bool deleteEntry;
3757 ············
3758 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3759 ············{
3760 ············}
3761 ············
3762 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3763 ············{
3764 ················parentObject = pc;
3765 ················relatedObject = relObj;
3766 ················relation = r;
3767 ················this.deleteEntry = deleteEntry;
3768 ············}
3769
3770 ············public bool DeleteEntry
3771 ············{
3772 ················get
3773 ················{
3774 ····················return deleteEntry;
3775 ················}
3776 ············}
3777
3778 ············public IPersistenceCapable ParentObject
3779 ············{
3780 ················get
3781 ················{
3782 ····················return parentObject;
3783 ················}
3784 ············}
3785
3786 ············public IPersistenceCapable RelatedObject
3787 ············{
3788 ················get
3789 ················{
3790 ····················return relatedObject;
3791 ················}
3792 ············}
3793
3794 ············public Relation Relation
3795 ············{
3796 ················get
3797 ················{
3798 ····················return relation;
3799 ················}
3800 ············}
3801 ········}
3802
3803 ········/// <summary>
3804 ········/// Get a DataRow representing the given object.
3805 ········/// </summary>
3806 ········/// <param name="o"></param>
3807 ········/// <returns></returns>
3808 ········public DataRow GetClonedDataRow( object o )
3809 ········{
3810 ············IPersistenceCapable pc = CheckPc( o );
3811
3812 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3813 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3814
3815 ············DataRow row = cache.GetDataRow( pc );
3816 ············DataTable newTable = row.Table.Clone();
3817 ············newTable.ImportRow( row );
3818 ············row = newTable.Rows[0];
3819
3820 ············Class cls = mappings.FindClass(o.GetType());
3821 ············WriteObject( pc, row, cls.ColumnNames );
3822 ············WriteForeignKeysToRow( pc, row );
3823
3824 ············return row;
3825 ········}
3826
3827 ········/// <summary>
3828 ········/// Gets an object, which shows all changes applied to the given object.
3829 ········/// This function can be used to build an audit system.
3830 ········/// </summary>
3831 ········/// <param name="o"></param>
3832 ········/// <returns>An ExpandoObject</returns>
3833 ········public ChangeLog GetChangeSet( object o )
3834 ········{
3835 ············var changeLog = new ChangeLog(this);
3836 ············changeLog.Initialize( o );
3837 ············return changeLog;
3838 ········}
3839
3840 ········/// <summary>
3841 ········/// Outputs a revision number representing the assembly version.
3842 ········/// </summary>
3843 ········/// <remarks>This can be used for debugging purposes</remarks>
3844 ········public int Revision
3845 ········{
3846 ············get
3847 ············{
3848 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3849 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3850 ················return int.Parse( vstring );
3851 ············}
3852 ········}
3853 ····}
3854 }
3855
New Commit (3eaf9d3)
1 //
2 // Copyright (c) 2002-2016 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.Dynamic;
32 using System.Text.RegularExpressions;
33 using System.Linq;
34 using System.Xml.Linq;
35
36 using NDO.Mapping;
37 using NDOInterfaces;
38 using NDO.ShortId;
39 using System.Globalization;
40 using NDO.Linq;
41 using NDO.Query;
42 using ST = System.Transactions;
43 using NDO.Configuration;
44 using NDO.ChangeLogging;
45
46 namespace NDO
47 {
48 ····/// <summary>
49 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
50 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
51 ····/// </summary>
52 ····public delegate void CollisionHandler(object o);
53 ····/// <summary>
54 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
55 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
56 ····/// </summary>
57 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
58 ····/// <summary>
59 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
60 ····/// </summary>
61 ····public delegate void OnSavingHandler(ICollection l);
62 ····/// <summary>
63 ····/// Delegate type for the OnSavedEvent.
64 ····/// </summary>
65 ····/// <param name="auditSet"></param>
66 ····public delegate void OnSavedHandler(AuditSet auditSet);
67
68 ····/// <summary>
69 ····/// 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.
70 ····/// </summary>
71 ····/// <param name="pc"></param>
72 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
73 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
74 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
75
76 ····/// <summary>
77 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
78 ····/// 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.
79 ····/// </summary>
80 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
81 ····{········
82 ········private bool hollowMode = false;
83 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
84
85 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
86 ········private ObjectLock removeLock = new ObjectLock();
87 ········private ObjectLock addLock = new ObjectLock();
88 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
89 ········// List of created objects that use mapping table and need to be stored in mapping table
90 ········// after they have been stored to the database to update foreign keys.
91 ········private ArrayList createdMappingTableObjects = new ArrayList();··
92 ········private TypeManager typeManager;
93 ········internal bool DeferredMode { get; private set; }
94 ········private INDOTransactionScope transactionScope;
95 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ConfigContainer.Resolve<INDOTransactionScope>());········
96
97 ········private OpenConnectionListener openConnectionListener;
98
99 ········/// <summary>
100 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
101 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
102 ········/// </summary>
103 ········public event CollisionHandler CollisionEvent;
104
105 ········/// <summary>
106 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
107 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
108 ········/// </summary>
109 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
110
111 ········/// <summary>
112 ········/// Register a listener to this event, if you want to be notified about the end
113 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
114 ········/// during the transaction and are to be saved or deleted.
115 ········/// </summary>
116 ········public event OnSavingHandler OnSavingEvent;
117 ········/// <summary>
118 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
119 ········/// </summary>
120 ········public event OnSavedHandler OnSavedEvent;
121 ········
122 ········private const string hollowMarker = "Hollow";
123 ········private byte[] encryptionKey;
124 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
125 ········private bool isClosing = false;
126
127 ········/// <summary>
128 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
129 ········/// </summary>
130 ········protected internal List<RelationChangeRecord> RelationChanges
131 ········{
132 ············get { return this.relationChanges; }
133 ········}
134
135 ········/// <summary>
136 ········/// Initializes a new PersistenceManager instance.
137 ········/// </summary>
138 ········/// <param name="mappingFileName"></param>
139 ········protected override void Init(string mappingFileName)
140 ········{
141 ············try
142 ············{
143 ················base.Init(mappingFileName);
144 ············}
145 ············catch (Exception ex)
146 ············{
147 ················if (ex is NDOException)
148 ····················throw ex;
149 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
150 ············}
151
152 ········}
153
154 ········/// <summary>
155 ········/// Initializes the persistence manager
156 ········/// </summary>
157 ········/// <remarks>
158 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
159 ········/// </remarks>
160 ········/// <param name="mapping"></param>
161 ········internal override void Init( Mappings mapping )
162 ········{
163 ············ConfigContainer.RegisterType<INDOTransactionScope, NDOTransactionScope>();
164
165 ············base.Init( mapping );
166
167 ············string dir = Path.GetDirectoryName( mapping.FileName );
168
169 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
170 ············typeManager = new TypeManager( typesFile, this.mappings );
171
172 ············sm = new StateManager( this );
173
174 ············InitClasses();
175 ········}
176
177
178 ········/// <summary>
179 ········/// Standard Constructor.
180 ········/// </summary>
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() : base()
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 ········/// <remarks>Only the Professional and Enterprise
197 ········/// Editions can handle more than one mapping file.</remarks>
198 ········public PersistenceManager(string mappingFile) : base (mappingFile)
199 ········{
200 ········}
201
202 ········/// <summary>
203 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
204 ········/// </summary>
205 ········/// <param name="mapping">The cached mapping object</param>
206 ········public PersistenceManager(NDOMapping mapping) : base (mapping)
207 ········{
208 ········}
209
210 ········#region Object Container Stuff
211 ········/// <summary>
212 ········/// Gets a container of all loaded objects and tries to load all child objects,
213 ········/// which are reachable through composite relations.
214 ········/// </summary>
215 ········/// <returns>An ObjectContainer object.</returns>
216 ········/// <remarks>
217 ········/// It is not recommended, to transfer objects with a state other than Hollow,
218 ········/// Persistent, or Transient.
219 ········/// The transfer format is binary.
220 ········/// </remarks>
221 ········public ObjectContainer GetObjectContainer()
222 ········{
223 ············IList l = this.cache.AllObjects;
224 ············foreach(IPersistenceCapable pc in l)
225 ············{
226 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
227 ················{
228 ····················if (this.VerboseMode)
229 ························this.LogAdapter.Warn("Call to GetObjectContainer returns changed objects.");
230 ····················System.Diagnostics.Trace.WriteLine("NDO warning: Call to GetObjectContainer returns changed objects.");
231 ················}
232 ············}
233 ············ObjectContainer oc = new ObjectContainer();
234 ············oc.AddList(l);
235 ············return oc;
236 ········}
237
238 ········/// <summary>
239 ········/// Returns a container of all objects provided in the objects list and searches for
240 ········/// child objects according to the serFlags.
241 ········/// </summary>
242 ········/// <param name="objects">The list of the root objects to add to the container.</param>
243 ········/// <returns>An ObjectContainer object.</returns>
244 ········/// <remarks>
245 ········/// It is not recommended, to transfer objects with a state other than Hollow,
246 ········/// Persistent, or Transient.
247 ········/// </remarks>
248 ········public ObjectContainer GetObjectContainer(IList objects)
249 ········{
250 ············foreach(object o in objects)
251 ············{
252 ················CheckPc(o);
253 ················IPersistenceCapable pc = o as IPersistenceCapable;
254 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
255 ····················LoadData(pc);
256 ············}
257 ············ObjectContainer oc = new ObjectContainer();
258 ············oc.AddList(objects);
259 ············return oc;
260 ········}
261
262
263 ········/// <summary>
264 ········/// Returns a container containing the provided object
265 ········/// and tries to load all child objects
266 ········/// reachable through composite relations.
267 ········/// </summary>
268 ········/// <param name="obj">The object to be added to the container.</param>
269 ········/// <returns>An ObjectContainer object.</returns>
270 ········/// <remarks>
271 ········/// It is not recommended, to transfer objects with a state other than Hollow,
272 ········/// Persistent, or Transient.
273 ········/// The transfer format is binary.
274 ········/// </remarks>
275 ········public ObjectContainer GetObjectContainer(Object obj)
276 ········{
277 ············CheckPc(obj);
278 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
279 ················LoadData(obj);
280 ············ObjectContainer oc = new ObjectContainer();
281 ············oc.AddObject(obj);
282 ············return oc;
283 ········}
284
285 ········/// <summary>
286 ········/// Merges an object container to the active objects in the pm. All changes and the state
287 ········/// of the objects will be taken over by the pm.
288 ········/// </summary>
289 ········/// <remarks>
290 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
291 ········/// The flag MarkAsTransient can be used to perform a kind
292 ········/// of object based replication using the ObjectContainer class.
293 ········/// Objects, which are persistent at one machine, can be transfered
294 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
295 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
296 ········/// transient object tree.
297 ········/// There is one difference to freshly created objects: If an object id exists, it will be
298 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
299 ········/// oids will be reused by the receiving PersistenceManager.
300 ········/// </remarks>
301 ········/// <param name="ocb">The object container to be merged.</param>
302 ········public void MergeObjectContainer(ObjectContainerBase ocb)
303 ········{
304 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
305 ············if (csc != null)
306 ············{
307 ················MergeChangeSet(csc);
308 ················return;
309 ············}
310 ············ObjectContainer oc = ocb as ObjectContainer;
311 ············if (oc != null)
312 ············{
313 ················InternalMergeObjectContainer(oc);
314 ················return;
315 ············}
316 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
317 ········}
318
319
320 ········void InternalMergeObjectContainer(ObjectContainer oc)
321 ········{
322 ············// TODO: Check, if other states are useful. Find use scenarios.
323 ············foreach(IPersistenceCapable pc in oc.RootObjects)
324 ············{
325 ················if (pc.NDOObjectState == NDOObjectState.Transient)
326 ····················MakePersistent(pc);
327 ············}
328 ············foreach(IPersistenceCapable pc in oc.RootObjects)
329 ············{
330 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
331 ············}
332 ········}
333
334 ········void MergeChangeSet(ChangeSetContainer cs)
335 ········{
336 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
337 ············{
338 ················InternalMakePersistent(pc, false);
339 ············}
340 ············foreach(ObjectId oid in cs.DeletedObjects)
341 ············{
342 ················IPersistenceCapable pc2 = FindObject(oid);
343 ················Delete(pc2);
344 ············}
345 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
346 ············{
347 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
348 ················Class pcClass = GetClass(pc);
349 ················// Make sure, the object is loaded.
350 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
351 ····················LoadData(pc2);
352 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
353 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
354 ················DataRow row = entry.row;
355 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
356 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
357 ············}
358 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
359 ············{
360 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
361 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
362 ················Class pcClass = GetClass(parent);
363 ················Relation r = pcClass.FindRelation(rcr.RelationName);
364 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
365 ····················LoadRelation(parent, r, true);
366 ················if (rcr.IsAdded)
367 ················{
368 ····················InternalAddRelatedObject(parent, r, child, true);
369 ····················if (r.Multiplicity == RelationMultiplicity.Element)
370 ····················{
371 ························mappings.SetRelationField(parent, r.FieldName, child);
372 ····················}
373 ····················else
374 ····················{
375 ························IList l = mappings.GetRelationContainer(parent, r);
376 ························l.Add(child);
377 ····················}
378 ················}
379 ················else
380 ················{
381 ····················RemoveRelatedObject(parent, r.FieldName, child);
382 ····················if (r.Multiplicity == RelationMultiplicity.Element)
383 ····················{
384 ························mappings.SetRelationField(parent, r.FieldName, null);
385 ····················}
386 ····················else
387 ····················{
388 ························IList l = mappings.GetRelationContainer(parent, r);
389 ························try
390 ························{
391 ····························ObjectListManipulator.Remove(l, child);
392 ························}
393 ························catch
394 ························{
395 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
396 ························}
397 ····················}
398 ················}
399 ············}
400
401 ········}
402 ········#endregion
403
404 ········#region Implementation of IPersistenceManager
405
406 ········// Complete documentation can be found in IPersistenceManager
407
408
409 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
410 ········{
411 ············if (!cl.Oid.IsDependent)
412 ················return;
413 ············WriteForeignKeysToRow(pc, row);
414 ········}
415
416 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
417 ········{
418 ············// Object is now under control of the state manager
419 ············pc.NDOStateManager = sm;
420
421 ············Type pcType = pc.GetType();
422 ············Class pcClass = GetClass(pc);
423
424 ············// Create new object
425 ············DataTable dt = GetTable(pcType);
426 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
427
428 ············// In case of a Guid oid the value will be computed now.
429 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
430 ············{
431 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
432 ················{
433 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
434 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
435 ····················else
436 ························row[oidColumn.Name] = Guid.NewGuid();
437 ················}
438 ············}
439
440 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
441
442 ············// If the object is merged from an ObjectContainer, the id should be reused,
443 ············// if the id is client generated (not Autoincremented).
444 ············// In every other case, the oid is set to null, to force generating a new oid.
445 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
446 ············if ((object)pc.NDOObjectId != null)
447 ············{
448 ················bool hasAutoincrement = false;
449 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
450 ················{
451 ····················if (oidColumn.AutoIncremented)
452 ····················{
453 ························hasAutoincrement = true;
454 ························break;
455 ····················}
456 ················}
457 ················if (hasAutoincrement) // can't store existing id
458 ················{
459 ····················pc.NDOObjectId = null;
460 ····················fireIdGeneration = true;
461 ················}
462 ············}
463
464 ············// In case of a dependent class the oid has to be read from the fields according to the relations
465 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
466
467 ············if ((object)pc.NDOObjectId == null)
468 ············{
469 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
470 ············}
471
472 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
473 ············{
474 ················if (fireIdGeneration)
475 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
476 ················// At this place the oid might have been
477 ················// - deserialized (MergeObjectContainer)
478 ················// - created using NewObjectId
479 ················// - defined by the IdGenerationEvent
480
481 ················// At this point we have a valid oid.
482 ················// If the object has a field mapped to the oid we have
483 ················// to write back the oid to the field
484 ················int i = 0;
485 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
486 ················{
487 ····················if (oidColumn.FieldName != null)
488 ····················{
489 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
490 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
491 ····················}
492 ····················i++;
493 ················});
494
495
496
497 ················// Now write back the data into the row
498 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
499 ············}
500
501 ············
502 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
503 ············dt.Rows.Add(row);
504
505 ············cache.Register(pc);
506
507 ············// new object that has never been written to the DS
508 ············pc.NDOObjectState = NDOObjectState.Created;
509 ············// Mark all Relations as loaded
510 ············SetRelationState(pc);
511
512 ············if (checkRelations)
513 ············{
514 ················// Handle related objects:
515 ················foreach(Relation r in pcClass.Relations)
516 ················{
517 ····················if (r.Multiplicity == RelationMultiplicity.Element)
518 ····················{
519 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
520 ························if(child != null)
521 ························{
522 ····························AddRelatedObject(pc, r, child);
523 ························}
524 ····················}
525 ····················else
526 ····················{
527 ························IList list = mappings.GetRelationContainer(pc, r);
528 ························if(list != null)
529 ························{
530 ····························foreach(IPersistenceCapable relObj in list)
531 ····························{
532 ································if (relObj != null)
533 ····································AddRelatedObject(pc, r, relObj);
534 ····························}
535 ························}
536 ····················}
537 ················}
538 ············}
539
540 ············var relations··= CollectRelationStates(pc);
541 ············cache.Lock(pc, row, relations);
542 ········}
543
544
545 ········/// <summary>
546 ········/// Make an object persistent.
547 ········/// </summary>
548 ········/// <param name="o">the transient object that should be made persistent</param>
549 ········public void MakePersistent(object o)
550 ········{
551 ············IPersistenceCapable pc = CheckPc(o);
552
553 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
554 ············//Debug.Indent();
555
556 ············if (pc.NDOObjectState != NDOObjectState.Transient)
557 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
558
559 ············InternalMakePersistent(pc, true);
560
561 ········}
562
563
564
565 ········//········/// <summary>
566 ········//········/// Checks, if an object has a valid id, which was created by the database
567 ········//········/// </summary>
568 ········//········/// <param name="pc"></param>
569 ········//········/// <returns></returns>
570 ········//········private bool HasValidId(IPersistenceCapable pc)
571 ········//········{
572 ········//············if (this.IdGenerationEvent != null)
573 ········//················return true;
574 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
575 ········//········}
576
577
578 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
579 ········{
580 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
581 ············if(r.HasSubclasses)
582 ············{
583 ················// we don't support 1:n with foreign fields in subclasses because we would have to
584 ················// search for objects in all subclasses! Instead use a mapping table.
585 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
586 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
587 ············}
588
589 ············if (!makeRelObjPersistent)
590 ················MarkDirty(relObj);
591 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
592 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
593
594 ············if (relObjRow == null)
595 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
596
597 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
598
599 ············if (relObj.NDOLoadState.LostRowInfo == null)
600 ············{
601 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
602 ············}
603 ············else
604 ············{
605 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
606 ············}
607 ········}
608
609 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
610 ········{
611 ············switch(relObj.NDOObjectState)
612 ············{
613 ················case NDOObjectState.Persistent:
614 ····················MarkDirty(relObj);
615 ····················break;
616 ················case NDOObjectState.Hollow:
617 ····················LoadData(relObj);
618 ····················MarkDirty(relObj);
619 ····················break;
620 ············}
621
622 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
623 ············{
624 ················IPersistenceCapable newpc;
625 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
626 ················{
627 ····················if (newpc != pc)
628 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
629 ················}
630 ················else
631 ················{
632 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
633 ················}
634 ············}
635 ············else
636 ············{
637 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
638 ····················LoadRelation(relObj, r.ForeignRelation, true);
639 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
640 ················if(l == null)
641 ················{
642 ····················try
643 ····················{
644 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
645 ····················}
646 ····················catch
647 ····················{
648 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
649 ····················}
650 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
651 ················}
652 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
653 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
654 ················if (!ObjectListManipulator.Contains(l, pc))
655 ····················l.Add(pc);
656 ············}
657 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
658 ········}
659
660
661 ········/// <summary>
662 ········/// Add a related object to the specified object.
663 ········/// </summary>
664 ········/// <param name="pc">the parent object</param>
665 ········/// <param name="fieldName">the field name of the relation</param>
666 ········/// <param name="relObj">the related object that should be added</param>
667 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
668 ········{
669 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
670 ············Relation r = mappings.FindRelation(pc, fieldName);
671 ············AddRelatedObject(pc, r, relObj);
672 ········}
673
674 ········/// <summary>
675 ········/// Core functionality to add an object to a relation container or relation field.
676 ········/// </summary>
677 ········/// <param name="pc"></param>
678 ········/// <param name="r"></param>
679 ········/// <param name="relObj"></param>
680 ········/// <param name="isMerging"></param>
681 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
682 ········{
683 ············
684 ············// avoid recursion
685 ············if (!addLock.GetLock(relObj))
686 ················return;
687
688 ············try
689 ············{
690 ················//TODO: We need a relation management, which is independent of
691 ················//the state management of an object. Currently the relation
692 ················//lists or elements are cached for restore, if an object is marked dirty.
693 ················//Thus we have to mark dirty our parent object in any case at the moment.
694 ················MarkDirty(pc);
695
696 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
697 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
698 ················//The latter happens in CreateAddedObjectRow
699
700 ················Class relClass = GetClass(relObj);
701
702 ················if (r.Multiplicity == RelationMultiplicity.Element
703 ····················&& r.HasSubclasses
704 ····················&& r.MappingTable == null················
705 ····················&& !this.HasOwnerCreatedIds
706 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
707 ····················&& !relClass.HasGuidOid)
708 ················{
709 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
710 ························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.");
711 ····················if (r.Composition)
712 ························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.");
713 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
714 ························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." );
715 ················}
716
717 ················bool isDependent = relClass.Oid.IsDependent;
718
719 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
720 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
721
722 ················// Need to patch pc into the relation relObj->pc, because
723 ················// the oid is built on base of this information
724 ················if (isDependent)
725 ················{
726 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
727 ················}
728
729 ················if (r.Composition || isDependent)
730 ················{
731 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
732 ························MakePersistent(relObj);
733 ················}
734
735 ················if(r.MappingTable == null)
736 ················{
737 ····················if (r.Bidirectional)
738 ····················{
739 ························// This object hasn't been saved yet, so the key is wrong.
740 ························// Therefore, the child must be written twice to update the foreign key.
741 #if trace
742 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
743 ························if (r.Multiplicity == RelationMultiplicity.Element)
744 ····························sb.Append("1");
745 ························else
746 ····························sb.Append("n");
747 ························sb.Append(":");
748 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
749 ····························sb.Append("1");
750 ························else
751 ····························sb.Append("n");
752 ························sb.Append ("OwnCreatedOther");
753 ························sb.Append(relObj.NDOObjectState.ToString());
754 ························sb.Append(' ');
755
756 ························sb.Append(types[0].ToString());
757 ························sb.Append(' ');
758 ························sb.Append(types[1].ToString());
759 ························Debug.WriteLine(sb.ToString());
760 #endif
761 ························//························if (r.Multiplicity == RelationMultiplicity.Element
762 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
763 ························//························{
764 ························// Element means:
765 ························// pc is keyholder
766 ························// -> relObj is saved first
767 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
768 ························// Both are Created - use type sort order
769 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
770 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
771 ························{
772 ····························if (mappings.GetUpdateOrder(pc.GetType())
773 ································< mappings.GetUpdateOrder(relObj.GetType()))
774 ································createdDirectObjects.Add(pc);
775 ····························else
776 ································createdDirectObjects.Add( relObj );
777 ························}
778 ····················}
779 ····················if (r.Multiplicity == RelationMultiplicity.List)
780 ····················{
781 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
782 ····················}
783 ················}
784 ················else
785 ················{
786 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
787 ················}
788 ················if(r.Bidirectional)
789 ················{
790 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
791 ····················{
792 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
793 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
794 ····················}
795 ····················else if ( !addLock.IsLocked( pc ) )
796 ····················{
797 ························PatchForeignRelation( pc, r, relObj );
798 ····················}
799 ················}
800
801 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
802 ············}
803 ············finally
804 ············{
805 ················addLock.Unlock(relObj);
806 ················//Debug.Unindent();
807 ············}
808 ········}
809
810 ········/// <summary>
811 ········/// Returns an integer value which determines the rank of the given type in the update order list.
812 ········/// </summary>
813 ········/// <param name="t">The type to determine the update order.</param>
814 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
815 ········/// <remarks>
816 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
817 ········/// </remarks>
818 ········public int GetUpdateRank(Type t)
819 ········{
820 ············return mappings.GetUpdateOrder(t);
821 ········}
822
823 ········/// <summary>
824 ········/// Add a related object to the specified object.
825 ········/// </summary>
826 ········/// <param name="pc">the parent object</param>
827 ········/// <param name="r">the relation mapping info</param>
828 ········/// <param name="relObj">the related object that should be added</param>
829 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
830 ········{
831 ············//············string idstr;
832 ············//············if (relObj.NDOObjectId == null)
833 ············//················idstr = relObj.GetType().ToString();
834 ············//············else
835 ············//················idstr = relObj.NDOObjectId.Dump();
836 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
837 ············//Debug.Indent();
838
839 ············Class relClass = GetClass(relObj);
840 ············bool isDependent = relClass.Oid.IsDependent;
841
842 ············// Do some checks to guarantee that the assignment is correct
843 ············if(r.Composition)
844 ············{
845 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
846 ················{
847 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
848 ················}
849 ············}
850 ············else
851 ············{
852 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
853 ················{
854 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
855 ················}
856 ············}
857
858 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
859 ············{
860 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
861 ············}
862
863 ············InternalAddRelatedObject(pc, r, relObj, false);
864
865 ········}
866
867 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
868 ········{
869 ············// Need to patch pc into the relation relObj->pc, because
870 ············// the oid is built on base of this information
871 ············// The second relation has to be set before adding relObj
872 ············// to the relation list.
873 ············PatchForeignRelation(pc, r, relObj);
874 ············IPersistenceCapable parent;
875 ············foreach (Relation oidRelation in relClass.Oid.Relations)
876 ············{
877 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
878 ················if (parent == null)
879 ····················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.");
880 ················if (parent.NDOObjectState == NDOObjectState.Transient)
881 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
882
883 ············}
884 ········}
885
886
887 ········/// <summary>
888 ········/// Remove a related object from the specified object.
889 ········/// </summary>
890 ········/// <param name="pc">the parent object</param>
891 ········/// <param name="fieldName">Field name of the relation</param>
892 ········/// <param name="relObj">the related object that should be removed</param>
893 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
894 ········{
895 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
896 ············Relation r = mappings.FindRelation(pc, fieldName);
897 ············InternalRemoveRelatedObject(pc, r, relObj, true);
898 ········}
899
900 ········/// <summary>
901 ········/// Registers a listener which will be notified, if a new connection is opened.
902 ········/// </summary>
903 ········/// <param name="listener">Delegate of a listener function</param>
904 ········/// <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>
905 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
906 ········{
907 ············this.openConnectionListener = listener;
908 ········}
909
910 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
911 ········{
912 ············if (openConnectionListener != null)
913 ················return openConnectionListener(conn);
914 ············return conn.Name;
915 ········}
916
917
918 ········/*
919 ········doCommit should be:
920 ········
921 ····················Query····Save····Save(true)
922 ········Optimistic····1········1········0
923 ········Pessimistic····0········1········0
924 ············
925 ········Deferred Mode············
926 ····················Query····Save····Save(true)
927 ········Optimistic····0········1········0
928 ········Pessimistic····0········1········0
929 ········ */
930
931 ········internal void CheckEndTransaction(bool doCommit)
932 ········{
933 ············if (doCommit)
934 ············{
935 ················TransactionScope.Complete();
936 ············}
937 ········}
938
939 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
940 ········{
941 ············CheckTransaction(handler, this.GetClass(t).Connection);
942 ········}
943
944 ········/// <summary>
945 ········/// Each and every database operation has to be preceded by a call to this function.
946 ········/// </summary>
947 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
948 ········{
949 ············TransactionScope.CheckTransaction();
950 ············
951 ············if (handler.Connection == null)
952 ············{
953 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
954 ················{
955 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
956 ····················string connStr = this.OnNewConnection( ndoConn );
957 ····················var connection = p.NewConnection( connStr );
958 ····················if (connection == null)
959 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
960 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
961 ····················return connection;
962 ················} );
963 ············}
964
965 ············if (TransactionMode != TransactionMode.None)
966 ············{
967 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
968 ············}
969
970 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
971 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
972 ············{
973 ················handler.Connection.Open();
974 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
975 ············}
976 ········}
977
978 ········/// <summary>
979 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
980 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
981 ········/// This is possible if there is a listener for the CollisionEvent.
982 ········/// Else we throw an exception.
983 ········/// </summary>
984 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
985 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
986 ········{
987 ············DataRow row = ex.Row;
988 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
989 ················throw(ex);
990 ············if (row.RowState == DataRowState.Detached)
991 ················return;
992 ············foreach (Cache.Entry e in cache.LockedObjects)
993 ············{
994 ················if (e.row == row)
995 ················{
996 ····················CollisionEvent(e.pc);
997 ····················return;
998 ················}
999 ············}
1000 ············throw ex;
1001 ········}
1002
1003
1004 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1005 ········{
1006 ············Class cl = GetClass(pc);
1007 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1008 ············Dictionary<string,MemberInfo> persistentFields = null;
1009 ············if (etypes.Length > 0)
1010 ············{
1011 ················FieldMap fm = new FieldMap(cl);
1012 ················persistentFields = fm.PersistentFields;
1013 ············}
1014 ············foreach(string s in etypes)
1015 ············{
1016 ················try
1017 ················{
1018 ····················NDO.Mapping.Field f = cl.FindField(s);
1019 ····················if (f == null)
1020 ························continue;
1021 ····················object o = row[f.Column.Name];
1022 ····················string[] arr = s.Split('.');
1023 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1024 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1025 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1026 ····················// Hole das Embedded Object
1027 ····················object parentOb = parentFi.GetValue(pc);
1028
1029 ····················if (parentOb == null)
1030 ························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]));
1031
1032 ····················// Suche darin das Feld mit Namen Arr[1]
1033
1034 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1035 ····················Type childType = childFi.FieldType;
1036
1037 ····················// Don't initialize value types, if DBNull is stored in the field.
1038 ····················// Exception: DateTime and Guid.
1039 ····················if (o == DBNull.Value && childType.IsValueType
1040 ························&& childType != typeof(Guid)
1041 ························&& childType != typeof(DateTime))
1042 ························continue;
1043
1044 ····················if (childType == typeof(DateTime))
1045 ····················{
1046 ························if (o == DBNull.Value)
1047 ····························o = DateTime.MinValue;
1048 ····················}
1049 ····················if (childType.IsClass)
1050 ····················{
1051 ························if (o == DBNull.Value)
1052 ····························o = null;
1053 ····················}
1054
1055 ····················if (childType == typeof (Guid))
1056 ····················{
1057 ························if (o == DBNull.Value)
1058 ····························o = Guid.Empty;
1059 ························if (o is string)
1060 ························{
1061 ····························childFi.SetValue(parentOb, new Guid((string)o));
1062 ························}
1063 ························else if (o is Guid)
1064 ························{
1065 ····························childFi.SetValue(parentOb, o);
1066 ························}
1067 ························else if (o is byte[])
1068 ························{
1069 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1070 ························}
1071 ························else
1072 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1073 ····················}
1074 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1075 ····················{
1076 ························object childOb = childFi.GetValue(parentOb);
1077 ························FieldInfo valueFi = childType.GetField("value__");
1078 ························valueFi.SetValue(childOb, o);
1079 ························childFi.SetValue(parentOb, childOb);
1080 ····················}
1081 ····················else
1082 ····················{
1083 ························childFi.SetValue(parentOb, o);
1084 ····················}
1085 ················}
1086 ················catch (Exception ex)
1087 ················{
1088 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1089
1090 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1091 ················}
1092
1093 ············}
1094 ············
1095 ············try
1096 ············{
1097 ················if (cl.HasEncryptedFields)
1098 ················{
1099 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1100 ····················{
1101 ························string name = field.Column.Name;
1102 ························string s = (string) row[name];
1103 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1104 ························row[name] = es;
1105 ····················}
1106 ················}
1107 ················pc.NDORead(row, fieldNames, startIndex);
1108 ············}
1109 ············catch (Exception ex)
1110 ············{
1111 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1112 ····················+ ex.Message);
1113 ············}
1114 ········}
1115
1116 ········/// <summary>
1117 ········/// Executes a sql script to generate the database tables.
1118 ········/// The function will execute any sql statements in the script
1119 ········/// which are valid according to the
1120 ········/// rules of the underlying database. Result sets are ignored.
1121 ········/// </summary>
1122 ········/// <param name="scriptFile">The script file to execute.</param>
1123 ········/// <param name="conn">A connection object, containing the connection
1124 ········/// string to the database, which should be altered.</param>
1125 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1126 ········/// <remarks>
1127 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1128 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1129 ········/// Their message property will appear in the result array.
1130 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1131 ········/// </remarks>
1132 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1133 ········{
1134 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1135 ········}
1136
1137 ········/// <summary>
1138 ········/// Executes a sql script to generate the database tables.
1139 ········/// The function will execute any sql statements in the script
1140 ········/// which are valid according to the
1141 ········/// rules of the underlying database. Result sets are ignored.
1142 ········/// </summary>
1143 ········/// <param name="scriptFile">The script file to execute.</param>
1144 ········/// <param name="conn">A connection object, containing the connection
1145 ········/// string to the database, which should be altered.</param>
1146 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1147 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1148 ········/// <remarks>
1149 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1150 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1151 ········/// Their message property will appear in the result array.
1152 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1153 ········/// </remarks>
1154 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1155 ········{
1156 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1157 ············string s = sr.ReadToEnd();
1158 ············sr.Close();
1159 ············string[] arr = s.Split(';');
1160 ············string last = arr[arr.Length - 1];
1161 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1162 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1163 ············using (var handler = GetSqlPassThroughHandler())
1164 ············{
1165 ················int i = 0;
1166 ················string ok = "OK";
1167 ················foreach (string statement in arr)
1168 ················{
1169 ····················if (statement != null && statement.Trim() != string.Empty)
1170 ····················{
1171 ························try
1172 ························{
1173 ····························handler.Execute(statement);
1174 ····························result[i] = ok;
1175 ························}
1176 ························catch (Exception ex)
1177 ························{
1178 ····························result[i] = ex.Message;
1179 ························}
1180 ····················}
1181 ····················i++;
1182 ················}
1183 ················CheckEndTransaction(true);
1184 ············}
1185 ············return result;
1186 ········}
1187
1188 ········/// <summary>
1189 ········/// Executes a sql script to generate the database tables.
1190 ········/// The function will execute any sql statements in the script
1191 ········/// which are valid according to the
1192 ········/// rules of the underlying database. Result sets are ignored.
1193 ········/// </summary>
1194 ········/// <param name="scriptFile">The script file to execute.</param>
1195 ········/// <returns></returns>
1196 ········/// <remarks>
1197 ········/// This function takes the first Connection object in the Connections list
1198 ········/// of the Mapping file und executes the script using that connection.
1199 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1200 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1201 ········/// Their message property will appear in the result array.
1202 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1203 ········/// </remarks>
1204 ········public string[] BuildDatabase(string scriptFile)
1205 ········{
1206 ············if (!File.Exists(scriptFile))
1207 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1208 ············if (!this.mappings.Connections.Any())
1209 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1210 ············Connection conn = new Connection( this.mappings );
1211 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1212 ············conn.Name = OnNewConnection( originalConnection );
1213 ············conn.Type = originalConnection.Type;
1214 ············//Connection conn = (Connection) this.mappings.Connections[0];
1215 ············return BuildDatabase(scriptFile, conn);
1216 ········}
1217
1218 ········/// <summary>
1219 ········/// Executes a sql script to generate the database tables.
1220 ········/// The function will execute any sql statements in the script
1221 ········/// which are valid according to the
1222 ········/// rules of the underlying database. Result sets are ignored.
1223 ········/// </summary>
1224 ········/// <returns>
1225 ········/// A string array, containing the error messages produced by the statements
1226 ········/// contained in the script.
1227 ········/// </returns>
1228 ········/// <remarks>
1229 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1230 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1231 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1232 ········/// This function takes the first Connection object in the Connections list
1233 ········/// of the Mapping file und executes the script using that connection.
1234 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1235 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1236 ········/// Their message property will appear in the result array.
1237 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1238 ········/// </remarks>
1239 ········public string[] BuildDatabase()
1240 ········{
1241 ············Assembly ass = Assembly.GetEntryAssembly();
1242 ············if (ass == null)
1243 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1244 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1245 ············return BuildDatabase(file);
1246 ········}
1247
1248 ········/// <summary>
1249 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1250 ········/// </summary>
1251 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1252 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1253 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1254 ········{
1255 ············if (!this.mappings.Connections.Any())
1256 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1257 ············if (conn == null)
1258 ············{
1259 ················conn = new Connection( this.mappings );
1260 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1261 ················conn.Name = OnNewConnection( originalConnection );
1262 ················conn.Type = originalConnection.Type;
1263 ············}
1264
1265 ············return new SqlPassThroughHandler( this, conn );
1266 ········}
1267
1268 ········/// <summary>
1269 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1270 ········/// </summary>
1271 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1272 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1273 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1274 ········{
1275 ············if (!this.mappings.Connections.Any())
1276 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1277 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1278 ············if (conn == null)
1279 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1280 ············return GetSqlPassThroughHandler( conn );
1281 ········}
1282
1283 ········/// <summary>
1284 ········/// Executes a xml script to generate the database tables.
1285 ········/// The function will generate and execute sql statements to perform
1286 ········/// the changes described by the xml.
1287 ········/// </summary>
1288 ········/// <returns></returns>
1289 ········/// <remarks>
1290 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1291 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1292 ········/// This function takes the first Connection object in the Connections list
1293 ········/// of the Mapping file und executes the script using that connection.
1294 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1295 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1296 ········/// Their message property will appear in the result string array.
1297 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1298 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1299 ········/// </remarks>
1300 ········public string[] PerformSchemaTransitions()
1301 ········{
1302 ············Assembly ass = Assembly.GetEntryAssembly();
1303 ············if (ass == null)
1304 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1305 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1306 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1307 ············if (fileNames.Count == 0)
1308 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1309 ············if (fileNames.Count > 1)
1310 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1311 ············return PerformSchemaTransitions( fileNames[0] );
1312 ········}
1313
1314
1315 ········/// <summary>
1316 ········/// Executes a xml script to generate the database tables.
1317 ········/// The function will generate and execute sql statements to perform
1318 ········/// the changes described by the xml.
1319 ········/// </summary>
1320 ········/// <param name="scriptFile">The script file to execute.</param>
1321 ········/// <returns></returns>
1322 ········/// <remarks>
1323 ········/// This function takes the first Connection object in the Connections list
1324 ········/// of the Mapping file und executes the script using that connection.
1325 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1326 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1327 ········/// Their message property will appear in the result string array.
1328 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1329 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1330 ········/// </remarks>
1331 ········public string[] PerformSchemaTransitions(string scriptFile)
1332 ········{
1333 ············if (!File.Exists(scriptFile))
1334 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1335
1336 ············if (!this.mappings.Connections.Any())
1337 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1338 ············Connection conn = new Connection( mappings );
1339 ············Connection originalConnection = mappings.Connections.First();
1340 ············conn.Name = OnNewConnection( originalConnection );
1341 ············conn.Type = originalConnection.Type;
1342 ············return PerformSchemaTransitions(scriptFile, conn);
1343 ········}
1344
1345
1346 ········int CompareFileName( string fn1, string fn2)
1347 ········{
1348 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1349 ············Match match = regex.Match( fn1 );
1350 ············string v1 = match.Groups[1].Value;
1351 ············match = regex.Match( fn2 );
1352 ············string v2 = match.Groups[1].Value;
1353 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1354 ········}
1355
1356 ········string GetSchemaVersion(Connection ndoConn, string schemaName)
1357 ········{
1358 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1359 ············string version = "0.0";··// Initial value
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············using (var handler = GetSqlPassThroughHandler())
1362 ············{
1363 ················string[] TableNames = provider.GetTableNames( connection );
1364 ················if (TableNames.Any(t=>t=="NDOSchemaVersion"))
1365 ················{
1366 ····················string sql = "SELECT Version from NDOSchemaVersion WHERE SchemaName ";
1367 ····················if (schemaName == null)
1368 ························sql += "IS NULL;";
1369 ····················else
1370 ························sql += "LIKE '" + schemaName + "'";
1371 ····················using(IDataReader dr = handler.Execute(sql, true))
1372 ····················{
1373 ························if (dr.Read())
1374 ····························version = dr.GetString( 0 );
1375 ····················}
1376 ················}
1377 ················else
1378 ················{
1379 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( NDOProviderFactory.Instance.Generators[ndoConn.Type], this.mappings );
1380 ····················string transition = @"<NdoSchemaTransition>
1381 ····<CreateTable name=""NDOSchemaVersion"">
1382 ······<CreateColumn name=""SchemaName"" type=""System.String,mscorlib"" allowNull=""True"" />
1383 ······<CreateColumn name=""Version"" type=""System.String,mscorlib"" size=""50"" />
1384 ····</CreateTable>
1385 </NdoSchemaTransition>";
1386 ····················XElement transitionElement = XElement.Parse(transition);
1387
1388 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1389 ····················handler.Execute(sql);
1390 ····················sql = String.Format( "INSERT INTO NDOSchemaVersion([SchemaName],[Version]) VALUES({0},'0')", schemaName == null ? "NULL" : provider.GetSqlLiteral( schemaName ) );
1391 ····················handler.Execute( sql );
1392 ····················handler.CommitTransaction();
1393 ················}
1394 ············}
1395
1396 ············return version;
1397 ········}
1398
1399 ········/// <summary>
1400 ········/// Executes a xml script to generate the database tables.
1401 ········/// The function will generate and execute sql statements to perform
1402 ········/// the changes described by the xml.
1403 ········/// </summary>
1404 ········/// <param name="scriptFile">The xml script file.</param>
1405 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1406 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1407 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1408 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1409 ········{
1410 ············string schemaName = null;
1411 ············// Gespeicherte Version ermitteln.
1412 ············XElement transitionElements = XElement.Load( scriptFile );
1413 ············if (transitionElements.Attribute( "schemaName" ) != null)
1414 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1415 ············Version version = new Version( GetSchemaVersion( ndoConn, schemaName ) );
1416 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( NDOProviderFactory.Instance.Generators[ndoConn.Type], this.mappings );
1417 ············MemoryStream ms = new MemoryStream();
1418 ············StreamWriter sw = new StreamWriter(ms, System.Text.Encoding.UTF8);
1419 ············bool hasChanges = false;
1420
1421 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition").Where(e=>new Version(e.Attribute("schemaVersion").Value).CompareTo(version) > 0))
1422 ············{
1423 ················hasChanges = true;
1424 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1425 ············}
1426
1427 ············if (!hasChanges)
1428 ················return new string[] { };
1429
1430 ············sw.Write( "UPDATE NDOSchemaVersion SET Version = '" );
1431 ············sw.Write( transitionElements.Attribute( "schemaVersion" ).Value );
1432 ············sw.Write( "' WHERE SchemaName " );
1433 ············if (schemaName == null)
1434 ················sw.WriteLine( "IS NULL;" );
1435 ············else
1436 ················sw.WriteLine( "LIKE '" + schemaName + "'" );············
1437
1438 ············sw.Flush();
1439 ············ms.Position = 0L;
1440
1441 ············StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);
1442 ············string s = sr.ReadToEnd();
1443 ············sr.Close();
1444
1445 ············return InternalPerformSchemaTransitions( ndoConn, s );
1446 ········}
1447
1448 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1449 ········{
1450 ············string[] arr = sql.Split( ';' );
1451 ············string last = arr[arr.Length - 1];
1452 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1453 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1454 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1455 ············//TransactionInfo ti = transactionTable[conn];
1456 ············//IDbConnection cn = ti.Connection;
1457 ············IDbConnection cn = provider.NewConnection( ndoConn.Name );
1458 ············cn.Open();
1459 ············IDbCommand cmd = provider.NewSqlCommand( cn );
1460 ············int i = 0;
1461 ············string ok = "OK";
1462 ············foreach (string statement in arr)
1463 ············{
1464 ················if (statement != null && statement.Trim() != string.Empty)
1465 ················{
1466 ····················try
1467 ····················{
1468 ························cmd.CommandText = statement;
1469 ························cmd.ExecuteNonQuery();
1470 ························result[i] = ok;
1471 ····················}
1472 ····················catch (Exception ex)
1473 ····················{
1474 ························result[i] = ex.Message;
1475 ····················}
1476 ················}
1477 ················i++;
1478 ············}
1479 ············cn.Close();
1480 ············return result;
1481 ········}
1482 ········
1483 ········/// <summary>
1484 ········/// Transfers Data from the object to the DataRow
1485 ········/// </summary>
1486 ········/// <param name="pc"></param>
1487 ········/// <param name="row"></param>
1488 ········/// <param name="fieldNames"></param>
1489 ········/// <param name="startIndex"></param>
1490 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1491 ········{
1492 ············Class cl = GetClass( pc );
1493 ············try
1494 ············{
1495 ················pc.NDOWrite(row, fieldNames, startIndex);
1496 ················if (cl.HasEncryptedFields)
1497 ················{
1498 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1499 ····················{
1500 ························string name = field.Column.Name;
1501 ························string s = (string) row[name];
1502 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1503 ························row[name] = es;
1504 ····················}
1505 ················}
1506 ············}
1507 ············catch (Exception ex)
1508 ············{
1509 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1510 ····················+ ex.Message);
1511 ············}
1512
1513 ············if (cl.TypeNameColumn != null)
1514 ············{
1515 ················Type t = pc.GetType();
1516 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1517 ············}
1518
1519 ············var etypes = cl.EmbeddedTypes;
1520 ············foreach(string s in etypes)
1521 ············{
1522 ················try
1523 ················{
1524 ····················NDO.Mapping.Field f = cl.FindField(s);
1525 ····················if (f == null)
1526 ························continue;
1527 ····················string[] arr = s.Split('.');
1528 ····················// Suche Feld mit Namen arr[0] als object
1529 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1530 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1531 ····················Object parentOb = parentFi.GetValue(pc);
1532 ····················if (parentOb == null)
1533 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1534 ····················// Suche darin das Feld mit Namen Arr[1]
1535 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1536 ····················object o = childFi.GetValue(parentOb);
1537 ····················if (o == null
1538 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1539 ························|| o is Guid && (Guid) o == Guid.Empty)
1540 ························o = DBNull.Value;
1541 ····················row[f.Column.Name] = o;
1542 ················}
1543 ················catch (Exception ex)
1544 ················{
1545 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1546
1547 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1548 ················}
1549 ············}
1550 ········}
1551
1552 ········/// <summary>
1553 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1554 ········/// </summary>
1555 ········/// <param name="o">The parent object.</param>
1556 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1557 ········public virtual void LoadField(object o, int fieldOrdinal)
1558 ········{
1559 ············IPersistenceCapable pc = CheckPc(o);
1560 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1561 ············{
1562 ················LoadState ls = pc.NDOLoadState;
1563 ················if (ls.FieldLoadState != null)
1564 ················{
1565 ····················if (ls.FieldLoadState[fieldOrdinal])
1566 ························return;
1567 ················}
1568 ················else
1569 ················{
1570 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1571 ················}
1572 ················LoadData(o);
1573 ········}
1574 ········}
1575
1576 #pragma warning disable 419
1577 ········/// <summary>
1578 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1579 ········/// </summary>
1580 ········/// <param name="o">The hollow object.</param>
1581 ········/// <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>
1582 #pragma warning restore 419
1583 ········public virtual void LoadData( object o )
1584 ········{
1585 ············IPersistenceCapable pc = CheckPc(o);
1586 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1587 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1588 ················return;
1589 ············Class cl = GetClass(pc);
1590 ············IQuery q;
1591 ············q = CreateOidQuery(pc, cl);
1592 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1593
1594 ············var objects = q.Execute();
1595 ············var count = objects.Count;
1596
1597 ············if (count > 1)
1598 ············{
1599 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1600 ············}
1601 ············else if (count == 0)
1602 ············{
1603 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1604 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1605 ············}
1606 ········}
1607
1608 ········/// <summary>
1609 ········/// Creates a new IQuery object for the given type
1610 ········/// </summary>
1611 ········/// <param name="t"></param>
1612 ········/// <param name="oql"></param>
1613 ········/// <param name="hollow"></param>
1614 ········/// <param name="queryLanguage"></param>
1615 ········/// <returns></returns>
1616 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1617 ········{
1618 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1619 ············Type qt = template.MakeGenericType( t );
1620 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1621 ········}
1622
1623 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1624 ········{
1625 ············ArrayList parameters = new ArrayList();
1626 ············string oql = "oid = {0}";
1627 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1628 ············q.Parameters.Add( pc.NDOObjectId );
1629 ············q.AllowSubclasses = false;
1630 ············return q;
1631 ········}
1632
1633 ········/// <summary>
1634 ········/// Mark the object dirty. The current state is
1635 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1636 ········/// object is locked in the cache.
1637 ········/// </summary>
1638 ········/// <param name="pc"></param>
1639 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1640 ········{
1641 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1642 ················return;
1643 ············SaveObjectState(pc);
1644 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1645 ········}
1646
1647 ········/// <summary>
1648 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1649 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1650 ········/// This is done, to allow easy rollback later. Also, the
1651 ········/// object is locked in the cache.
1652 ········/// </summary>
1653 ········/// <param name="pc"></param>
1654 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1655 ········{
1656 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1657
1658 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1659 ············{
1660 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1661 ············}
1662
1663 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1664 ················LoadData(pc);
1665
1666 ············// state is either (Created), Persistent, PersistentDirty
1667 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1668 ············{
1669 ················MarkDirty(pc);
1670 ············}
1671 ········}
1672
1673
1674
1675 ········/// <summary>
1676 ········/// Save current object state in DS and lock the object in the cache.
1677 ········/// The saved state can be used later to retrieve the original object value if the
1678 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1679 ········/// </summary>
1680 ········/// <param name="pc">The object that should be saved</param>
1681 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1682 ········/// <remarks>
1683 ········/// In a data row there are the following things:
1684 ········/// Item································Responsible for writing
1685 ········/// State (own, inherited, embedded)····WriteObject
1686 ········/// TimeStamp····························NDOPersistenceHandler
1687 ········/// Oid····································WriteId
1688 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1689 ········/// </remarks>
1690 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1691 ········{
1692 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1693 ············
1694 ············DataTable table = GetTable(pc);
1695 ············DataRow row = table.NewRow();
1696 ············Class cl = GetClass(pc);
1697 ············WriteObject(pc, row, cl.ColumnNames, 0);
1698 ············WriteIdToRow(pc, row);
1699 ············if (!isDeleting)
1700 ················WriteLostForeignKeysToRow(cl, pc, row);
1701 ············table.Rows.Add(row);
1702 ············row.AcceptChanges();
1703 ············
1704 ············var relations = CollectRelationStates(pc);
1705 ············cache.Lock(pc, row, relations);
1706 ········}
1707
1708 ········private void SaveFakeRow(IPersistenceCapable pc)
1709 ········{
1710 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1711 ············
1712 ············DataTable table = GetTable(pc);
1713 ············DataRow row = table.NewRow();
1714 ············Class pcClass = GetClass(pc);
1715 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1716 ············Class cl = GetClass(pc);
1717 ············//WriteObject(pc, row, cl.FieldNames, 0);
1718 ············WriteIdToRow(pc, row);
1719 ············table.Rows.Add(row);
1720 ············row.AcceptChanges();
1721 ············
1722 ············cache.Lock(pc, row, null);
1723 ········}
1724
1725 ········/// <summary>
1726 ········/// This defines one column of the row, in which we use the
1727 ········/// ColumnError property to determine, if the row is a fake row.
1728 ········/// </summary>
1729 ········/// <param name="pcClass"></param>
1730 ········/// <returns></returns>
1731 ········private string GetFakeRowOidColumnName(Class pcClass)
1732 ········{
1733 ············// In case of several OidColumns the first column defined in the mapping
1734 ············// will be the one, holding the fake row info.
1735 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1736 ········}
1737
1738 ········private bool IsFakeRow(Class cl, DataRow row)
1739 ········{
1740 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1741 ········}
1742
1743 ········/// <summary>
1744 ········/// Make a list of objects persistent.
1745 ········/// </summary>
1746 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1747 ········public void MakePersistent(System.Collections.IList list)
1748 ········{
1749 ············foreach (IPersistenceCapable pc in list)
1750 ············{
1751 ················MakePersistent(pc);
1752 ············}
1753 ········}
1754
1755 ········/// <summary>
1756 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1757 ········/// not duplicated.
1758 ········/// </summary>
1759 ········/// <param name="pc">the parent object of all relations</param>
1760 ········/// <returns></returns>
1761 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1762 ········{
1763 ············// Save state of relations
1764 ············Class c = GetClass(pc);
1765 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1766 ············foreach(Relation r in c.Relations)
1767 ············{
1768 ················if (r.Multiplicity == RelationMultiplicity.Element)
1769 ················{
1770 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1771 ················}
1772 ················else
1773 ················{
1774 ····················IList l = mappings.GetRelationContainer(pc, r);
1775 ····················if(l != null)
1776 ····················{
1777 ························l = (IList) ListCloner.CloneList(l);
1778 ····················}
1779 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1780 ················}
1781 ············}
1782
1783 ············return relations;
1784 ········}
1785
1786
1787 ········/// <summary>
1788 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1789 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1790 ········/// the list had at the beginning of the transaction.
1791 ········/// </summary>
1792 ········/// <param name="pc"></param>
1793 ········/// <param name="relations"></param>
1794 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1795 ········{
1796 ············Class c = GetClass(pc);
1797
1798 ············foreach(var entry in relations)
1799 ············{
1800 ················var r = entry.Key;
1801 ················if (r.Multiplicity == RelationMultiplicity.Element)
1802 ················{
1803 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1804 ················}
1805 ················else
1806 ················{
1807 ····················if (pc.NDOGetLoadState(r.Ordinal))
1808 ····················{
1809 ························// Help GC by clearing lists
1810 ························IList l = mappings.GetRelationContainer(pc, r);
1811 ························if(l != null)
1812 ························{
1813 ····························l.Clear();
1814 ························}
1815 ························// Restore relation
1816 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1817 ····················}
1818 ················}
1819 ············}
1820 ········}
1821
1822
1823 ········/// <summary>
1824 ········/// Generates a query for related objects without mapping table.
1825 ········/// Note: this function can't be called in polymorphic scenarios,
1826 ········/// since they need a mapping table.
1827 ········/// </summary>
1828 ········/// <returns></returns>
1829 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1830 ········{
1831 ············// At this point of execution we know,
1832 ············// that the target type is not polymorphic and is not 1:1.
1833
1834 ············// We can't fetch these objects with an NDOql query
1835 ············// since this would require a relation in the opposite direction
1836
1837 ············IList relatedObjects;
1838 ············if (l != null)
1839 ················relatedObjects = l;
1840 ············else
1841 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1842
1843 ············Type t = r.ReferencedType;
1844 ············Class cl = GetClass( t );
1845 ············var provider = cl.Provider;
1846
1847 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1848 ············var relClass = GetClass( r.ReferencedType );
1849 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1850 ············sb.Append( " WHERE " );
1851 ············int i = 0;
1852 ············List<object> parameters = new List<object>();
1853 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1854 ·············· {
1855 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1856 ·················· sb.Append( " = {" );
1857 ·················· sb.Append(i);
1858 ·················· sb.Append( '}' );
1859 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1860 ·················· if (!isLastElement)
1861 ······················ sb.Append( " AND " );
1862 ·················· i++;
1863 ·············· } );
1864
1865 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1866 ············{
1867 ················sb.Append( " AND " );
1868 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1869 ················sb.Append( " = " );
1870 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1871 ············}
1872
1873 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1874
1875 ············foreach (var p in parameters)
1876 ············{
1877 ················q.Parameters.Add( p );
1878 ············}
1879
1880 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1881
1882 ············IList l2 = q.Execute();
1883
1884 ············foreach (object o in l2)
1885 ················relatedObjects.Add( o );
1886
1887 ············return relatedObjects;
1888 ········}
1889
1890
1891 ········/// <summary>
1892 ········/// Resolves an relation. The loaded objects will be hollow.
1893 ········/// </summary>
1894 ········/// <param name="o">The parent object.</param>
1895 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1896 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1897 ········/// <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>
1898 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1899 ········{
1900 ············IPersistenceCapable pc = CheckPc(o);
1901 ············LoadRelationInternal(pc, fieldName, hollow);
1902 ········}
1903
1904 ········
1905
1906 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1907 ········{
1908 ············IList result = null;
1909
1910 ············if (pc.NDOObjectState == NDOObjectState.Created)
1911 ················return null;
1912
1913 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1914 ················LoadData(pc);
1915
1916 ············if(r.MappingTable == null)
1917 ············{
1918 ················// 1:1 are loaded with LoadData
1919 ················if (r.Multiplicity == RelationMultiplicity.List)
1920 ················{
1921 ····················// Help GC by clearing lists
1922 ····················IList l = mappings.GetRelationContainer(pc, r);
1923 ····················if(l != null)
1924 ························l.Clear();
1925 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1926 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1927 ····················result = relatedObjects;
1928 ················}
1929 ············}
1930 ············else
1931 ············{
1932 ················DataTable dt = null;
1933
1934 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1935 ················{
1936 ····················handler.VerboseMode = VerboseMode;
1937 ····················handler.LogAdapter = LogAdapter;
1938 ····················CheckTransaction( handler, r.MappingTable.Connection );
1939 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1940 ················}
1941
1942 ················IList relatedObjects;
1943 ················if(r.Multiplicity == RelationMultiplicity.Element)
1944 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1945 ················else
1946 ················{
1947 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1948 ····················if(relatedObjects != null)
1949 ························relatedObjects.Clear();··// Objects will be reread
1950 ····················else
1951 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1952 ················}
1953 ····················
1954 ················foreach(DataRow objRow in dt.Rows)
1955 ················{
1956 ····················Type relType;
1957
1958 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1959 ····················{
1960 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1961 ························if (typeCodeObj is System.DBNull)
1962 ····························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() ) );
1963 ························relType = typeManager[(int)typeCodeObj];
1964 ························if (relType == null)
1965 ····························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));
1966 ····················}························
1967 ····················else
1968 ····················{
1969 ························relType = r.ReferencedType;
1970 ····················}
1971
1972 ····················//TODO: Generic Types: Exctract the type description from the type name column
1973 ····················if (relType.IsGenericTypeDefinition)
1974 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1975 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1976 ····················IPersistenceCapable relObj = FindObject(id);
1977 ····················relatedObjects.Add(relObj);
1978 ················}····
1979 ················if (r.Multiplicity == RelationMultiplicity.Element)
1980 ················{
1981 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
1982 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
1983 ················}
1984 ················else
1985 ················{
1986 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1987 ····················result = relatedObjects;
1988 ················}
1989 ············}
1990 ············// Mark relation as loaded
1991 ············pc.NDOSetLoadState(r.Ordinal, true);
1992 ············return result;
1993 ········}
1994
1995 ········/// <summary>
1996 ········/// Loads elements of a relation
1997 ········/// </summary>
1998 ········/// <param name="pc">The object which needs to load the relation</param>
1999 ········/// <param name="relationName">The name of the relation</param>
2000 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2001 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2002 ········{
2003 ············if (pc.NDOObjectState == NDOObjectState.Created)
2004 ················return null;
2005 ············Class cl = GetClass(pc);
2006
2007 ············Relation r = cl.FindRelation(relationName);
2008
2009 ············if ( r == null )
2010 ················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 ) );
2011
2012 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2013 ················return null;
2014
2015 ············return LoadRelation(pc, r, hollow);
2016 ········}
2017
2018 ········/// <summary>
2019 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2020 ········/// a list of objects that has been read from the DB.
2021 ········/// </summary>
2022 ········/// <param name="pc">The parent object of the relations</param>
2023 ········/// <param name="row">A data row containing the state of the object</param>
2024 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2025 ········{
2026 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2027 ············Class cl = GetClass(pc);
2028 ············foreach(Relation r in cl.Relations)
2029 ············{
2030 ················if(r.MappingTable == null)
2031 ················{
2032 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2033 ····················{
2034 ························bool isNull = false;
2035 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2036 ························{
2037 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2038 ························}
2039 ························if (isNull)
2040 ························{
2041 ····························mappings.SetRelationField(pc, r.FieldName, null);
2042 ························}
2043 ························else
2044 ························{
2045 ····························Type relType;
2046 ····························if (r.HasSubclasses)
2047 ····························{
2048 ································object o = row[r.ForeignKeyTypeColumnName];
2049 ································if (o == DBNull.Value)
2050 ····································throw new NDOException(75, String.Format(
2051 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2052 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2053 ································relType = typeManager[(int)o];
2054 ····························}
2055 ····························else
2056 ····························{
2057 ································relType = r.ReferencedType;
2058 ····························}
2059 ····························if (relType == null)
2060 ····························{
2061 ································throw new NDOException(75, String.Format(
2062 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2063 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2064 ····························}
2065 ····
2066 ····························int count = r.ForeignKeyColumns.Count();
2067 ····························object[] keydata = new object[count];
2068 ····························int i = 0;
2069 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2070 ····························{
2071 ································keydata[i++] = row[fkColumn.Name];
2072 ····························}
2073
2074 ····························Type oidType = relType;
2075 ····························if (oidType.IsGenericTypeDefinition)
2076 ································oidType = mappings.GetRelationFieldType(r);
2077
2078 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2079 ····························if(childOid.IsValid())
2080 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2081 ····························else
2082 ································mappings.SetRelationField(pc, r.FieldName, null);
2083
2084 ························}
2085 ························pc.NDOSetLoadState(r.Ordinal, true);
2086 ····················}
2087 ················}
2088 ············}
2089 ········}
2090
2091 ········
2092 ········/// <summary>
2093 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2094 ········/// </summary>
2095 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2096 ········/// <param name="t">The type of the object, the id will belong to.</param>
2097 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2098 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2099 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2100 ········{
2101 ············return new ObjectId(oid.Id, t);
2102 ········}
2103
2104 ········/*
2105 ········/// <summary>
2106 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2107 ········/// </summary>
2108 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2109 ········/// <param name="t">The type of the object.</param>
2110 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2111 ········/// <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>
2112 ········public ObjectId NewObjectId(object keyData, Type t)
2113 ········{
2114 ············if (keyData == null || keyData == DBNull.Value)
2115 ················return ObjectId.InvalidId;
2116
2117 ············Class cl =··GetClass(t);············
2118 ············
2119 ············if (cl.Oid.FieldType == typeof(int))
2120 ················return new ObjectId(new Int32Key(t, (int)keyData));
2121 ············else if (cl.Oid.FieldType == typeof(string))
2122 ················return new ObjectId(new StringKey(t, (String) keyData));
2123 ············else if (cl.Oid.FieldType == typeof(Guid))
2124 ················if (keyData is string)
2125 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2126 ················else
2127 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2128 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2129 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2130 ············else
2131 ················return ObjectId.InvalidId;
2132 ········}
2133 ········*/
2134
2135
2136 ········/*
2137 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2138 ····················{
2139 ························// The column, which hold the oid gets overwritten, if
2140 ························// Oid.FieldName contains a value.
2141 */
2142 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2143 ········{
2144 ············Class cl = GetClass(pc);
2145
2146 ············if (cl.Oid.IsDependent)
2147 ················return;
2148
2149 ············Key key = pc.NDOObjectId.Id;
2150
2151 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2152 ············{
2153 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2154 ················if (oidColumn.FieldName != null)
2155 ················{
2156 ····················row[oidColumn.Name] = key[i];
2157 ················}
2158 ············}
2159 ········}
2160
2161 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2162 ········{
2163 ············NDO.Mapping.Class cl = GetClass(pc);
2164 ············ObjectId oid = pc.NDOObjectId;
2165
2166 ············if (cl.TimeStampColumn != null)
2167 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2168
2169 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2170 ················return;
2171
2172 ············Key key = oid.Id;
2173
2174 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2175 ············{
2176 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2177 ················row[oidColumn.Name] = key[i];
2178 ············}
2179 ········}
2180
2181 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2182 ········{
2183 ············ObjectId oid = pc.NDOObjectId;
2184 ············NDO.Mapping.Class cl = GetClass(pc);
2185
2186 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2187 ················return;
2188
2189 ············Key key = oid.Id;
2190
2191 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2192 ············{
2193 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2194 ················object o = row[oidColumn.Name];
2195 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2196 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2197 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2198 ····················key[i] = new Guid((string)o);
2199 ················else
2200 ····················key[i] = o;
2201 ············}
2202
2203 ········}
2204
2205 ········private void ReadId (Cache.Entry e)
2206 ········{
2207 ············ReadIdFromRow(e.pc, e.row);
2208 ········}
2209
2210 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2211 ········{
2212 ············WriteObject(pc, row, fieldNames, 0);
2213 ········}
2214
2215 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2216 ········{
2217 ············if (cl.TimeStampColumn == null)
2218 ················return;
2219 ············object col = row[cl.TimeStampColumn];
2220 ············if (col is String)
2221 ················pc.NDOTimeStamp = new Guid(col.ToString());
2222 ············else
2223 ················pc.NDOTimeStamp = (Guid) col;
2224 ········}
2225
2226
2227
2228 ········private void ReadTimeStamp(Cache.Entry e)
2229 ········{
2230 ············IPersistenceCapable pc = e.pc;
2231 ············NDO.Mapping.Class cl = GetClass(pc);
2232 ············Debug.Assert(!IsFakeRow(cl, e.row));
2233 ············if (cl.TimeStampColumn == null)
2234 ················return;
2235 ············if (e.row[cl.TimeStampColumn] is String)
2236 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2237 ············else
2238 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2239 ········}
2240
2241 ········/// <summary>
2242 ········/// Determines, if any objects are new, changed or deleted.
2243 ········/// </summary>
2244 ········public virtual bool HasChanges
2245 ········{
2246 ············get
2247 ············{
2248 ················return cache.LockedObjects.Count > 0;
2249 ············}
2250 ········}
2251
2252 ········/// <summary>
2253 ········/// Do the update for all rows in the ds.
2254 ········/// </summary>
2255 ········/// <param name="types">Types with changes.</param>
2256 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2257 ········/// <remarks>
2258 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2259 ········/// </remarks>
2260 ········private void UpdateTypes(IList types, bool delete)
2261 ········{
2262 ············foreach(Type t in types)
2263 ············{
2264 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2265 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2266 ················{
2267 ····················handler.VerboseMode = VerboseMode;
2268 ····················handler.LogAdapter = LogAdapter;
2269 ····················CheckTransaction( handler, t );
2270 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2271 ····················handler.ConcurrencyError += ceh;
2272 ····················try
2273 ····················{
2274 ························DataTable dt = GetTable(t);
2275 ························if (delete)
2276 ····························handler.UpdateDeletedObjects( dt );
2277 ························else
2278 ····························handler.Update( dt );
2279 ····················}
2280 ····················finally
2281 ····················{
2282 ························handler.ConcurrencyError -= ceh;
2283 ····················}
2284 ················}
2285 ············}
2286 ········}
2287
2288 ········internal void UpdateCreatedMappingTableEntries()
2289 ········{
2290 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2291 ············{
2292 ················if (!e.DeleteEntry)
2293 ····················WriteMappingTableEntry(e);
2294 ············}
2295 ············// Now update all mapping tables
2296 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2297 ············{
2298 ················handler.LogAdapter = this.LogAdapter;
2299 ················handler.VerboseMode = this.VerboseMode;
2300 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2301 ················handler.Update(ds);
2302 ············}
2303 ········}
2304
2305 ········internal void UpdateDeletedMappingTableEntries()
2306 ········{
2307 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2308 ············{
2309 ················if (e.DeleteEntry)
2310 ····················WriteMappingTableEntry(e);
2311 ············}
2312 ············// Now update all mapping tables
2313 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2314 ············{
2315 ················handler.LogAdapter = this.LogAdapter;
2316 ················handler.VerboseMode = this.VerboseMode;
2317 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2318 ················handler.Update(ds);
2319 ············}
2320 ········}
2321
2322 ········/// <summary>
2323 ········/// Save all changed object into the DataSet and update the DB.
2324 ········/// When a newly created object is written to DB, the key might change. Therefore,
2325 ········/// the id is updated and the object is removed and re-inserted into the cache.
2326 ········/// </summary>
2327 ········public virtual void Save(bool deferCommit = false)
2328 ········{
2329 ············this.DeferredMode = deferCommit;
2330 ············var htOnSaving = new HashSet<ObjectId>();
2331 ············for(;;)
2332 ············{
2333 ················// We need to work on a copy of the locked objects list,
2334 ················// since the handlers might add more objects to the cache
2335 ················var lockedObjects = cache.LockedObjects.ToList();
2336 ················int count = lockedObjects.Count;
2337 ················foreach(Cache.Entry e in lockedObjects)
2338 ················{
2339 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2340 ····················{
2341 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2342 ························if (ipn != null)
2343 ························{
2344 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2345 ····························{
2346 ································ipn.OnSaving();
2347 ································htOnSaving.Add(e.pc.NDOObjectId);
2348 ····························}
2349 ························}
2350 ····················}
2351 ················}
2352 ················// The system is stable, if the count doesn't change
2353 ················if (cache.LockedObjects.Count == count)
2354 ····················break;
2355 ············}
2356
2357 ············if (this.OnSavingEvent != null)
2358 ············{
2359 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2360 ················foreach(Cache.Entry e in cache.LockedObjects)
2361 ····················onSavingObjects.Add(e.pc);
2362 ················OnSavingEvent(onSavingObjects);
2363 ············}
2364
2365 ············List<Type> types = new List<Type>();
2366 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2367 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2368 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2369 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2370 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2371
2372 ············// Save current state in DataSet
2373 ············foreach (Cache.Entry e in cache.LockedObjects)
2374 ············{
2375 ················Type objType = e.pc.GetType();
2376
2377 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2378 ····················objType = objType.GetGenericTypeDefinition();
2379
2380 ················Class cl = GetClass(e.pc);
2381 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2382 ················if(!types.Contains(objType))
2383 ················{
2384 ····················//Debug.WriteLine("Added··type " + objType.Name);
2385 ····················types.Add(objType);
2386 ················}
2387 ················NDOObjectState objectState = e.pc.NDOObjectState;
2388 ················if(objectState == NDOObjectState.Deleted)
2389 ················{
2390 ····················deletedObjects.Add(e.pc);
2391 ················}
2392 ················else if(objectState == NDOObjectState.Created)
2393 ················{
2394 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2395 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2396 ····················WriteForeignKeysToRow(e.pc, e.row);
2397 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2398 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2399 ····················//····················i = 0;
2400 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2401 ····················//····················{
2402 ····················//························rows[i++] = e2.row;
2403 ····················//····················}
2404 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2405 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2406
2407 ····················addedCacheEntries.Add(e);
2408 ····················addedObjects.Add( e.pc );
2409 ····················//····················objectState = NDOObjectState.Persistent;
2410 ················}
2411 ················else
2412 ················{
2413 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2414 ························changedObjects.Add( e.pc );
2415 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2416 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2417 ················}
2418 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2419 ················{
2420 ····················hollowModeObjects.Add(e.pc);
2421 ················}
2422 ············}
2423
2424 ············// Before we delete any db rows, we have to make sure, to delete mapping
2425 ············// table entries first, which might have relations to the db rows to be deleted
2426 ············UpdateDeletedMappingTableEntries();
2427
2428 ············// Update DB
2429 ············if (ds.HasChanges())
2430 ············{
2431
2432 ················// We need the reversed update order for deletions.
2433 ················types.Sort( ( t1, t2 ) =>
2434 ················{
2435 ····················int i1 = mappings.GetUpdateOrder( t1 );
2436 ····················int i2 = mappings.GetUpdateOrder( t2 );
2437 ····················if (i1 < i2)
2438 ····················{
2439 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2440 ····························i1 += 100000;
2441 ····················}
2442 ····················else
2443 ····················{
2444 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2445 ····························i2 += 100000;
2446 ····················}
2447 ····················return i2 - i1;
2448 ················} );
2449
2450 ················// Delete records first
2451
2452 ················UpdateTypes(types, true);
2453
2454 ················// Now do all other updates in correct order.
2455 ················types.Reverse();
2456
2457 ················UpdateTypes(types, false);
2458 ················
2459 ················ds.AcceptChanges();
2460 ················if(createdDirectObjects.Count > 0)
2461 ················{
2462 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2463 ····················// They must be written again to store the correct foreign keys.
2464 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2465 ····················{
2466 ························Class cl = GetClass(pc);
2467 ························DataRow r = this.cache.GetDataRow(pc);
2468 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2469 ························object o = r[fakeColumnName];
2470 ························r[fakeColumnName] = o;
2471 ····················}
2472
2473 ····················UpdateTypes(types, false);
2474 ················}
2475
2476 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2477 ················foreach(Cache.Entry e in addedCacheEntries)
2478 ················{
2479 ····················cache.DeregisterLockedObject(e.pc);
2480 ····················ReadId(e);
2481 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2482 ················}
2483
2484 ················// Now update all mapping tables. Because of possible subclasses, there is no
2485 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2486 ················// tables only after all other objects have been written to ensure correct foreign keys.
2487 ················UpdateCreatedMappingTableEntries();
2488
2489 ················// The rows may contain now new Ids, which should be
2490 ················// stored in the lostRowInfo's before the rows get detached
2491 ················foreach(Cache.Entry e in cache.LockedObjects)
2492 ················{
2493 ····················if (e.row.RowState != DataRowState.Detached)
2494 ····················{
2495 ························IPersistenceCapable pc = e.pc;
2496 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2497 ····················}
2498 ················}
2499
2500 ················ds.AcceptChanges();
2501 ············}
2502
2503 ············EndSave(!deferCommit);
2504
2505 ············foreach(IPersistenceCapable pc in deletedObjects)
2506 ············{
2507 ················MakeObjectTransient(pc, false);
2508 ············}
2509
2510 ············ds.Clear();
2511 ············mappingHandler.Clear();
2512 ············createdDirectObjects.Clear();
2513 ············createdMappingTableObjects.Clear();
2514 ············this.relationChanges.Clear();
2515
2516 ············if(hollowMode)
2517 ············{
2518 ················MakeHollow(hollowModeObjects);
2519 ············}
2520
2521 ············if (this.OnSavedEvent != null)
2522 ············{
2523 ················AuditSet auditSet = new AuditSet()
2524 ················{
2525 ····················ChangedObjects = changedObjects,
2526 ····················CreatedObjects = addedObjects,
2527 ····················DeletedObjects = deletedObjects
2528 ················};
2529 ················this.OnSavedEvent( auditSet );
2530 ············}
2531 ········}
2532
2533 ········private void EndSave(bool forceCommit)
2534 ········{
2535 ············foreach(Cache.Entry e in cache.LockedObjects)
2536 ············{
2537 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2538 ····················this.ReadTimeStamp(e);
2539 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2540 ············}
2541
2542 ············cache.UnlockAll();
2543
2544 ············CheckEndTransaction(forceCommit);
2545 ········}
2546
2547 ········/// <summary>
2548 ········/// Write all foreign keys for 1:1-relations.
2549 ········/// </summary>
2550 ········/// <param name="pc">The persistent object.</param>
2551 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2552 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2553 ········{
2554 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2555 ············{
2556 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2557 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2558
2559 ················if ( relObj != null )
2560 ················{
2561 ····················int i = 0;
2562 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2563 ····················{
2564 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2565 ····················}
2566 ····················if ( r.ForeignKeyTypeColumnName != null )
2567 ····················{
2568 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2569 ····················}
2570 ················}
2571 ················else
2572 ················{
2573 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2574 ····················{
2575 ························pcRow[fkColumn.Name] = DBNull.Value;
2576 ····················}
2577 ····················if ( r.ForeignKeyTypeColumnName != null )
2578 ····················{
2579 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2580 ····················}
2581 ················}
2582 ············}
2583 ········}
2584
2585
2586
2587 ········/// <summary>
2588 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2589 ········/// </summary>
2590 ········/// <param name="e">the mapping table entry</param>
2591 ········private void WriteMappingTableEntry(MappingTableEntry e)
2592 ········{
2593 ············IPersistenceCapable pc = e.ParentObject;
2594 ············IPersistenceCapable relObj = e.RelatedObject;
2595 ············Relation r = e.Relation;
2596 ············DataTable dt = GetTable(r.MappingTable.TableName);
2597 ············DataRow row = dt.NewRow();
2598 ············int i = 0;
2599 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2600 ············{
2601 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2602 ············}
2603 ············i = 0;
2604 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2605 ············{
2606 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2607 ············}
2608
2609 ············if (r.ForeignKeyTypeColumnName != null)
2610 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2611 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2612 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2613
2614 ············dt.Rows.Add(row);
2615 ············if(e.DeleteEntry)
2616 ············{
2617 ················row.AcceptChanges();
2618 ················row.Delete();
2619 ············}
2620
2621 ············IMappingTableHandler handler;
2622 ············if (!mappingHandler.TryGetValue( r, out handler ))
2623 ············{
2624 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2625 ············}
2626 ········}
2627
2628
2629 ········/// <summary>
2630 ········/// Undo changes of a certain object
2631 ········/// </summary>
2632 ········/// <param name="o">Object to undo</param>
2633 ········public void Restore(object o)
2634 ········{············
2635 ············IPersistenceCapable pc = CheckPc(o);
2636 ············Cache.Entry e = null;
2637 ············foreach (Cache.Entry entry in cache.LockedObjects)
2638 ············{
2639 ················if (entry.pc == pc)
2640 ················{
2641 ····················e = entry;
2642 ····················break;
2643 ················}
2644 ············}
2645 ············if (e == null)
2646 ················return;
2647 ············Class cl = GetClass(e.pc);
2648 ············switch (pc.NDOObjectState)
2649 ············{
2650 ················case NDOObjectState.PersistentDirty:
2651 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2652 ····················foreach(Relation r in cl.Relations)
2653 ····················{
2654 ························if (r.Multiplicity == RelationMultiplicity.Element)
2655 ························{
2656 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2657 ····························if (subPc != null && cache.IsLocked(subPc))
2658 ································Restore(subPc);
2659 ························}
2660 ························else
2661 ························{
2662 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2663 ································continue;
2664 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2665 ····························if (subList != null)
2666 ····························{
2667 ································foreach(IPersistenceCapable subPc2 in subList)
2668 ································{
2669 ····································if (cache.IsLocked(subPc2))
2670 ········································Restore(subPc2);
2671 ································}
2672 ····························}
2673 ························}
2674 ····················}
2675 ····················RestoreRelatedObjects(pc, e.relations);
2676 ····················e.row.RejectChanges();
2677 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2678 ····················cache.Unlock(pc);
2679 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2680 ····················break;
2681 ················case NDOObjectState.Created:
2682 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2683 ····················cache.Unlock(pc);
2684 ····················MakeObjectTransient(pc, true);
2685 ····················break;
2686 ················case NDOObjectState.Deleted:
2687 ····················if (!this.IsFakeRow(cl, e.row))
2688 ····················{
2689 ························e.row.RejectChanges();
2690 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2691 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2692 ····················}
2693 ····················else
2694 ····················{
2695 ························e.row.RejectChanges();
2696 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2697 ····················}
2698 ····················cache.Unlock(pc);
2699 ····················break;
2700
2701 ············}
2702 ········}
2703
2704 ········/// <summary>
2705 ········/// Aborts a pending transaction without restoring the object state.
2706 ········/// </summary>
2707 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2708 ········public virtual void AbortTransaction()
2709 ········{
2710 ············TransactionScope.Dispose();
2711 ········}
2712
2713 ········/// <summary>
2714 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2715 ········/// </summary>
2716 ········public virtual void Abort()
2717 ········{
2718 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2719 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2720 ············// individual row.
2721 ············createdDirectObjects.Clear();
2722 ············createdMappingTableObjects.Clear();
2723 ············ArrayList deletedObjects = new ArrayList();
2724 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2725
2726 ············// Read all objects from DataSet
2727 ············foreach (Cache.Entry e in cache.LockedObjects)
2728 ············{
2729 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2730
2731 ················Class cl = GetClass(e.pc);
2732 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2733 ················if (!isFakeRow)
2734 ················{
2735 ····················RestoreRelatedObjects(e.pc, e.relations);
2736 ················}
2737 ················else
2738 ················{
2739 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2740 ················}
2741
2742 ················switch(e.pc.NDOObjectState)
2743 ················{
2744 ····················case NDOObjectState.Created:
2745 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2746 ························deletedObjects.Add(e.pc);
2747 ························break;
2748
2749 ····················case NDOObjectState.PersistentDirty:
2750 ························e.row.RejectChanges();
2751 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2752 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2753 ························break;
2754
2755 ····················case NDOObjectState.Deleted:
2756 ························if (!isFakeRow)
2757 ························{
2758 ····························e.row.RejectChanges();
2759 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2760 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2761 ························}
2762 ························else
2763 ························{
2764 ····························e.row.RejectChanges();
2765 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2766 ························}
2767 ························break;
2768
2769 ····················default:
2770 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2771 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2772 ························//break;
2773 ················}
2774 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2775 ················{
2776 ····················hollowModeObjects.Add(e.pc);
2777 ················}
2778 ············}
2779 ············cache.UnlockAll();
2780 ············foreach(IPersistenceCapable pc in deletedObjects)
2781 ············{
2782 ················MakeObjectTransient(pc, true);
2783 ············}
2784 ············ds.Clear();
2785 ············mappingHandler.Clear();
2786 ············if(hollowMode)
2787 ············{
2788 ················MakeHollow(hollowModeObjects);
2789 ············}
2790
2791 ············this.relationChanges.Clear();
2792
2793
2794 ············AbortTransaction();
2795 ········}
2796
2797
2798 ········/// <summary>
2799 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2800 ········/// disable future access.
2801 ········/// </summary>
2802 ········/// <param name="pc"></param>
2803 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2804 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2805 ········{
2806 ············cache.Deregister(pc);
2807 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2808 ············if(removeId)
2809 ············{
2810 ················pc.NDOObjectId = null;
2811 ············}
2812 ············pc.NDOObjectState = NDOObjectState.Transient;
2813 ············pc.NDOStateManager = null;
2814 ········}
2815
2816 ········/// <summary>
2817 ········/// Makes an object transient.
2818 ········/// The object can be used afterwards, but changes will not be saved in the database.
2819 ········/// </summary>
2820 ········/// <remarks>
2821 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2822 ········/// </remarks>
2823 ········/// <param name="o">The object to detach.</param>
2824 ········public void MakeTransient(object o)
2825 ········{
2826 ············IPersistenceCapable pc = CheckPc(o);
2827 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2828 ············{
2829 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2830 ············}
2831
2832 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2833 ············{
2834 ················LoadData(pc);
2835 ············}
2836 ············MakeObjectTransient(pc, true);
2837 ········}
2838
2839
2840 ········/// <summary>
2841 ········/// Make all objects of a list transient.
2842 ········/// </summary>
2843 ········/// <param name="list">the list of transient objects</param>
2844 ········public void MakeTransient(System.Collections.IList list)
2845 ········{
2846 ············foreach (IPersistenceCapable pc in list)
2847 ················MakeTransient(pc);
2848 ········}
2849
2850 ········/// <summary>
2851 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2852 ········/// </summary>
2853 ········/// <param name="o">The object to remove</param>
2854 ········public void Delete(object o)
2855 ········{
2856 ············IPersistenceCapable pc = CheckPc(o);
2857 if ( pc. NDOObjectState == NDOObjectState. Transient)
2858 ············{
2859 ················throw new NDOException( 120, "Can't delete transient object" );
2860 ············}
2861 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2862 ············{
2863 ················Delete(pc, true);
2864 ············}
2865 ········}
2866
2867
2868 ········private void LoadAllRelations(object o)
2869 ········{
2870 ············IPersistenceCapable pc = CheckPc(o);
2871 ············Class cl = GetClass(pc);
2872 ············foreach(Relation r in cl.Relations)
2873 ············{
2874 ················if (!pc.NDOGetLoadState(r.Ordinal))
2875 ····················LoadRelation(pc, r, true);
2876 ············}
2877 ········}
2878
2879
2880 ········/// <summary>
2881 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2882 ········/// </summary>
2883 ········/// <remarks>
2884 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2885 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2886 ········/// the parent object.
2887 ········/// </remarks>
2888 ········/// <param name="pc">the object to remove</param>
2889 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2890 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2891 ········{
2892 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2893 ············//Debug.Indent();
2894 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2895 ············if (idn != null)
2896 ················idn.OnDelete();
2897
2898 ············LoadAllRelations(pc);
2899 ············DeleteRelatedObjects(pc, checkAssoziations);
2900
2901 ············switch(pc.NDOObjectState)
2902 ············{
2903 ················case NDOObjectState.Transient:
2904 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2905
2906 ················case NDOObjectState.Created:
2907 ····················DataRow row = cache.GetDataRow(pc);
2908 ····················row.Delete();
2909 ····················ArrayList cdosToDelete = new ArrayList();
2910 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2911 ························if ((object)cdo == (object)pc)
2912 ····························cdosToDelete.Add(cdo);
2913 ····················foreach (object o in cdosToDelete)
2914 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2915 ····················MakeObjectTransient(pc, true);
2916 ····················break;
2917 ················case NDOObjectState.Persistent:
2918 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2919 ························SaveObjectState(pc, true);
2920 ····················row = cache.GetDataRow(pc);
2921 ····················row.Delete();
2922 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2923 ····················break;
2924 ················case NDOObjectState.Hollow:
2925 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2926 ························SaveFakeRow(pc);
2927 ····················row = cache.GetDataRow(pc);
2928 ····················row.Delete();
2929 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2930 ····················break;
2931
2932 ················case NDOObjectState.PersistentDirty:
2933 ····················row = cache.GetDataRow(pc);
2934 ····················row.Delete();
2935 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2936 ····················break;
2937
2938 ················case NDOObjectState.Deleted:
2939 ····················break;
2940 ············}
2941
2942 ············//Debug.Unindent();
2943 ········}
2944
2945
2946 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2947 ········{
2948 ············MappingTableEntry mte = null;
2949 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2950 ············{
2951 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2952 ················{
2953 ····················mte = e;
2954 ····················break;
2955 ················}
2956 ············}
2957
2958 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2959 ············{
2960 ················if (mte != null)
2961 ····················createdMappingTableObjects.Remove(mte);
2962 ············}
2963 ············else
2964 ············{
2965 ················if (mte == null)
2966 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2967 ············}
2968 ········}
2969
2970 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2971 ········{
2972 ············// Two tasks: a) Null the foreign key
2973 ············//··············b) remove the element from the foreign container
2974
2975 ············if (!r.Bidirectional)
2976 ················return;
2977
2978 ············// this keeps the oid valid
2979 ············if (GetClass(child.GetType()).Oid.IsDependent)
2980 ················return;
2981
2982 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2983 ············{
2984 ················LoadAndMarkDirty(child);
2985 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2986 ············}
2987 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
2988 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
2989 ············{
2990 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
2991 ····················LoadRelation(child, r.ForeignRelation, true);
2992 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
2993 ················if (l == null)
2994 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
2995 ················ObjectListManipulator.Remove(l, pc);
2996 ················// Don't need to delete the mapping table entry, because that was done
2997 ················// through the parent.
2998 ············}
2999 ········}
3000
3001 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3002 ········{
3003 ············// 1) Element····nomap····ass
3004 ············// 2) Element····nomap····comp
3005 ············// 3) Element····map········ass
3006 ············// 4) Element····map········comp
3007 ············// 5) List········nomap····ass
3008 ············// 6) List········nomap····comp
3009 ············// 7) List········map········ass
3010 ············// 8) List········map········comp
3011
3012 ············// Two tasks: Null foreign key and, if Composition, delete the child
3013
3014 ············// If Mapping Table, delete the entry - 3,7
3015 ············// If List and assoziation, null the foreign key in the foreign class - 5
3016 ············// If Element, null the foreign key in the own class 1,2,3,4
3017 ············// If composition, delete the child 2,4,6,8
3018
3019 ············// If the relObj is newly created
3020 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3021 ············Class childClass = GetClass(child);
3022
3023 ············if (r.MappingTable != null)··// 3,7
3024 ············{················
3025 ················DeleteMappingTableEntry(pc, r, child);
3026 ············}
3027 ············else if (r.Multiplicity == RelationMultiplicity.List
3028 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3029 ············{················
3030 ················LoadAndMarkDirty(child);
3031 ················DataRow row = this.cache.GetDataRow(child);
3032 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3033 ················{
3034 ····················row[fkColumnn.Name] = DBNull.Value;
3035 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3036 ················}
3037 ············}
3038 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3039 ············{
3040 ················LoadAndMarkDirty(pc);
3041 ············}
3042 ············if (r.Composition || childClass.Oid.IsDependent)
3043 ············{
3044 #if DEBUG
3045 ················if (child.NDOObjectState == NDOObjectState.Transient)
3046 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3047 #endif
3048 ················// Deletes the foreign key in case of List multiplicity
3049 ················// In case of Element multiplicity, the parent is either deleted,
3050 ················// or RemoveRelatedObject is called because the relation has been nulled.
3051 ················Delete(child);··
3052 ············}
3053 ········}
3054
3055 ········/// <summary>
3056 ········/// Remove a related object
3057 ········/// </summary>
3058 ········/// <param name="pc"></param>
3059 ········/// <param name="r"></param>
3060 ········/// <param name="child"></param>
3061 ········/// <param name="calledFromStateManager"></param>
3062 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3063 ········{
3064 ············//TODO: We need a relation management, which is independent of
3065 ············//the state management of an object. At the moment the relation
3066 ············//lists or elements are cached for restore, if an object is marked dirty.
3067 ············//Thus we have to mark dirty our parent object in any case at the moment.
3068 ············if (calledFromStateManager)
3069 ················MarkDirty(pc);
3070
3071 ············// Object can be deleted in an OnDelete-Handler
3072 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3073 ················return;
3074 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3075 ············// Preconditions
3076 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3077 ············// The Object state is checked there
3078
3079 ············// If there is a composition in the opposite direction
3080 ············// && the other direction hasn't been processed
3081 ············// throw an exception.
3082 ············// If an exception is thrown at this point, have a look at IsLocked....
3083 ············if (r.Bidirectional && r.ForeignRelation.Composition
3084 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3085 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3086
3087 ············if (!removeLock.GetLock(pc, r, child))
3088 ················return;
3089
3090 ············try
3091 ············{
3092 ················// must be in this order, since the child
3093 ················// can be deleted in DeleteOrNullRelation
3094 ················//if (changeForeignRelations)
3095 ················DeleteOrNullForeignRelation(pc, r, child);
3096 ················DeleteOrNullRelation(pc, r, child);
3097 ············}
3098 ············finally
3099 ············{
3100 ················removeLock.Unlock(pc, r, child);
3101 ············}
3102
3103 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3104 ········}
3105
3106 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3107 ········{
3108 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3109 ············//············Debug.Indent();
3110 ············if (r.Multiplicity == RelationMultiplicity.Element)
3111 ············{
3112 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3113 ················if(child != null)
3114 ················{
3115 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3116 ····················//····················{
3117 ····················//························if (!r.ForeignRelation.Composition)
3118 ····················//························{
3119 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3120 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3121 ····················//························}
3122 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3123 ····················//························else
3124 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3125 ····················//····················}
3126 ····················InternalRemoveRelatedObject(pc, r, child, false);
3127 ················}
3128 ············}
3129 ············else
3130 ············{
3131 ················IList list = mappings.GetRelationContainer(pc, r);
3132 ················if(list != null && list.Count > 0)
3133 ················{
3134 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3135 ····················//····················{
3136 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3137 ····················//····················}
3138 ····················// Since RemoveRelatedObjects probably changes the list,
3139 ····················// we iterate through a copy of the list.
3140 ····················ArrayList al = new ArrayList(list);
3141 ····················foreach(IPersistenceCapable relObj in al)
3142 ····················{
3143 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3144 ····················}
3145 ················}
3146 ············}
3147 ············//············Debug.Unindent();
3148 ········}
3149
3150 ········/// <summary>
3151 ········/// Remove all related objects from a parent.
3152 ········/// </summary>
3153 ········/// <param name="pc">the parent object</param>
3154 ········/// <param name="checkAssoziations"></param>
3155 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3156 ········{
3157 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3158 ············//············Debug.Indent();
3159 ············// delete all related objects:
3160 ············Class parentClass = GetClass(pc);
3161 ············// Remove Assoziations first
3162 ············foreach(Relation r in parentClass.Relations)
3163 ············{
3164 ················if (!r.Composition)
3165 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3166 ············}
3167 ············foreach(Relation r in parentClass.Relations)
3168 ············{
3169 ················if (r.Composition)
3170 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3171 ············}
3172
3173 ············//············Debug.Unindent();
3174 ········}
3175
3176 ········/// <summary>
3177 ········/// Delete a list of objects
3178 ········/// </summary>
3179 ········/// <param name="list">the list of objects to remove</param>
3180 ········public void Delete(IList list)
3181 ········{
3182 ············for (int i = 0; i < list.Count; i++)
3183 ············{
3184 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3185 ················Delete(pc);
3186 ············}
3187 ········}
3188
3189 ········/// <summary>
3190 ········/// Make object hollow. All relations will be unloaded and object data will be
3191 ········/// newly fetched during the next touch of a persistent field.
3192 ········/// </summary>
3193 ········/// <param name="o"></param>
3194 ········public virtual void MakeHollow(object o)
3195 ········{
3196 ············IPersistenceCapable pc = CheckPc(o);
3197 ············MakeHollow(pc, false);
3198 ········}
3199
3200 ········/// <summary>
3201 ········/// Make the object hollow if it is persistent. Unload all complex data.
3202 ········/// </summary>
3203 ········/// <param name="o"></param>
3204 ········/// <param name="recursive">if true then unload related objects as well</param>
3205 ········public virtual void MakeHollow(object o, bool recursive)
3206 ········{
3207 ············IPersistenceCapable pc = CheckPc(o);
3208 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3209 ················return;
3210 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3211 ············{
3212 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3213 ············}
3214 ············pc.NDOObjectState = NDOObjectState.Hollow;
3215 ············MakeRelationsHollow(pc, recursive);
3216 ········}
3217
3218 ········/// <summary>
3219 ········/// Make all objects of a list hollow.
3220 ········/// </summary>
3221 ········/// <param name="list">the list of objects that should be made hollow</param>
3222 ········public virtual void MakeHollow(System.Collections.IList list)
3223 ········{
3224 ············MakeHollow(list, false);
3225 ········}
3226
3227 ········/// <summary>
3228 ········/// Make all objects of a list hollow.
3229 ········/// </summary>
3230 ········/// <param name="list">the list of objects that should be made hollow</param>
3231 ········/// <param name="recursive">if true then unload related objects as well</param>
3232 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3233 ········{
3234 ············foreach (IPersistenceCapable pc in list)
3235 ················MakeHollow(pc, recursive);················
3236 ········}
3237
3238 ········/// <summary>
3239 ········/// Make all unlocked objects in the cache hollow.
3240 ········/// </summary>
3241 ········public virtual void MakeAllHollow()
3242 ········{
3243 ············foreach(var pc in cache.UnlockedObjects)
3244 ············{
3245 ················MakeHollow(pc, false);
3246 ············}
3247 ········}
3248
3249 ········/// <summary>
3250 ········/// Make relations hollow.
3251 ········/// </summary>
3252 ········/// <param name="pc">The parent object</param>
3253 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3254 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3255 ········{
3256 ············Class c = GetClass(pc);
3257 ············foreach(Relation r in c.Relations)
3258 ············{
3259 ················if (r.Multiplicity == RelationMultiplicity.Element)
3260 ················{
3261 ····················mappings.SetRelationField(pc, r.FieldName, null);
3262 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3263 ····················//····················if((null != child) && recursive)
3264 ····················//····················{
3265 ····················//························MakeHollow(child, true);
3266 ····················//····················}
3267 ················}
3268 ················else
3269 ················{
3270 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3271 ························continue;
3272 ····················// Help GC by clearing lists
3273 ····················IList l = mappings.GetRelationContainer(pc, r);
3274 ····················if(l != null)
3275 ····················{
3276 ························if(recursive)
3277 ························{
3278 ····························MakeHollow(l, true);
3279 ························}
3280 ························l.Clear();
3281 ····················}
3282 ····················// Hollow relation
3283 ····················mappings.SetRelationContainer(pc, r, null);
3284 ················}
3285 ············}
3286 ············ClearRelationState(pc);
3287 ········}
3288
3289 ········private void ClearRelationState(IPersistenceCapable pc)
3290 ········{
3291 ············Class cl = GetClass(pc);
3292 ············foreach(Relation r in cl.Relations)
3293 ················pc.NDOSetLoadState(r.Ordinal, false);
3294 ········}
3295
3296 ········private void SetRelationState(IPersistenceCapable pc)
3297 ········{
3298 ············Class cl = GetClass(pc);
3299 ············// Due to a bug in the enhancer the constructors are not always patched right,
3300 ············// so NDOLoadState might be uninitialized
3301 ············if (pc.NDOLoadState == null)
3302 ············{
3303 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3304 ················if (fi == null)
3305 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3306 ················fi.SetValue(pc, new LoadState());
3307 ············}
3308
3309 ············// After serialization the relation load state is null
3310 ············if (pc.NDOLoadState.RelationLoadState == null)
3311 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3312 ············foreach(Relation r in cl.Relations)
3313 ················pc.NDOSetLoadState(r.Ordinal, true);
3314 ········}
3315
3316 ········/// <summary>
3317 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3318 ········/// </summary>
3319 ········/// <param name="t">The type of the persistent object</param>
3320 ········/// <returns></returns>
3321 ········public IPersistenceCapable CreateObject(Type t)
3322 ········{
3323 ············return Metaclasses.GetClass( t ).CreateObject( this.ConfigContainer );
3324 ········}
3325
3326 ········/// <summary>
3327 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3328 ········/// </summary>
3329 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3330 ········/// <returns></returns>
3331 ········public T CreateObject<T>()
3332 ········{
3333 ············return (T)CreateObject( typeof( T ) );
3334 ········}
3335
3336 ········/// <summary>
3337 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3338 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3339 ········/// is performed.
3340 ········/// </summary>
3341 ········/// <param name="t">The type of the object to retrieve.</param>
3342 ········/// <param name="keyData">The key value to build the object id.</param>
3343 ········/// <returns>A hollow object</returns>
3344 ········/// <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>
3345 ········public IPersistenceCapable FindObject(Type t, object keyData)
3346 ········{
3347 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3348 ············return FindObject(oid);
3349 ········}
3350
3351 ········/// <summary>
3352 ········/// Finds an object using a short id.
3353 ········/// </summary>
3354 ········/// <param name="encodedShortId"></param>
3355 ········/// <returns></returns>
3356 ········public IPersistenceCapable FindObject(string encodedShortId)
3357 ········{
3358 ············string shortId = encodedShortId.Decode();
3359 ············string[] arr = shortId.Split( '-' );
3360 ············if (arr.Length != 3)
3361 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3362 ············Type t = shortId.GetObjectType(this);··// try readable format
3363 ············if (t == null)
3364 ············{
3365 ················int typeCode = 0;
3366 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3367 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3368 ················t = this.typeManager[typeCode];
3369 ················if (t == null)
3370 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3371 ············}
3372
3373 ············Class cls = GetClass( t );
3374 ············if (cls == null)
3375 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3376
3377 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3378 ············string[] oidValues = arr[2].Split( ' ' );
3379
3380 ············int i = 0;
3381 ············foreach (var oidValue in oidValues)
3382 ············{
3383 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3384 ················if (oidType == typeof( int ))
3385 ················{
3386 ····················int key;
3387 ····················if (!int.TryParse( oidValue, out key ))
3388 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3389 ····················if (key > (int.MaxValue >> 1))
3390 ························key = -(int.MaxValue - key);
3391 ····················keydata[i] = key;
3392 ················}
3393 ················else if (oidType == typeof( Guid ))
3394 ················{
3395 ····················Guid key;
3396 ····················if (!Guid.TryParse( oidValue, out key ))
3397 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3398 ····················keydata[i] = key;
3399 ················}
3400 ················else if (oidType == typeof( string ))
3401 ················{
3402 ····················keydata[i] = oidValue;
3403 ················}
3404 ················else
3405 ················{
3406 ····················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 ) );
3407 ················}
3408
3409 ················i++;
3410 ············}
3411
3412 ············if (keydata.Length == 1)
3413 ················return FindObject( t, keydata[0] );
3414
3415 ············return FindObject( t, keydata );············
3416 ········}
3417
3418 ········/// <summary>
3419 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3420 ········/// instance of the object is returned. In either case, the DB is not accessed!
3421 ········/// </summary>
3422 ········/// <param name="id">Object id</param>
3423 ········/// <returns>The object to retrieve in hollow state</returns>········
3424 ········public IPersistenceCapable FindObject(ObjectId id)
3425 ········{
3426 ············if(id == null)
3427 ············{
3428 ················throw new ArgumentNullException("id");
3429 ············}
3430
3431 ············if(!id.IsValid())
3432 ············{
3433 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3434 ············}
3435
3436 ············IPersistenceCapable pc = cache.GetObject(id);
3437 ············if(pc == null)
3438 ············{
3439 ················pc = CreateObject(id.Id.Type);
3440 ················pc.NDOObjectId = id;
3441 ················pc.NDOStateManager = sm;
3442 ················pc.NDOObjectState = NDOObjectState.Hollow;
3443 ················cache.UpdateCache(pc);
3444 ············}
3445 ············return pc;
3446 ········}
3447
3448
3449 ········/// <summary>
3450 ········/// Reload an object from the database.
3451 ········/// </summary>
3452 ········/// <param name="o">The object to be reloaded.</param>
3453 ········public virtual void Refresh(object o)
3454 ········{
3455 ············IPersistenceCapable pc = CheckPc(o);
3456 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3457 ············{
3458 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3459 ············}
3460
3461 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3462 ················return; // Cannot update objects in current transaction
3463
3464 ············MakeHollow(pc);
3465 ············LoadData(pc);
3466 ········}
3467
3468 ········/// <summary>
3469 ········/// Refresh a list of objects.
3470 ········/// </summary>
3471 ········/// <param name="list">The list of objects to be refreshed.</param>
3472 ········public virtual void Refresh(IList list)
3473 ········{
3474 ············foreach (IPersistenceCapable pc in list)
3475 ················Refresh(pc);························
3476 ········}
3477
3478 ········/// <summary>
3479 ········/// Refreshes all unlocked objects in the cache.
3480 ········/// </summary>
3481 ········public virtual void RefreshAll()
3482 ········{
3483 ············Refresh( cache.UnlockedObjects.ToList() );
3484 ········}
3485
3486 ········/// <summary>
3487 ········/// Closes the PersistenceManager and releases all resources.
3488 ········/// </summary>
3489 ········public override void Close()
3490 ········{
3491 ············if (this.isClosing)
3492 ················return;
3493 ············this.isClosing = true;
3494 ············TransactionScope.Dispose();
3495 ············UnloadCache();
3496 ············base.Close();
3497 ········}
3498
3499 ········internal void LogIfVerbose(string msg)
3500 ········{
3501 ············if (VerboseMode && LogAdapter != null)
3502 ············{
3503 ················this.LogAdapter.Info( msg );
3504 ············}
3505 ········}
3506
3507
3508 ········#endregion
3509
3510
3511 #region Class extent
3512 ········/// <summary>
3513 ········/// Gets all objects of a given class.
3514 ········/// </summary>
3515 ········/// <param name="t">the type of the class</param>
3516 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3517 ········public virtual IList GetClassExtent(Type t)
3518 ········{
3519 ············return GetClassExtent(t, true);
3520 ········}
3521
3522 ········/// <summary>
3523 ········/// Gets all objects of a given class.
3524 ········/// </summary>
3525 ········/// <param name="t">The type of the class.</param>
3526 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3527 ········/// <returns>A list of all persistent objects of the given class.</returns>
3528 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3529 ········public virtual IList GetClassExtent(Type t, bool hollow)
3530 ········{
3531 ············IQuery q = NewQuery( t, null, hollow );
3532 ············return q.Execute();
3533 ········}
3534
3535 #endregion
3536
3537 #region Query engine
3538
3539
3540 ········/// <summary>
3541 ········/// Returns a virtual table for Linq queries.
3542 ········/// </summary>
3543 ········/// <typeparam name="T"></typeparam>
3544 ········/// <returns></returns>
3545 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3546 ········{
3547 ············return new VirtualTable<T>( this );
3548 ········}
3549
3550 ········
3551 ········/// <summary>
3552 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3553 ········/// 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
3554 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3555 ········/// not lose the foreign key it will be stored as part of the object.
3556 ········/// </summary>
3557 ········/// <param name="cl"></param>
3558 ········/// <param name="pc"></param>
3559 ········/// <param name="row"></param>
3560 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3561 ········{
3562 ············if (cl.FKColumnNames != null)
3563 ············{
3564 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3565 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3566 ················foreach(string colName in cl.FKColumnNames)
3567 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3568 ················pc.NDOLoadState.LostRowInfo = kvl;················
3569 ············}
3570 ········}
3571
3572 ········/// <summary>
3573 ········/// Writes information into the data row, which cannot be stored in the object.
3574 ········/// </summary>
3575 ········/// <param name="cl"></param>
3576 ········/// <param name="pc"></param>
3577 ········/// <param name="row"></param>
3578 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3579 ········{
3580 ············if (cl.FKColumnNames != null)
3581 ············{
3582 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3583 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3584 ················if (kvl == null)
3585 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3586 ················foreach (KeyValuePair pair in kvl)
3587 ····················row[(string) pair.Key] = pair.Value;
3588 ············}
3589 ········}
3590
3591 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3592 ········{
3593 ············ReadObject(pc, row, cl.ColumnNames, 0);
3594 ············ReadTimeStamp(cl, pc, row);
3595 ············ReadLostForeignKeysFromRow(cl, pc, row);
3596 ············LoadRelated1To1Objects(pc, row);
3597 ············pc.NDOObjectState = NDOObjectState.Persistent;
3598 ········}
3599
3600
3601 ········/// <summary>
3602 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3603 ········/// </summary>
3604 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3605 ········{
3606 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3607 ············if (rows.Count == 0)
3608 ················return queryResult;
3609
3610 ············IList callbackObjects = new ArrayList();
3611 ············Class cl = GetClass(t);
3612 ············if (t.IsGenericTypeDefinition)
3613 ············{
3614 ················if (cl.TypeNameColumn == null)
3615 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3616 ················IEnumerator en = rows.GetEnumerator();
3617 ················en.MoveNext();··// Move to the first element
3618 ················DataRow testrow = (DataRow)en.Current;
3619 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3620 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3621 ············}
3622
3623 ············foreach(DataRow row in rows)
3624 ············{
3625 ················Type concreteType = t;
3626 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3627 ················{
3628 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3629 ····················{
3630 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3631 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3632 ····················}
3633 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3634 ····················concreteType = Type.GetType(typeStr);
3635 ····················if (concreteType == null)
3636 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3637 ················}
3638 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3639 ················IPersistenceCapable pc = cache.GetObject(id);················
3640 ················if(pc == null)
3641 ················{
3642 ····················var mc = Metaclasses.GetClass(concreteType);
3643 ····················pc = mc.CreateObject( this.ConfigContainer );
3644 ····················pc.NDOObjectId = id;
3645 ····················pc.NDOStateManager = sm;
3646 ····················// If the object shouldn't be hollow, this will be overwritten later.
3647 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3648 ················}
3649 ················// If we have found a non hollow object, the time stamp remains the old one.
3650 ················// In every other case we use the new time stamp.
3651 ················// Note, that there could be a hollow object in the cache.
3652 ················// We need the time stamp in hollow objects in order to correctly
3653 ················// delete objects using fake rows.
3654 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3655 ················{
3656 ····················ReadTimeStamp(cl, pc, row);
3657 ················}
3658 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3659 ················{
3660 ····················Row2Object(cl, pc, row);
3661 ····················if ((pc as IPersistenceNotifiable) != null)
3662 ························callbackObjects.Add(pc);
3663 ················}
3664
3665 ················cache.UpdateCache(pc);
3666 ················queryResult.Add(pc);
3667 ············}
3668 ············// Make shure this is the last statement before returning
3669 ············// to the caller, so the user can recursively use persistent
3670 ············// objects
3671 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3672 ················ipn.OnLoaded();
3673
3674 ············return queryResult;
3675 ········}
3676
3677 #endregion
3678
3679 #region Cache Management
3680 ········/// <summary>
3681 ········/// Remove all unused entries from the cache.
3682 ········/// </summary>
3683 ········public void CleanupCache()
3684 ········{
3685 ············GC.Collect(GC.MaxGeneration);
3686 ············GC.WaitForPendingFinalizers();
3687 ············cache.Cleanup();
3688 ········}
3689
3690 ········/// <summary>
3691 ········/// Remove all unlocked objects from the cache. Use with care!
3692 ········/// </summary>
3693 ········public void UnloadCache()
3694 ········{
3695 ············cache.Unload();
3696 ········}
3697 #endregion
3698
3699 ········/// <summary>
3700 ········/// Default encryption key for NDO
3701 ········/// </summary>
3702 ········/// <remarks>
3703 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3704 ········/// </remarks>
3705 ········public virtual byte[] EncryptionKey
3706 ········{
3707 ············get
3708 ············{
3709 ················if (this.encryptionKey == null)
3710 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3711 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3712 ················return this.encryptionKey;
3713 ············}
3714 ············set { this.encryptionKey = value; }
3715 ········}
3716
3717 ········/// <summary>
3718 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3719 ········/// </summary>
3720 ········public virtual bool HollowMode
3721 ········{
3722 ············get { return hollowMode; }
3723 ············set { hollowMode = value; }
3724 ········}
3725
3726 ········internal TypeManager TypeManager
3727 ········{
3728 ············get { return typeManager; }
3729 ········}
3730
3731 ········/// <summary>
3732 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3733 ········/// </summary>
3734 ········/// <remarks>
3735 ········/// Set this value before you start any transactions.
3736 ········/// </remarks>
3737 ········public TransactionMode TransactionMode
3738 ········{
3739 ············get { return TransactionScope.TransactionMode; }
3740 ············set { TransactionScope.TransactionMode = value; }
3741 ········}
3742
3743 ········/// <summary>
3744 ········/// Sets or gets the Isolation Level.
3745 ········/// </summary>
3746 ········/// <remarks>
3747 ········/// Set this value before you start any transactions.
3748 ········/// </remarks>
3749 ········public IsolationLevel IsolationLevel
3750 ········{
3751 ············get { return TransactionScope.IsolationLevel; }
3752 ············set { TransactionScope.IsolationLevel = value; }
3753 ········}
3754
3755 ········internal class MappingTableEntry
3756 ········{
3757 ············private IPersistenceCapable parentObject;
3758 ············private IPersistenceCapable relatedObject;
3759 ············private Relation relation;
3760 ············private bool deleteEntry;
3761 ············
3762 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3763 ············{
3764 ············}
3765 ············
3766 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3767 ············{
3768 ················parentObject = pc;
3769 ················relatedObject = relObj;
3770 ················relation = r;
3771 ················this.deleteEntry = deleteEntry;
3772 ············}
3773
3774 ············public bool DeleteEntry
3775 ············{
3776 ················get
3777 ················{
3778 ····················return deleteEntry;
3779 ················}
3780 ············}
3781
3782 ············public IPersistenceCapable ParentObject
3783 ············{
3784 ················get
3785 ················{
3786 ····················return parentObject;
3787 ················}
3788 ············}
3789
3790 ············public IPersistenceCapable RelatedObject
3791 ············{
3792 ················get
3793 ················{
3794 ····················return relatedObject;
3795 ················}
3796 ············}
3797
3798 ············public Relation Relation
3799 ············{
3800 ················get
3801 ················{
3802 ····················return relation;
3803 ················}
3804 ············}
3805 ········}
3806
3807 ········/// <summary>
3808 ········/// Get a DataRow representing the given object.
3809 ········/// </summary>
3810 ········/// <param name="o"></param>
3811 ········/// <returns></returns>
3812 ········public DataRow GetClonedDataRow( object o )
3813 ········{
3814 ············IPersistenceCapable pc = CheckPc( o );
3815
3816 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3817 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3818
3819 ············DataRow row = cache.GetDataRow( pc );
3820 ············DataTable newTable = row.Table.Clone();
3821 ············newTable.ImportRow( row );
3822 ············row = newTable.Rows[0];
3823
3824 ············Class cls = mappings.FindClass(o.GetType());
3825 ············WriteObject( pc, row, cls.ColumnNames );
3826 ············WriteForeignKeysToRow( pc, row );
3827
3828 ············return row;
3829 ········}
3830
3831 ········/// <summary>
3832 ········/// Gets an object, which shows all changes applied to the given object.
3833 ········/// This function can be used to build an audit system.
3834 ········/// </summary>
3835 ········/// <param name="o"></param>
3836 ········/// <returns>An ExpandoObject</returns>
3837 ········public ChangeLog GetChangeSet( object o )
3838 ········{
3839 ············var changeLog = new ChangeLog(this);
3840 ············changeLog.Initialize( o );
3841 ············return changeLog;
3842 ········}
3843
3844 ········/// <summary>
3845 ········/// Outputs a revision number representing the assembly version.
3846 ········/// </summary>
3847 ········/// <remarks>This can be used for debugging purposes</remarks>
3848 ········public int Revision
3849 ········{
3850 ············get
3851 ············{
3852 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3853 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3854 ················return int.Parse( vstring );
3855 ············}
3856 ········}
3857 ····}
3858 }
3859