Datei: NDODLL/PersistenceManager.cs

Last Commit (7e08240)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the ServiceScopeEvent event of the PersistenceManager.
59 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
60 ····/// </summary>
61 ····public delegate IServiceProvider ServiceScopeHandler( Type t, ObjectId oid );
62
63 ····/// <summary>
64 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
65 ····/// </summary>
66 ····public delegate void OnSavingHandler(ICollection l);
67 ····/// <summary>
68 ····/// Delegate type for the OnSavedEvent.
69 ····/// </summary>
70 ····/// <param name="auditSet"></param>
71 ····public delegate void OnSavedHandler(AuditSet auditSet);
72
73 ····/// <summary>
74 ····/// 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.
75 ····/// </summary>
76 ····/// <param name="pc"></param>
77 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
78 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
79 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
80
81 ····/// <summary>
82 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
83 ····/// 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.
84 ····/// </summary>
85 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
86 ····{········
87 ········private bool hollowMode = false;
88 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
89
90 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
91 ········private ObjectLock removeLock = new ObjectLock();
92 ········private ObjectLock addLock = new ObjectLock();
93 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
94 ········// List of created objects that use mapping table and need to be stored in mapping table
95 ········// after they have been stored to the database to update foreign keys.
96 ········private ArrayList createdMappingTableObjects = new ArrayList();··
97 ········private TypeManager typeManager;
98 ········internal bool DeferredMode { get; private set; }
99 ········private INDOTransactionScope transactionScope;
100 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
101
102 ········private OpenConnectionListener openConnectionListener;
103
104 ········/// <summary>
105 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
106 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
107 ········/// </summary>
108 ········public event CollisionHandler CollisionEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
112 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
113 ········/// </summary>
114 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
115
116 ········/// <summary>
117 ········/// Register a listener to this event, if you want to be notified about the end
118 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
119 ········/// during the transaction and are to be saved or deleted.
120 ········/// </summary>
121 ········public event OnSavingHandler OnSavingEvent;
122 ········/// <summary>
123 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
124 ········/// </summary>
125 ········public event OnSavedHandler OnSavedEvent;
126 ········
127 ········private const string hollowMarker = "Hollow";
128 ········private byte[] encryptionKey;
129 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
130 ········private bool isClosing = false;
131
132 ········/// <summary>
133 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
134 ········/// </summary>
135 ········protected internal List<RelationChangeRecord> RelationChanges
136 ········{
137 ············get { return this.relationChanges; }
138 ········}
139
140 ········/// <summary>
141 ········/// Initializes a new PersistenceManager instance.
142 ········/// </summary>
143 ········/// <param name="mappingFileName"></param>
144 ········protected override void Init(string mappingFileName)
145 ········{
146 ············try
147 ············{
148 ················base.Init(mappingFileName);
149 ············}
150 ············catch (Exception ex)
151 ············{
152 ················if (ex is NDOException)
153 ····················throw;
154 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
155 ············}
156
157 ········}
158
159 ········/// <summary>
160 ········/// Initializes the persistence manager
161 ········/// </summary>
162 ········/// <remarks>
163 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
164 ········/// </remarks>
165 ········/// <param name="mapping"></param>
166 ········internal override void Init( Mappings mapping )
167 ········{
168 ············base.Init( mapping );
169
170 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
171
172 ············string dir = Path.GetDirectoryName( mapping.FileName );
173
174 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
175 ············typeManager = new TypeManager( typesFile, this.mappings );
176
177 ············sm = new StateManager( this );
178
179 ············InitClasses();
180 ········}
181
182
183 ········/// <summary>
184 ········/// Standard Constructor.
185 ········/// </summary>
186 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
187 ········/// <remarks>
188 ········/// Searches for a mapping file in the application directory.
189 ········/// The constructor tries to find a file with the same name as
190 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
191 ········/// file called AssemblyName.ndo.mapping in the application directory.
192 ········/// </remarks>
193 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
194 ········{
195 ········}
196
197 ········/// <summary>
198 ········/// Loads the mapping file from the specified location. This allows to use
199 ········/// different mapping files with different classes mapped in it.
200 ········/// </summary>
201 ········/// <param name="mappingFile">Path to the mapping file.</param>
202 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
203 ········/// <remarks>Only the Professional and Enterprise
204 ········/// Editions can handle more than one mapping file.</remarks>
205 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
206 ········{
207 ········}
208
209 ········/// <summary>
210 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
211 ········/// </summary>
212 ········/// <param name="mapping">The cached mapping object</param>
213 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
214 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
215 ········{
216 ········}
217
218 ········#region Object Container Stuff
219 ········/// <summary>
220 ········/// Gets a container of all loaded objects and tries to load all child objects,
221 ········/// which are reachable through composite relations.
222 ········/// </summary>
223 ········/// <returns>An ObjectContainer object.</returns>
224 ········/// <remarks>
225 ········/// It is not recommended, to transfer objects with a state other than Hollow,
226 ········/// Persistent, or Transient.
227 ········/// The transfer format is binary.
228 ········/// </remarks>
229 ········public ObjectContainer GetObjectContainer()
230 ········{
231 ············IList l = this.cache.AllObjects;
232 ············foreach(IPersistenceCapable pc in l)
233 ············{
234 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
235 ················{
236 ····················if (Logger != null)
237 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
238 ····················System.Diagnostics.Trace.WriteLine("NDO warning: Call to GetObjectContainer returns changed objects.");
239 ················}
240 ············}
241
242 ············ObjectContainer oc = new ObjectContainer();
243 ············oc.AddList(l);
244 ············return oc;
245 ········}
246
247 ········/// <summary>
248 ········/// Returns a container of all objects provided in the objects list and searches for
249 ········/// child objects according to the serFlags.
250 ········/// </summary>
251 ········/// <param name="objects">The list of the root objects to add to the container.</param>
252 ········/// <returns>An ObjectContainer object.</returns>
253 ········/// <remarks>
254 ········/// It is not recommended, to transfer objects with a state other than Hollow,
255 ········/// Persistent, or Transient.
256 ········/// </remarks>
257 ········public ObjectContainer GetObjectContainer(IList objects)
258 ········{
259 ············foreach(object o in objects)
260 ············{
261 ················CheckPc(o);
262 ················IPersistenceCapable pc = o as IPersistenceCapable;
263 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
264 ····················LoadData(pc);
265 ············}
266 ············ObjectContainer oc = new ObjectContainer();
267 ············oc.AddList(objects);
268 ············return oc;
269 ········}
270
271
272 ········/// <summary>
273 ········/// Returns a container containing the provided object
274 ········/// and tries to load all child objects
275 ········/// reachable through composite relations.
276 ········/// </summary>
277 ········/// <param name="obj">The object to be added to the container.</param>
278 ········/// <returns>An ObjectContainer object.</returns>
279 ········/// <remarks>
280 ········/// It is not recommended, to transfer objects with a state other than Hollow,
281 ········/// Persistent, or Transient.
282 ········/// The transfer format is binary.
283 ········/// </remarks>
284 ········public ObjectContainer GetObjectContainer(Object obj)
285 ········{
286 ············CheckPc(obj);
287 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
288 ················LoadData(obj);
289 ············ObjectContainer oc = new ObjectContainer();
290 ············oc.AddObject(obj);
291 ············return oc;
292 ········}
293
294 ········/// <summary>
295 ········/// Merges an object container to the active objects in the pm. All changes and the state
296 ········/// of the objects will be taken over by the pm.
297 ········/// </summary>
298 ········/// <remarks>
299 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
300 ········/// The flag MarkAsTransient can be used to perform a kind
301 ········/// of object based replication using the ObjectContainer class.
302 ········/// Objects, which are persistent at one machine, can be transfered
303 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
304 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
305 ········/// transient object tree.
306 ········/// There is one difference to freshly created objects: If an object id exists, it will be
307 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
308 ········/// oids will be reused by the receiving PersistenceManager.
309 ········/// </remarks>
310 ········/// <param name="ocb">The object container to be merged.</param>
311 ········public void MergeObjectContainer(ObjectContainerBase ocb)
312 ········{
313 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
314 ············if (csc != null)
315 ············{
316 ················MergeChangeSet(csc);
317 ················return;
318 ············}
319 ············ObjectContainer oc = ocb as ObjectContainer;
320 ············if (oc != null)
321 ············{
322 ················InternalMergeObjectContainer(oc);
323 ················return;
324 ············}
325 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
326 ········}
327
328
329 ········void InternalMergeObjectContainer(ObjectContainer oc)
330 ········{
331 ············// TODO: Check, if other states are useful. Find use scenarios.
332 ············foreach(IPersistenceCapable pc in oc.RootObjects)
333 ············{
334 ················if (pc.NDOObjectState == NDOObjectState.Transient)
335 ····················MakePersistent(pc);
336 ············}
337 ············foreach(IPersistenceCapable pc in oc.RootObjects)
338 ············{
339 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
340 ············}
341 ········}
342
343 ········void MergeChangeSet(ChangeSetContainer cs)
344 ········{
345 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
346 ············{
347 ················InternalMakePersistent(pc, false);
348 ············}
349 ············foreach(ObjectId oid in cs.DeletedObjects)
350 ············{
351 ················IPersistenceCapable pc2 = FindObject(oid);
352 ················Delete(pc2);
353 ············}
354 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
355 ············{
356 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
357 ················Class pcClass = GetClass(pc);
358 ················// Make sure, the object is loaded.
359 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
360 ····················LoadData(pc2);
361 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
362 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
363 ················DataRow row = entry.row;
364 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
365 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
366 ············}
367 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
368 ············{
369 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
370 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
371 ················Class pcClass = GetClass(parent);
372 ················Relation r = pcClass.FindRelation(rcr.RelationName);
373 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
374 ····················LoadRelation(parent, r, true);
375 ················if (rcr.IsAdded)
376 ················{
377 ····················InternalAddRelatedObject(parent, r, child, true);
378 ····················if (r.Multiplicity == RelationMultiplicity.Element)
379 ····················{
380 ························mappings.SetRelationField(parent, r.FieldName, child);
381 ····················}
382 ····················else
383 ····················{
384 ························IList l = mappings.GetRelationContainer(parent, r);
385 ························l.Add(child);
386 ····················}
387 ················}
388 ················else
389 ················{
390 ····················RemoveRelatedObject(parent, r.FieldName, child);
391 ····················if (r.Multiplicity == RelationMultiplicity.Element)
392 ····················{
393 ························mappings.SetRelationField(parent, r.FieldName, null);
394 ····················}
395 ····················else
396 ····················{
397 ························IList l = mappings.GetRelationContainer(parent, r);
398 ························try
399 ························{
400 ····························ObjectListManipulator.Remove(l, child);
401 ························}
402 ························catch
403 ························{
404 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
405 ························}
406 ····················}
407 ················}
408 ············}
409
410 ········}
411 ········#endregion
412
413 ········#region Implementation of IPersistenceManager
414
415 ········// Complete documentation can be found in IPersistenceManager
416
417
418 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
419 ········{
420 ············if (!cl.Oid.IsDependent)
421 ················return;
422 ············WriteForeignKeysToRow(pc, row);
423 ········}
424
425 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
426 ········{
427 ············// Object is now under control of the state manager
428 ············pc.NDOStateManager = sm;
429
430 ············Type pcType = pc.GetType();
431 ············Class pcClass = GetClass(pc);
432
433 ············// Create new object
434 ············DataTable dt = GetTable(pcType);
435 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
436
437 ············// In case of a Guid oid the value will be computed now.
438 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
439 ············{
440 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
441 ················{
442 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
443 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
444 ····················else
445 ························row[oidColumn.Name] = Guid.NewGuid();
446 ················}
447 ············}
448
449 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
450
451 ············// If the object is merged from an ObjectContainer, the id should be reused,
452 ············// if the id is client generated (not Autoincremented).
453 ············// In every other case, the oid is set to null, to force generating a new oid.
454 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
455 ············if ((object)pc.NDOObjectId != null)
456 ············{
457 ················bool hasAutoincrement = false;
458 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
459 ················{
460 ····················if (oidColumn.AutoIncremented)
461 ····················{
462 ························hasAutoincrement = true;
463 ························break;
464 ····················}
465 ················}
466 ················if (hasAutoincrement) // can't store existing id
467 ················{
468 ····················pc.NDOObjectId = null;
469 ····················fireIdGeneration = true;
470 ················}
471 ············}
472
473 ············// In case of a dependent class the oid has to be read from the fields according to the relations
474 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
475
476 ············if ((object)pc.NDOObjectId == null)
477 ············{
478 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
479 ············}
480
481 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
482 ············{
483 ················if (fireIdGeneration)
484 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
485 ················// At this place the oid might have been
486 ················// - deserialized (MergeObjectContainer)
487 ················// - created using NewObjectId
488 ················// - defined by the IdGenerationEvent
489
490 ················// At this point we have a valid oid.
491 ················// If the object has a field mapped to the oid we have
492 ················// to write back the oid to the field
493 ················int i = 0;
494 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
495 ················{
496 ····················if (oidColumn.FieldName != null)
497 ····················{
498 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
499 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
500 ····················}
501 ····················i++;
502 ················});
503
504
505
506 ················// Now write back the data into the row
507 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
508 ············}
509
510 ············
511 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
512 ············dt.Rows.Add(row);
513
514 ············cache.Register(pc);
515
516 ············// new object that has never been written to the DS
517 ············pc.NDOObjectState = NDOObjectState.Created;
518 ············// Mark all Relations as loaded
519 ············SetRelationState(pc);
520
521 ············if (checkRelations)
522 ············{
523 ················// Handle related objects:
524 ················foreach(Relation r in pcClass.Relations)
525 ················{
526 ····················if (r.Multiplicity == RelationMultiplicity.Element)
527 ····················{
528 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
529 ························if(child != null)
530 ························{
531 ····························AddRelatedObject(pc, r, child);
532 ························}
533 ····················}
534 ····················else
535 ····················{
536 ························IList list = mappings.GetRelationContainer(pc, r);
537 ························if(list != null)
538 ························{
539 ····························foreach(IPersistenceCapable relObj in list)
540 ····························{
541 ································if (relObj != null)
542 ····································AddRelatedObject(pc, r, relObj);
543 ····························}
544 ························}
545 ····················}
546 ················}
547 ············}
548
549 ············var relations··= CollectRelationStates(pc);
550 ············cache.Lock(pc, row, relations);
551 ········}
552
553
554 ········/// <summary>
555 ········/// Make an object persistent.
556 ········/// </summary>
557 ········/// <param name="o">the transient object that should be made persistent</param>
558 ········public void MakePersistent(object o)
559 ········{
560 ············IPersistenceCapable pc = CheckPc(o);
561
562 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
563 ············//Debug.Indent();
564
565 ············if (pc.NDOObjectState != NDOObjectState.Transient)
566 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
567
568 ············InternalMakePersistent(pc, true);
569
570 ········}
571
572
573
574 ········//········/// <summary>
575 ········//········/// Checks, if an object has a valid id, which was created by the database
576 ········//········/// </summary>
577 ········//········/// <param name="pc"></param>
578 ········//········/// <returns></returns>
579 ········//········private bool HasValidId(IPersistenceCapable pc)
580 ········//········{
581 ········//············if (this.IdGenerationEvent != null)
582 ········//················return true;
583 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
584 ········//········}
585
586
587 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
588 ········{
589 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
590 ············if(r.HasSubclasses)
591 ············{
592 ················// we don't support 1:n with foreign fields in subclasses because we would have to
593 ················// search for objects in all subclasses! Instead use a mapping table.
594 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
595 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
596 ············}
597
598 ············if (!makeRelObjPersistent)
599 ················MarkDirty(relObj);
600 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
601 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
602
603 ············if (relObjRow == null)
604 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
605
606 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
607
608 ············if (relObj.NDOLoadState.LostRowInfo == null)
609 ············{
610 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
611 ············}
612 ············else
613 ············{
614 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
615 ············}
616 ········}
617
618 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
619 ········{
620 ············switch(relObj.NDOObjectState)
621 ············{
622 ················case NDOObjectState.Persistent:
623 ····················MarkDirty(relObj);
624 ····················break;
625 ················case NDOObjectState.Hollow:
626 ····················LoadData(relObj);
627 ····················MarkDirty(relObj);
628 ····················break;
629 ············}
630
631 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
632 ············{
633 ················IPersistenceCapable newpc;
634 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
635 ················{
636 ····················if (newpc != pc)
637 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
638 ················}
639 ················else
640 ················{
641 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
642 ················}
643 ············}
644 ············else
645 ············{
646 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
647 ····················LoadRelation(relObj, r.ForeignRelation, true);
648 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
649 ················if(l == null)
650 ················{
651 ····················try
652 ····················{
653 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
654 ····················}
655 ····················catch
656 ····················{
657 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
658 ····················}
659 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
660 ················}
661 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
662 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
663 ················if (!ObjectListManipulator.Contains(l, pc))
664 ····················l.Add(pc);
665 ············}
666 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
667 ········}
668
669
670 ········/// <summary>
671 ········/// Add a related object to the specified object.
672 ········/// </summary>
673 ········/// <param name="pc">the parent object</param>
674 ········/// <param name="fieldName">the field name of the relation</param>
675 ········/// <param name="relObj">the related object that should be added</param>
676 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
677 ········{
678 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
679 ············Relation r = mappings.FindRelation(pc, fieldName);
680 ············AddRelatedObject(pc, r, relObj);
681 ········}
682
683 ········/// <summary>
684 ········/// Core functionality to add an object to a relation container or relation field.
685 ········/// </summary>
686 ········/// <param name="pc"></param>
687 ········/// <param name="r"></param>
688 ········/// <param name="relObj"></param>
689 ········/// <param name="isMerging"></param>
690 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
691 ········{
692 ············
693 ············// avoid recursion
694 ············if (!addLock.GetLock(relObj))
695 ················return;
696
697 ············try
698 ············{
699 ················//TODO: We need a relation management, which is independent of
700 ················//the state management of an object. Currently the relation
701 ················//lists or elements are cached for restore, if an object is marked dirty.
702 ················//Thus we have to mark dirty our parent object in any case at the moment.
703 ················MarkDirty(pc);
704
705 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
706 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
707 ················//The latter happens in CreateAddedObjectRow
708
709 ················Class relClass = GetClass(relObj);
710
711 ················if (r.Multiplicity == RelationMultiplicity.Element
712 ····················&& r.HasSubclasses
713 ····················&& r.MappingTable == null················
714 ····················&& !this.HasOwnerCreatedIds
715 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
716 ····················&& !relClass.HasGuidOid)
717 ················{
718 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
719 ························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.");
720 ····················if (r.Composition)
721 ························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.");
722 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
723 ························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." );
724 ················}
725
726 ················bool isDependent = relClass.Oid.IsDependent;
727
728 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
729 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
730
731 ················// Need to patch pc into the relation relObj->pc, because
732 ················// the oid is built on base of this information
733 ················if (isDependent)
734 ················{
735 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
736 ················}
737
738 ················if (r.Composition || isDependent)
739 ················{
740 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
741 ························MakePersistent(relObj);
742 ················}
743
744 ················if(r.MappingTable == null)
745 ················{
746 ····················if (r.Bidirectional)
747 ····················{
748 ························// This object hasn't been saved yet, so the key is wrong.
749 ························// Therefore, the child must be written twice to update the foreign key.
750 #if trace
751 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
752 ························if (r.Multiplicity == RelationMultiplicity.Element)
753 ····························sb.Append("1");
754 ························else
755 ····························sb.Append("n");
756 ························sb.Append(":");
757 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
758 ····························sb.Append("1");
759 ························else
760 ····························sb.Append("n");
761 ························sb.Append ("OwnCreatedOther");
762 ························sb.Append(relObj.NDOObjectState.ToString());
763 ························sb.Append(' ');
764
765 ························sb.Append(types[0].ToString());
766 ························sb.Append(' ');
767 ························sb.Append(types[1].ToString());
768 ························Debug.WriteLine(sb.ToString());
769 #endif
770 ························//························if (r.Multiplicity == RelationMultiplicity.Element
771 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
772 ························//························{
773 ························// Element means:
774 ························// pc is keyholder
775 ························// -> relObj is saved first
776 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
777 ························// Both are Created - use type sort order
778 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
779 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
780 ························{
781 ····························if (mappings.GetUpdateOrder(pc.GetType())
782 ································< mappings.GetUpdateOrder(relObj.GetType()))
783 ································createdDirectObjects.Add(pc);
784 ····························else
785 ································createdDirectObjects.Add( relObj );
786 ························}
787 ····················}
788 ····················if (r.Multiplicity == RelationMultiplicity.List)
789 ····················{
790 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
791 ····················}
792 ················}
793 ················else
794 ················{
795 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
796 ················}
797 ················if(r.Bidirectional)
798 ················{
799 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
800 ····················{
801 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
802 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
803 ····················}
804 ····················else if ( !addLock.IsLocked( pc ) )
805 ····················{
806 ························PatchForeignRelation( pc, r, relObj );
807 ····················}
808 ················}
809
810 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
811 ············}
812 ············finally
813 ············{
814 ················addLock.Unlock(relObj);
815 ················//Debug.Unindent();
816 ············}
817 ········}
818
819 ········/// <summary>
820 ········/// Returns an integer value which determines the rank of the given type in the update order list.
821 ········/// </summary>
822 ········/// <param name="t">The type to determine the update order.</param>
823 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
824 ········/// <remarks>
825 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
826 ········/// </remarks>
827 ········public int GetUpdateRank(Type t)
828 ········{
829 ············return mappings.GetUpdateOrder(t);
830 ········}
831
832 ········/// <summary>
833 ········/// Add a related object to the specified object.
834 ········/// </summary>
835 ········/// <param name="pc">the parent object</param>
836 ········/// <param name="r">the relation mapping info</param>
837 ········/// <param name="relObj">the related object that should be added</param>
838 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
839 ········{
840 ············//············string idstr;
841 ············//············if (relObj.NDOObjectId == null)
842 ············//················idstr = relObj.GetType().ToString();
843 ············//············else
844 ············//················idstr = relObj.NDOObjectId.Dump();
845 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
846 ············//Debug.Indent();
847
848 ············Class relClass = GetClass(relObj);
849 ············bool isDependent = relClass.Oid.IsDependent;
850
851 ············// Do some checks to guarantee that the assignment is correct
852 ············if(r.Composition)
853 ············{
854 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
855 ················{
856 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
857 ················}
858 ············}
859 ············else
860 ············{
861 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
862 ················{
863 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
864 ················}
865 ············}
866
867 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
868 ············{
869 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
870 ············}
871
872 ············InternalAddRelatedObject(pc, r, relObj, false);
873
874 ········}
875
876 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
877 ········{
878 ············// Need to patch pc into the relation relObj->pc, because
879 ············// the oid is built on base of this information
880 ············// The second relation has to be set before adding relObj
881 ············// to the relation list.
882 ············PatchForeignRelation(pc, r, relObj);
883 ············IPersistenceCapable parent;
884 ············foreach (Relation oidRelation in relClass.Oid.Relations)
885 ············{
886 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
887 ················if (parent == null)
888 ····················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.");
889 ················if (parent.NDOObjectState == NDOObjectState.Transient)
890 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
891
892 ············}
893 ········}
894
895
896 ········/// <summary>
897 ········/// Remove a related object from the specified object.
898 ········/// </summary>
899 ········/// <param name="pc">the parent object</param>
900 ········/// <param name="fieldName">Field name of the relation</param>
901 ········/// <param name="relObj">the related object that should be removed</param>
902 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
903 ········{
904 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
905 ············Relation r = mappings.FindRelation(pc, fieldName);
906 ············InternalRemoveRelatedObject(pc, r, relObj, true);
907 ········}
908
909 ········/// <summary>
910 ········/// Registers a listener which will be notified, if a new connection is opened.
911 ········/// </summary>
912 ········/// <param name="listener">Delegate of a listener function</param>
913 ········/// <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>
914 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
915 ········{
916 ············this.openConnectionListener = listener;
917 ········}
918
919 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
920 ········{
921 ············if (openConnectionListener != null)
922 ················return openConnectionListener(conn);
923 ············return conn.Name;
924 ········}
925
926
927 ········/*
928 ········doCommit should be:
929 ········
930 ····················Query····Save····Save(true)
931 ········Optimistic····1········1········0
932 ········Pessimistic····0········1········0
933 ············
934 ········Deferred Mode············
935 ····················Query····Save····Save(true)
936 ········Optimistic····0········1········0
937 ········Pessimistic····0········1········0
938 ········ */
939
940 ········internal void CheckEndTransaction(bool doCommit)
941 ········{
942 ············if (doCommit)
943 ············{
944 ················TransactionScope.Complete();
945 ············}
946 ········}
947
948 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
949 ········{
950 ············CheckTransaction(handler, this.GetClass(t).Connection);
951 ········}
952
953 ········/// <summary>
954 ········/// Each and every database operation has to be preceded by a call to this function.
955 ········/// </summary>
956 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
957 ········{
958 ············TransactionScope.CheckTransaction();
959 ············
960 ············if (handler.Connection == null)
961 ············{
962 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
963 ················{
964 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
965 ····················string connStr = this.OnNewConnection( ndoConn );
966 ····················var connection = p.NewConnection( connStr );
967 ····················if (connection == null)
968 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
969 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
970 ····················return connection;
971 ················} );
972 ············}
973
974 ············if (TransactionMode != TransactionMode.None)
975 ············{
976 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
977 ············}
978
979 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
980 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
981 ············{
982 ················handler.Connection.Open();
983 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
984 ············}
985 ········}
986
987 ········/// <summary>
988 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
989 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
990 ········/// This is possible if there is a listener for the CollisionEvent.
991 ········/// Else we throw an exception.
992 ········/// </summary>
993 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
994 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
995 ········{
996 ············DataRow row = ex.Row;
997 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
998 ················throw(ex);
999 ············if (row.RowState == DataRowState.Detached)
1000 ················return;
1001 ············foreach (Cache.Entry e in cache.LockedObjects)
1002 ············{
1003 ················if (e.row == row)
1004 ················{
1005 ····················CollisionEvent(e.pc);
1006 ····················return;
1007 ················}
1008 ············}
1009 ············throw ex;
1010 ········}
1011
1012
1013 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1014 ········{
1015 ············Class cl = GetClass(pc);
1016 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1017 ············Dictionary<string,MemberInfo> persistentFields = null;
1018 ············if (etypes.Length > 0)
1019 ············{
1020 ················FieldMap fm = new FieldMap(cl);
1021 ················persistentFields = fm.PersistentFields;
1022 ············}
1023 ············foreach(string s in etypes)
1024 ············{
1025 ················try
1026 ················{
1027 ····················NDO.Mapping.Field f = cl.FindField(s);
1028 ····················if (f == null)
1029 ························continue;
1030 ····················object o = row[f.Column.Name];
1031 ····················string[] arr = s.Split('.');
1032 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1033 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1034 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1035 ····················// Hole das Embedded Object
1036 ····················object parentOb = parentFi.GetValue(pc);
1037
1038 ····················if (parentOb == null)
1039 ························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]));
1040
1041 ····················// Suche darin das Feld mit Namen Arr[1]
1042
1043 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1044 ····················Type childType = childFi.FieldType;
1045
1046 ····················// Don't initialize value types, if DBNull is stored in the field.
1047 ····················// Exception: DateTime and Guid.
1048 ····················if (o == DBNull.Value && childType.IsValueType
1049 ························&& childType != typeof(Guid)
1050 ························&& childType != typeof(DateTime))
1051 ························continue;
1052
1053 ····················if (childType == typeof(DateTime))
1054 ····················{
1055 ························if (o == DBNull.Value)
1056 ····························o = DateTime.MinValue;
1057 ····················}
1058 ····················if (childType.IsClass)
1059 ····················{
1060 ························if (o == DBNull.Value)
1061 ····························o = null;
1062 ····················}
1063
1064 ····················if (childType == typeof (Guid))
1065 ····················{
1066 ························if (o == DBNull.Value)
1067 ····························o = Guid.Empty;
1068 ························if (o is string)
1069 ························{
1070 ····························childFi.SetValue(parentOb, new Guid((string)o));
1071 ························}
1072 ························else if (o is Guid)
1073 ························{
1074 ····························childFi.SetValue(parentOb, o);
1075 ························}
1076 ························else if (o is byte[])
1077 ························{
1078 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1079 ························}
1080 ························else
1081 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1082 ····················}
1083 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1084 ····················{
1085 ························object childOb = childFi.GetValue(parentOb);
1086 ························FieldInfo valueFi = childType.GetField("value__");
1087 ························valueFi.SetValue(childOb, o);
1088 ························childFi.SetValue(parentOb, childOb);
1089 ····················}
1090 ····················else
1091 ····················{
1092 ························childFi.SetValue(parentOb, o);
1093 ····················}
1094 ················}
1095 ················catch (Exception ex)
1096 ················{
1097 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1098
1099 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1100 ················}
1101
1102 ············}
1103 ············
1104 ············try
1105 ············{
1106 ················if (cl.HasEncryptedFields)
1107 ················{
1108 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1109 ····················{
1110 ························string name = field.Column.Name;
1111 ························string s = (string) row[name];
1112 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1113 ························row[name] = es;
1114 ····················}
1115 ················}
1116 ················pc.NDORead(row, fieldNames, startIndex);
1117 ············}
1118 ············catch (Exception ex)
1119 ············{
1120 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1121 ····················+ ex.Message);
1122 ············}
1123 ········}
1124
1125 ········/// <summary>
1126 ········/// Executes a sql script to generate the database tables.
1127 ········/// The function will execute any sql statements in the script
1128 ········/// which are valid according to the
1129 ········/// rules of the underlying database. Result sets are ignored.
1130 ········/// </summary>
1131 ········/// <param name="scriptFile">The script file to execute.</param>
1132 ········/// <param name="conn">A connection object, containing the connection
1133 ········/// string to the database, which should be altered.</param>
1134 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1135 ········/// <remarks>
1136 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1137 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1138 ········/// Their message property will appear in the result array.
1139 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1140 ········/// </remarks>
1141 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1142 ········{
1143 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1144 ········}
1145
1146 ········/// <summary>
1147 ········/// Executes a sql script to generate the database tables.
1148 ········/// The function will execute any sql statements in the script
1149 ········/// which are valid according to the
1150 ········/// rules of the underlying database. Result sets are ignored.
1151 ········/// </summary>
1152 ········/// <param name="scriptFile">The script file to execute.</param>
1153 ········/// <param name="conn">A connection object, containing the connection
1154 ········/// string to the database, which should be altered.</param>
1155 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1156 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1157 ········/// <remarks>
1158 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1159 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1160 ········/// Their message property will appear in the result array.
1161 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1162 ········/// </remarks>
1163 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1164 ········{
1165 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1166 ············string s = sr.ReadToEnd();
1167 ············sr.Close();
1168 ············string[] arr = s.Split(';');
1169 ············string last = arr[arr.Length - 1];
1170 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1171 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1172 ············using (var handler = GetSqlPassThroughHandler())
1173 ············{
1174 ················int i = 0;
1175 ················string ok = "OK";
1176 ················foreach (string statement in arr)
1177 ················{
1178 ····················if (statement != null && statement.Trim() != string.Empty)
1179 ····················{
1180 ························try
1181 ························{
1182 ····························handler.Execute(statement);
1183 ····························result[i] = ok;
1184 ························}
1185 ························catch (Exception ex)
1186 ························{
1187 ····························result[i] = ex.Message;
1188 ························}
1189 ····················}
1190 ····················i++;
1191 ················}
1192 ················CheckEndTransaction(true);
1193 ············}
1194 ············return result;
1195 ········}
1196
1197 ········/// <summary>
1198 ········/// Executes a sql script to generate the database tables.
1199 ········/// The function will execute any sql statements in the script
1200 ········/// which are valid according to the
1201 ········/// rules of the underlying database. Result sets are ignored.
1202 ········/// </summary>
1203 ········/// <param name="scriptFile">The script file to execute.</param>
1204 ········/// <returns></returns>
1205 ········/// <remarks>
1206 ········/// This function takes the first Connection object in the Connections list
1207 ········/// of the Mapping file und executes the script using that connection.
1208 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1209 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1210 ········/// Their message property will appear in the result array.
1211 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1212 ········/// </remarks>
1213 ········public string[] BuildDatabase(string scriptFile)
1214 ········{
1215 ············if (!File.Exists(scriptFile))
1216 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1217 ············if (!this.mappings.Connections.Any())
1218 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1219 ············Connection conn = new Connection( this.mappings );
1220 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1221 ············conn.Name = OnNewConnection( originalConnection );
1222 ············conn.Type = originalConnection.Type;
1223 ············//Connection conn = (Connection) this.mappings.Connections[0];
1224 ············return BuildDatabase(scriptFile, conn);
1225 ········}
1226
1227 ········/// <summary>
1228 ········/// Executes a sql script to generate the database tables.
1229 ········/// The function will execute any sql statements in the script
1230 ········/// which are valid according to the
1231 ········/// rules of the underlying database. Result sets are ignored.
1232 ········/// </summary>
1233 ········/// <returns>
1234 ········/// A string array, containing the error messages produced by the statements
1235 ········/// contained in the script.
1236 ········/// </returns>
1237 ········/// <remarks>
1238 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1239 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1240 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1241 ········/// This function takes the first Connection object in the Connections list
1242 ········/// of the Mapping file und executes the script using that connection.
1243 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1244 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1245 ········/// Their message property will appear in the result array.
1246 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1247 ········/// </remarks>
1248 ········public string[] BuildDatabase()
1249 ········{
1250 ············Assembly ass = Assembly.GetEntryAssembly();
1251 ············if (ass == null)
1252 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1253 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1254 ············return BuildDatabase(file);
1255 ········}
1256
1257 ········/// <summary>
1258 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1259 ········/// </summary>
1260 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1261 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1262 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1263 ········{
1264 ············if (!this.mappings.Connections.Any())
1265 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1266 ············if (conn == null)
1267 ············{
1268 ················conn = new Connection( this.mappings );
1269 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1270 ················conn.Name = OnNewConnection( originalConnection );
1271 ················conn.Type = originalConnection.Type;
1272 ············}
1273
1274 ············return new SqlPassThroughHandler( this, conn );
1275 ········}
1276
1277 ········/// <summary>
1278 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1279 ········/// </summary>
1280 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1281 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1282 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1283 ········{
1284 ············if (!this.mappings.Connections.Any())
1285 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1286 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1287 ············if (conn == null)
1288 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1289 ············return GetSqlPassThroughHandler( conn );
1290 ········}
1291
1292 ········/// <summary>
1293 ········/// Executes a xml script to generate the database tables.
1294 ········/// The function will generate and execute sql statements to perform
1295 ········/// the changes described by the xml.
1296 ········/// </summary>
1297 ········/// <returns></returns>
1298 ········/// <remarks>
1299 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1300 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1301 ········/// This function takes the first Connection object in the Connections list
1302 ········/// of the Mapping file und executes the script using that connection.
1303 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1304 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1305 ········/// Their message property will appear in the result string array.
1306 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1307 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1308 ········/// </remarks>
1309 ········public string[] PerformSchemaTransitions()
1310 ········{
1311 ············Assembly ass = Assembly.GetEntryAssembly();
1312 ············if (ass == null)
1313 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1314 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1315 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1316 ············if (fileNames.Count == 0)
1317 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1318 ············if (fileNames.Count > 1)
1319 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1320 ············return PerformSchemaTransitions( fileNames[0] );
1321 ········}
1322
1323
1324 ········/// <summary>
1325 ········/// Executes a xml script to generate the database tables.
1326 ········/// The function will generate and execute sql statements to perform
1327 ········/// the changes described by the xml.
1328 ········/// </summary>
1329 ········/// <param name="scriptFile">The script file to execute.</param>
1330 ········/// <returns></returns>
1331 ········/// <remarks>
1332 ········/// This function takes the first Connection object in the Connections list
1333 ········/// of the Mapping file und executes the script using that connection.
1334 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1335 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1336 ········/// Their message property will appear in the result string array.
1337 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1338 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1339 ········/// </remarks>
1340 ········public string[] PerformSchemaTransitions(string scriptFile)
1341 ········{
1342 ············if (!File.Exists(scriptFile))
1343 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1344
1345 ············if (!this.mappings.Connections.Any())
1346 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1347 ············Connection conn = new Connection( mappings );
1348 ············Connection originalConnection = mappings.Connections.First();
1349 ············conn.Name = OnNewConnection( originalConnection );
1350 ············conn.Type = originalConnection.Type;
1351 ············return PerformSchemaTransitions(scriptFile, conn);
1352 ········}
1353
1354
1355 ········int CompareFileName( string fn1, string fn2)
1356 ········{
1357 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1358 ············Match match = regex.Match( fn1 );
1359 ············string v1 = match.Groups[1].Value;
1360 ············match = regex.Match( fn2 );
1361 ············string v2 = match.Groups[1].Value;
1362 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1363 ········}
1364
1365 ········string GetSchemaVersion(Connection ndoConn, string schemaName)
1366 ········{
1367 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1368 ············string version = "0.0";··// Initial value
1369 ············var connection = provider.NewConnection( ndoConn.Name );
1370 ············using (var handler = GetSqlPassThroughHandler())
1371 ············{
1372 ················string[] TableNames = provider.GetTableNames( connection );
1373 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaVersion", true ) == 0 ))
1374 ················{
1375 ····················string sql = "SELECT Version from NDOSchemaVersion WHERE SchemaName ";
1376 ····················if (schemaName == null)
1377 ························sql += "IS NULL;";
1378 ····················else
1379 ························sql += "LIKE '" + schemaName + "'";
1380 ····················using(IDataReader dr = handler.Execute(sql, true))
1381 ····················{
1382 ························if (dr.Read())
1383 ····························version = dr.GetString( 0 );
1384 ····················}
1385 ················}
1386 ················else
1387 ················{
1388 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1389 ····················string transition = @"<NdoSchemaTransition>
1390 ····<CreateTable name=""NDOSchemaVersion"">
1391 ······<CreateColumn name=""SchemaName"" type=""System.String,mscorlib"" allowNull=""True"" />
1392 ······<CreateColumn name=""Version"" type=""System.String,mscorlib"" size=""50"" />
1393 ····</CreateTable>
1394 </NdoSchemaTransition>";
1395 ····················XElement transitionElement = XElement.Parse(transition);
1396
1397 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1398 ····················handler.Execute(sql);
1399 ····················var colSchemaName = provider.GetQuotedName("SchemaName");
1400 ····················var colVersion = provider.GetQuotedName("Version");
1401 ····················var schemaVal = schemaName == null ? "NULL" : provider.GetSqlLiteral( schemaName );
1402 ····················sql = String.Format( $"INSERT INTO NDOSchemaVersion({colSchemaName},{colVersion}) VALUES({schemaVal},'0')" );
1403 ····················handler.Execute( sql );
1404 ····················handler.CommitTransaction();
1405 ················}
1406 ············}
1407
1408 ············return version;
1409 ········}
1410
1411 ········/// <summary>
1412 ········/// Executes a xml script to generate the database tables.
1413 ········/// The function will generate and execute sql statements to perform
1414 ········/// the changes described by the xml.
1415 ········/// </summary>
1416 ········/// <param name="scriptFile">The xml script file.</param>
1417 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1418 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1419 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1420 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1421 ········{
1422 ············string schemaName = null;
1423 ············// Gespeicherte Version ermitteln.
1424 ············XElement transitionElements = XElement.Load( scriptFile );
1425 ············if (transitionElements.Attribute( "schemaName" ) != null)
1426 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1427 ············Version version = new Version( GetSchemaVersion( ndoConn, schemaName ) );
1428 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1429 ············MemoryStream ms = new MemoryStream();
1430 ············StreamWriter sw = new StreamWriter(ms, System.Text.Encoding.UTF8);
1431 ············bool hasChanges = false;
1432
1433 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition").Where(e=>new Version(e.Attribute("schemaVersion").Value).CompareTo(version) > 0))
1434 ············{
1435 ················hasChanges = true;
1436 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1437 ············}
1438
1439 ············if (!hasChanges)
1440 ················return new string[] { };
1441
1442 ············sw.Write( "UPDATE NDOSchemaVersion SET Version = '" );
1443 ············sw.Write( transitionElements.Attribute( "schemaVersion" ).Value );
1444 ············sw.Write( "' WHERE SchemaName " );
1445 ············if (schemaName == null)
1446 ················sw.WriteLine( "IS NULL;" );
1447 ············else
1448 ················sw.WriteLine( "LIKE '" + schemaName + "'" );············
1449
1450 ············sw.Flush();
1451 ············ms.Position = 0L;
1452
1453 ············StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);
1454 ············string s = sr.ReadToEnd();
1455 ············sr.Close();
1456
1457 ············return InternalPerformSchemaTransitions( ndoConn, s );
1458 ········}
1459
1460 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1461 ········{
1462 ············string[] arr = sql.Split( ';' );
1463 ············string last = arr[arr.Length - 1];
1464 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1465 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1466 ············int i = 0;
1467 ············string ok = "OK";
1468 ············using (var handler = GetSqlPassThroughHandler())
1469 ············{
1470 ················foreach (string statement in arr)
1471 ················{
1472 ····················if (statement != null && statement.Trim() != string.Empty)
1473 ····················{
1474 ························try
1475 ························{
1476 ····························handler.Execute( statement );
1477 ····························result[i] = ok;
1478 ························}
1479 ························catch (Exception ex)
1480 ························{
1481 ····························result[i] = ex.Message;
1482 ························}
1483 ····················}
1484 ····················i++;
1485 ················}
1486
1487 ················handler.CommitTransaction();
1488 ············}
1489 ············return result;
1490 ········}
1491 ········
1492 ········/// <summary>
1493 ········/// Transfers Data from the object to the DataRow
1494 ········/// </summary>
1495 ········/// <param name="pc"></param>
1496 ········/// <param name="row"></param>
1497 ········/// <param name="fieldNames"></param>
1498 ········/// <param name="startIndex"></param>
1499 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1500 ········{
1501 ············Class cl = GetClass( pc );
1502 ············try
1503 ············{
1504 ················pc.NDOWrite(row, fieldNames, startIndex);
1505 ················if (cl.HasEncryptedFields)
1506 ················{
1507 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1508 ····················{
1509 ························string name = field.Column.Name;
1510 ························string s = (string) row[name];
1511 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1512 ························row[name] = es;
1513 ····················}
1514 ················}
1515 ············}
1516 ············catch (Exception ex)
1517 ············{
1518 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1519 ····················+ ex.Message);
1520 ············}
1521
1522 ············if (cl.TypeNameColumn != null)
1523 ············{
1524 ················Type t = pc.GetType();
1525 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1526 ············}
1527
1528 ············var etypes = cl.EmbeddedTypes;
1529 ············foreach(string s in etypes)
1530 ············{
1531 ················try
1532 ················{
1533 ····················NDO.Mapping.Field f = cl.FindField(s);
1534 ····················if (f == null)
1535 ························continue;
1536 ····················string[] arr = s.Split('.');
1537 ····················// Suche Feld mit Namen arr[0] als object
1538 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1539 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1540 ····················Object parentOb = parentFi.GetValue(pc);
1541 ····················if (parentOb == null)
1542 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1543 ····················// Suche darin das Feld mit Namen Arr[1]
1544 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1545 ····················object o = childFi.GetValue(parentOb);
1546 ····················if (o == null
1547 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1548 ························|| o is Guid && (Guid) o == Guid.Empty)
1549 ························o = DBNull.Value;
1550 ····················row[f.Column.Name] = o;
1551 ················}
1552 ················catch (Exception ex)
1553 ················{
1554 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1555
1556 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1557 ················}
1558 ············}
1559 ········}
1560
1561 ········/// <summary>
1562 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1563 ········/// </summary>
1564 ········/// <param name="o">The parent object.</param>
1565 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1566 ········public virtual void LoadField(object o, int fieldOrdinal)
1567 ········{
1568 ············IPersistenceCapable pc = CheckPc(o);
1569 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1570 ············{
1571 ················LoadState ls = pc.NDOLoadState;
1572 ················if (ls.FieldLoadState != null)
1573 ················{
1574 ····················if (ls.FieldLoadState[fieldOrdinal])
1575 ························return;
1576 ················}
1577 ················else
1578 ················{
1579 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1580 ················}
1581 ················LoadData(o);
1582 ········}
1583 ········}
1584
1585 #pragma warning disable 419
1586 ········/// <summary>
1587 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1588 ········/// </summary>
1589 ········/// <param name="o">The hollow object.</param>
1590 ········/// <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>
1591 #pragma warning restore 419
1592 ········public virtual void LoadData( object o )
1593 ········{
1594 ············IPersistenceCapable pc = CheckPc(o);
1595 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1596 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1597 ················return;
1598 ············Class cl = GetClass(pc);
1599 ············IQuery q;
1600 ············q = CreateOidQuery(pc, cl);
1601 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1602
1603 ············var objects = q.Execute();
1604 ············var count = objects.Count;
1605
1606 ············if (count > 1)
1607 ············{
1608 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1609 ············}
1610 ············else if (count == 0)
1611 ············{
1612 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1613 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1614 ············}
1615 ········}
1616
1617 ········/// <summary>
1618 ········/// Creates a new IQuery object for the given type
1619 ········/// </summary>
1620 ········/// <param name="t"></param>
1621 ········/// <param name="oql"></param>
1622 ········/// <param name="hollow"></param>
1623 ········/// <param name="queryLanguage"></param>
1624 ········/// <returns></returns>
1625 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1626 ········{
1627 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1628 ············Type qt = template.MakeGenericType( t );
1629 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1630 ········}
1631
1632 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1633 ········{
1634 ············ArrayList parameters = new ArrayList();
1635 ············string oql = "oid = {0}";
1636 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1637 ············q.Parameters.Add( pc.NDOObjectId );
1638 ············q.AllowSubclasses = false;
1639 ············return q;
1640 ········}
1641
1642 ········/// <summary>
1643 ········/// Mark the object dirty. The current state is
1644 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1645 ········/// object is locked in the cache.
1646 ········/// </summary>
1647 ········/// <param name="pc"></param>
1648 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1649 ········{
1650 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1651 ················return;
1652 ············SaveObjectState(pc);
1653 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1654 ········}
1655
1656 ········/// <summary>
1657 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1658 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1659 ········/// This is done, to allow easy rollback later. Also, the
1660 ········/// object is locked in the cache.
1661 ········/// </summary>
1662 ········/// <param name="pc"></param>
1663 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1664 ········{
1665 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1666
1667 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1668 ············{
1669 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1670 ············}
1671
1672 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1673 ················LoadData(pc);
1674
1675 ············// state is either (Created), Persistent, PersistentDirty
1676 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1677 ············{
1678 ················MarkDirty(pc);
1679 ············}
1680 ········}
1681
1682
1683
1684 ········/// <summary>
1685 ········/// Save current object state in DS and lock the object in the cache.
1686 ········/// The saved state can be used later to retrieve the original object value if the
1687 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1688 ········/// </summary>
1689 ········/// <param name="pc">The object that should be saved</param>
1690 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1691 ········/// <remarks>
1692 ········/// In a data row there are the following things:
1693 ········/// Item································Responsible for writing
1694 ········/// State (own, inherited, embedded)····WriteObject
1695 ········/// TimeStamp····························NDOPersistenceHandler
1696 ········/// Oid····································WriteId
1697 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1698 ········/// </remarks>
1699 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1700 ········{
1701 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1702 ············
1703 ············DataTable table = GetTable(pc);
1704 ············DataRow row = table.NewRow();
1705 ············Class cl = GetClass(pc);
1706 ············WriteObject(pc, row, cl.ColumnNames, 0);
1707 ············WriteIdToRow(pc, row);
1708 ············if (!isDeleting)
1709 ················WriteLostForeignKeysToRow(cl, pc, row);
1710 ············table.Rows.Add(row);
1711 ············row.AcceptChanges();
1712 ············
1713 ············var relations = CollectRelationStates(pc);
1714 ············cache.Lock(pc, row, relations);
1715 ········}
1716
1717 ········private void SaveFakeRow(IPersistenceCapable pc)
1718 ········{
1719 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1720 ············
1721 ············DataTable table = GetTable(pc);
1722 ············DataRow row = table.NewRow();
1723 ············Class pcClass = GetClass(pc);
1724 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1725 ············Class cl = GetClass(pc);
1726 ············//WriteObject(pc, row, cl.FieldNames, 0);
1727 ············WriteIdToRow(pc, row);
1728 ············table.Rows.Add(row);
1729 ············row.AcceptChanges();
1730 ············
1731 ············cache.Lock(pc, row, null);
1732 ········}
1733
1734 ········/// <summary>
1735 ········/// This defines one column of the row, in which we use the
1736 ········/// ColumnError property to determine, if the row is a fake row.
1737 ········/// </summary>
1738 ········/// <param name="pcClass"></param>
1739 ········/// <returns></returns>
1740 ········private string GetFakeRowOidColumnName(Class pcClass)
1741 ········{
1742 ············// In case of several OidColumns the first column defined in the mapping
1743 ············// will be the one, holding the fake row info.
1744 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1745 ········}
1746
1747 ········private bool IsFakeRow(Class cl, DataRow row)
1748 ········{
1749 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1750 ········}
1751
1752 ········/// <summary>
1753 ········/// Make a list of objects persistent.
1754 ········/// </summary>
1755 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1756 ········public void MakePersistent(System.Collections.IList list)
1757 ········{
1758 ············foreach (IPersistenceCapable pc in list)
1759 ············{
1760 ················MakePersistent(pc);
1761 ············}
1762 ········}
1763
1764 ········/// <summary>
1765 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1766 ········/// not duplicated.
1767 ········/// </summary>
1768 ········/// <param name="pc">the parent object of all relations</param>
1769 ········/// <returns></returns>
1770 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1771 ········{
1772 ············// Save state of relations
1773 ············Class c = GetClass(pc);
1774 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1775 ············foreach(Relation r in c.Relations)
1776 ············{
1777 ················if (r.Multiplicity == RelationMultiplicity.Element)
1778 ················{
1779 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1780 ················}
1781 ················else
1782 ················{
1783 ····················IList l = mappings.GetRelationContainer(pc, r);
1784 ····················if(l != null)
1785 ····················{
1786 ························l = (IList) ListCloner.CloneList(l);
1787 ····················}
1788 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1789 ················}
1790 ············}
1791
1792 ············return relations;
1793 ········}
1794
1795
1796 ········/// <summary>
1797 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1798 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1799 ········/// the list had at the beginning of the transaction.
1800 ········/// </summary>
1801 ········/// <param name="pc"></param>
1802 ········/// <param name="relations"></param>
1803 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1804 ········{
1805 ············Class c = GetClass(pc);
1806
1807 ············foreach(var entry in relations)
1808 ············{
1809 ················var r = entry.Key;
1810 ················if (r.Multiplicity == RelationMultiplicity.Element)
1811 ················{
1812 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1813 ················}
1814 ················else
1815 ················{
1816 ····················if (pc.NDOGetLoadState(r.Ordinal))
1817 ····················{
1818 ························// Help GC by clearing lists
1819 ························IList l = mappings.GetRelationContainer(pc, r);
1820 ························if(l != null)
1821 ························{
1822 ····························l.Clear();
1823 ························}
1824 ························// Restore relation
1825 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1826 ····················}
1827 ················}
1828 ············}
1829 ········}
1830
1831
1832 ········/// <summary>
1833 ········/// Generates a query for related objects without mapping table.
1834 ········/// Note: this function can't be called in polymorphic scenarios,
1835 ········/// since they need a mapping table.
1836 ········/// </summary>
1837 ········/// <returns></returns>
1838 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1839 ········{
1840 ············// At this point of execution we know,
1841 ············// that the target type is not polymorphic and is not 1:1.
1842
1843 ············// We can't fetch these objects with an NDOql query
1844 ············// since this would require a relation in the opposite direction
1845
1846 ············IList relatedObjects;
1847 ············if (l != null)
1848 ················relatedObjects = l;
1849 ············else
1850 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1851
1852 ············Type t = r.ReferencedType;
1853 ············Class cl = GetClass( t );
1854 ············var provider = cl.Provider;
1855
1856 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1857 ············var relClass = GetClass( r.ReferencedType );
1858 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1859 ············sb.Append( " WHERE " );
1860 ············int i = 0;
1861 ············List<object> parameters = new List<object>();
1862 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1863 ·············· {
1864 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1865 ·················· sb.Append( " = {" );
1866 ·················· sb.Append(i);
1867 ·················· sb.Append( '}' );
1868 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1869 ·················· if (!isLastElement)
1870 ······················ sb.Append( " AND " );
1871 ·················· i++;
1872 ·············· } );
1873
1874 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1875 ············{
1876 ················sb.Append( " AND " );
1877 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1878 ················sb.Append( " = " );
1879 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1880 ············}
1881
1882 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1883
1884 ············foreach (var p in parameters)
1885 ············{
1886 ················q.Parameters.Add( p );
1887 ············}
1888
1889 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1890
1891 ············IList l2 = q.Execute();
1892
1893 ············foreach (object o in l2)
1894 ················relatedObjects.Add( o );
1895
1896 ············return relatedObjects;
1897 ········}
1898
1899
1900 ········/// <summary>
1901 ········/// Resolves an relation. The loaded objects will be hollow.
1902 ········/// </summary>
1903 ········/// <param name="o">The parent object.</param>
1904 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1905 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1906 ········/// <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>
1907 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1908 ········{
1909 ············IPersistenceCapable pc = CheckPc(o);
1910 ············LoadRelationInternal(pc, fieldName, hollow);
1911 ········}
1912
1913 ········
1914
1915 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1916 ········{
1917 ············IList result = null;
1918
1919 ············if (pc.NDOObjectState == NDOObjectState.Created)
1920 ················return null;
1921
1922 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1923 ················LoadData(pc);
1924
1925 ············if(r.MappingTable == null)
1926 ············{
1927 ················// 1:1 are loaded with LoadData
1928 ················if (r.Multiplicity == RelationMultiplicity.List)
1929 ················{
1930 ····················// Help GC by clearing lists
1931 ····················IList l = mappings.GetRelationContainer(pc, r);
1932 ····················if(l != null)
1933 ························l.Clear();
1934 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1935 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1936 ····················result = relatedObjects;
1937 ················}
1938 ············}
1939 ············else
1940 ············{
1941 ················DataTable dt = null;
1942
1943 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1944 ················{
1945 ····················CheckTransaction( handler, r.MappingTable.Connection );
1946 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1947 ················}
1948
1949 ················IList relatedObjects;
1950 ················if(r.Multiplicity == RelationMultiplicity.Element)
1951 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1952 ················else
1953 ················{
1954 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1955 ····················if(relatedObjects != null)
1956 ························relatedObjects.Clear();··// Objects will be reread
1957 ····················else
1958 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1959 ················}
1960 ····················
1961 ················foreach(DataRow objRow in dt.Rows)
1962 ················{
1963 ····················Type relType;
1964
1965 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1966 ····················{
1967 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1968 ························if (typeCodeObj is System.DBNull)
1969 ····························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() ) );
1970 ························relType = typeManager[(int)typeCodeObj];
1971 ························if (relType == null)
1972 ····························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));
1973 ····················}························
1974 ····················else
1975 ····················{
1976 ························relType = r.ReferencedType;
1977 ····················}
1978
1979 ····················//TODO: Generic Types: Exctract the type description from the type name column
1980 ····················if (relType.IsGenericTypeDefinition)
1981 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1982 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1983 ····················IPersistenceCapable relObj = FindObject(id);
1984 ····················relatedObjects.Add(relObj);
1985 ················}····
1986 ················if (r.Multiplicity == RelationMultiplicity.Element)
1987 ················{
1988 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
1989 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
1990 ················}
1991 ················else
1992 ················{
1993 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1994 ····················result = relatedObjects;
1995 ················}
1996 ············}
1997 ············// Mark relation as loaded
1998 ············pc.NDOSetLoadState(r.Ordinal, true);
1999 ············return result;
2000 ········}
2001
2002 ········/// <summary>
2003 ········/// Loads elements of a relation
2004 ········/// </summary>
2005 ········/// <param name="pc">The object which needs to load the relation</param>
2006 ········/// <param name="relationName">The name of the relation</param>
2007 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2008 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2009 ········{
2010 ············if (pc.NDOObjectState == NDOObjectState.Created)
2011 ················return null;
2012 ············Class cl = GetClass(pc);
2013
2014 ············Relation r = cl.FindRelation(relationName);
2015
2016 ············if ( r == null )
2017 ················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 ) );
2018
2019 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2020 ················return null;
2021
2022 ············return LoadRelation(pc, r, hollow);
2023 ········}
2024
2025 ········/// <summary>
2026 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2027 ········/// a list of objects that has been read from the DB.
2028 ········/// </summary>
2029 ········/// <param name="pc">The parent object of the relations</param>
2030 ········/// <param name="row">A data row containing the state of the object</param>
2031 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2032 ········{
2033 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2034 ············Class cl = GetClass(pc);
2035 ············foreach(Relation r in cl.Relations)
2036 ············{
2037 ················if(r.MappingTable == null)
2038 ················{
2039 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2040 ····················{
2041 ························bool isNull = false;
2042 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2043 ························{
2044 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2045 ························}
2046 ························if (isNull)
2047 ························{
2048 ····························mappings.SetRelationField(pc, r.FieldName, null);
2049 ························}
2050 ························else
2051 ························{
2052 ····························Type relType;
2053 ····························if (r.HasSubclasses)
2054 ····························{
2055 ································object o = row[r.ForeignKeyTypeColumnName];
2056 ································if (o == DBNull.Value)
2057 ····································throw new NDOException(75, String.Format(
2058 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2059 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2060 ································relType = typeManager[(int)o];
2061 ····························}
2062 ····························else
2063 ····························{
2064 ································relType = r.ReferencedType;
2065 ····························}
2066 ····························if (relType == null)
2067 ····························{
2068 ································throw new NDOException(75, String.Format(
2069 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2070 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2071 ····························}
2072 ····
2073 ····························int count = r.ForeignKeyColumns.Count();
2074 ····························object[] keydata = new object[count];
2075 ····························int i = 0;
2076 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2077 ····························{
2078 ································keydata[i++] = row[fkColumn.Name];
2079 ····························}
2080
2081 ····························Type oidType = relType;
2082 ····························if (oidType.IsGenericTypeDefinition)
2083 ································oidType = mappings.GetRelationFieldType(r);
2084
2085 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2086 ····························if(childOid.IsValid())
2087 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2088 ····························else
2089 ································mappings.SetRelationField(pc, r.FieldName, null);
2090
2091 ························}
2092 ························pc.NDOSetLoadState(r.Ordinal, true);
2093 ····················}
2094 ················}
2095 ············}
2096 ········}
2097
2098 ········
2099 ········/// <summary>
2100 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2101 ········/// </summary>
2102 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2103 ········/// <param name="t">The type of the object, the id will belong to.</param>
2104 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2105 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2106 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2107 ········{
2108 ············return new ObjectId(oid.Id, t);
2109 ········}
2110
2111 ········/*
2112 ········/// <summary>
2113 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2114 ········/// </summary>
2115 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2116 ········/// <param name="t">The type of the object.</param>
2117 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2118 ········/// <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>
2119 ········public ObjectId NewObjectId(object keyData, Type t)
2120 ········{
2121 ············if (keyData == null || keyData == DBNull.Value)
2122 ················return ObjectId.InvalidId;
2123
2124 ············Class cl =··GetClass(t);············
2125 ············
2126 ············if (cl.Oid.FieldType == typeof(int))
2127 ················return new ObjectId(new Int32Key(t, (int)keyData));
2128 ············else if (cl.Oid.FieldType == typeof(string))
2129 ················return new ObjectId(new StringKey(t, (String) keyData));
2130 ············else if (cl.Oid.FieldType == typeof(Guid))
2131 ················if (keyData is string)
2132 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2133 ················else
2134 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2135 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2136 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2137 ············else
2138 ················return ObjectId.InvalidId;
2139 ········}
2140 ········*/
2141
2142
2143 ········/*
2144 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2145 ····················{
2146 ························// The column, which hold the oid gets overwritten, if
2147 ························// Oid.FieldName contains a value.
2148 */
2149 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2150 ········{
2151 ············Class cl = GetClass(pc);
2152
2153 ············if (cl.Oid.IsDependent)
2154 ················return;
2155
2156 ············Key key = pc.NDOObjectId.Id;
2157
2158 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2159 ············{
2160 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2161 ················if (oidColumn.FieldName != null)
2162 ················{
2163 ····················row[oidColumn.Name] = key[i];
2164 ················}
2165 ············}
2166 ········}
2167
2168 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2169 ········{
2170 ············NDO.Mapping.Class cl = GetClass(pc);
2171 ············ObjectId oid = pc.NDOObjectId;
2172
2173 ············if (cl.TimeStampColumn != null)
2174 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2175
2176 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2177 ················return;
2178
2179 ············Key key = oid.Id;
2180
2181 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2182 ············{
2183 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2184 ················row[oidColumn.Name] = key[i];
2185 ············}
2186 ········}
2187
2188 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2189 ········{
2190 ············ObjectId oid = pc.NDOObjectId;
2191 ············NDO.Mapping.Class cl = GetClass(pc);
2192
2193 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2194 ················return;
2195
2196 ············Key key = oid.Id;
2197
2198 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2199 ············{
2200 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2201 ················object o = row[oidColumn.Name];
2202 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2203 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2204 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2205 ····················key[i] = new Guid((string)o);
2206 ················else
2207 ····················key[i] = o;
2208 ············}
2209
2210 ········}
2211
2212 ········private void ReadId (Cache.Entry e)
2213 ········{
2214 ············ReadIdFromRow(e.pc, e.row);
2215 ········}
2216
2217 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2218 ········{
2219 ············WriteObject(pc, row, fieldNames, 0);
2220 ········}
2221
2222 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2223 ········{
2224 ············if (cl.TimeStampColumn == null)
2225 ················return;
2226 ············object col = row[cl.TimeStampColumn];
2227 ············if (col is String)
2228 ················pc.NDOTimeStamp = new Guid(col.ToString());
2229 ············else
2230 ················pc.NDOTimeStamp = (Guid) col;
2231 ········}
2232
2233
2234
2235 ········private void ReadTimeStamp(Cache.Entry e)
2236 ········{
2237 ············IPersistenceCapable pc = e.pc;
2238 ············NDO.Mapping.Class cl = GetClass(pc);
2239 ············Debug.Assert(!IsFakeRow(cl, e.row));
2240 ············if (cl.TimeStampColumn == null)
2241 ················return;
2242 ············if (e.row[cl.TimeStampColumn] is String)
2243 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2244 ············else
2245 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2246 ········}
2247
2248 ········/// <summary>
2249 ········/// Determines, if any objects are new, changed or deleted.
2250 ········/// </summary>
2251 ········public virtual bool HasChanges
2252 ········{
2253 ············get
2254 ············{
2255 ················return cache.LockedObjects.Count > 0;
2256 ············}
2257 ········}
2258
2259 ········/// <summary>
2260 ········/// Do the update for all rows in the ds.
2261 ········/// </summary>
2262 ········/// <param name="types">Types with changes.</param>
2263 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2264 ········/// <remarks>
2265 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2266 ········/// </remarks>
2267 ········private void UpdateTypes(IList types, bool delete)
2268 ········{
2269 ············foreach(Type t in types)
2270 ············{
2271 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2272 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2273 ················{
2274 ····················CheckTransaction( handler, t );
2275 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2276 ····················handler.ConcurrencyError += ceh;
2277 ····················try
2278 ····················{
2279 ························DataTable dt = GetTable(t);
2280 ························if (delete)
2281 ····························handler.UpdateDeletedObjects( dt );
2282 ························else
2283 ····························handler.Update( dt );
2284 ····················}
2285 ····················finally
2286 ····················{
2287 ························handler.ConcurrencyError -= ceh;
2288 ····················}
2289 ················}
2290 ············}
2291 ········}
2292
2293 ········internal void UpdateCreatedMappingTableEntries()
2294 ········{
2295 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2296 ············{
2297 ················if (!e.DeleteEntry)
2298 ····················WriteMappingTableEntry(e);
2299 ············}
2300 ············// Now update all mapping tables
2301 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2302 ············{
2303 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2304 ················handler.Update(ds);
2305 ············}
2306 ········}
2307
2308 ········internal void UpdateDeletedMappingTableEntries()
2309 ········{
2310 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2311 ············{
2312 ················if (e.DeleteEntry)
2313 ····················WriteMappingTableEntry(e);
2314 ············}
2315 ············// Now update all mapping tables
2316 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2317 ············{
2318 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2319 ················handler.Update(ds);
2320 ············}
2321 ········}
2322
2323 ········/// <summary>
2324 ········/// Save all changed object into the DataSet and update the DB.
2325 ········/// When a newly created object is written to DB, the key might change. Therefore,
2326 ········/// the id is updated and the object is removed and re-inserted into the cache.
2327 ········/// </summary>
2328 ········public virtual void Save(bool deferCommit = false)
2329 ········{
2330 ············this.DeferredMode = deferCommit;
2331 ············var htOnSaving = new HashSet<ObjectId>();
2332 ············for(;;)
2333 ············{
2334 ················// We need to work on a copy of the locked objects list,
2335 ················// since the handlers might add more objects to the cache
2336 ················var lockedObjects = cache.LockedObjects.ToList();
2337 ················int count = lockedObjects.Count;
2338 ················foreach(Cache.Entry e in lockedObjects)
2339 ················{
2340 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2341 ····················{
2342 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2343 ························if (ipn != null)
2344 ························{
2345 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2346 ····························{
2347 ································ipn.OnSaving();
2348 ································htOnSaving.Add(e.pc.NDOObjectId);
2349 ····························}
2350 ························}
2351 ····················}
2352 ················}
2353 ················// The system is stable, if the count doesn't change
2354 ················if (cache.LockedObjects.Count == count)
2355 ····················break;
2356 ············}
2357
2358 ············if (this.OnSavingEvent != null)
2359 ············{
2360 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2361 ················foreach(Cache.Entry e in cache.LockedObjects)
2362 ····················onSavingObjects.Add(e.pc);
2363 ················OnSavingEvent(onSavingObjects);
2364 ············}
2365
2366 ············List<Type> types = new List<Type>();
2367 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2368 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2369 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2370 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2371 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2372
2373 ············// Save current state in DataSet
2374 ············foreach (Cache.Entry e in cache.LockedObjects)
2375 ············{
2376 ················Type objType = e.pc.GetType();
2377
2378 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2379 ····················objType = objType.GetGenericTypeDefinition();
2380
2381 ················Class cl = GetClass(e.pc);
2382 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2383 ················if(!types.Contains(objType))
2384 ················{
2385 ····················//Debug.WriteLine("Added··type " + objType.Name);
2386 ····················types.Add(objType);
2387 ················}
2388 ················NDOObjectState objectState = e.pc.NDOObjectState;
2389 ················if(objectState == NDOObjectState.Deleted)
2390 ················{
2391 ····················deletedObjects.Add(e.pc);
2392 ················}
2393 ················else if(objectState == NDOObjectState.Created)
2394 ················{
2395 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2396 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2397 ····················WriteForeignKeysToRow(e.pc, e.row);
2398 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2399 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2400 ····················//····················i = 0;
2401 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2402 ····················//····················{
2403 ····················//························rows[i++] = e2.row;
2404 ····················//····················}
2405 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2406 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2407
2408 ····················addedCacheEntries.Add(e);
2409 ····················addedObjects.Add( e.pc );
2410 ····················//····················objectState = NDOObjectState.Persistent;
2411 ················}
2412 ················else
2413 ················{
2414 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2415 ························changedObjects.Add( e.pc );
2416 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2417 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2418 ················}
2419 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2420 ················{
2421 ····················hollowModeObjects.Add(e.pc);
2422 ················}
2423 ············}
2424
2425 ············// Before we delete any db rows, we have to make sure, to delete mapping
2426 ············// table entries first, which might have relations to the db rows to be deleted
2427 ············UpdateDeletedMappingTableEntries();
2428
2429 ············// Update DB
2430 ············if (ds.HasChanges())
2431 ············{
2432
2433 ················// We need the reversed update order for deletions.
2434 ················types.Sort( ( t1, t2 ) =>
2435 ················{
2436 ····················int i1 = mappings.GetUpdateOrder( t1 );
2437 ····················int i2 = mappings.GetUpdateOrder( t2 );
2438 ····················if (i1 < i2)
2439 ····················{
2440 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2441 ····························i1 += 100000;
2442 ····················}
2443 ····················else
2444 ····················{
2445 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2446 ····························i2 += 100000;
2447 ····················}
2448 ····················return i2 - i1;
2449 ················} );
2450
2451 ················// Delete records first
2452
2453 ················UpdateTypes(types, true);
2454
2455 ················// Now do all other updates in correct order.
2456 ················types.Reverse();
2457
2458 ················UpdateTypes(types, false);
2459 ················
2460 ················ds.AcceptChanges();
2461 ················if(createdDirectObjects.Count > 0)
2462 ················{
2463 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2464 ····················// They must be written again to store the correct foreign keys.
2465 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2466 ····················{
2467 ························Class cl = GetClass(pc);
2468 ························DataRow r = this.cache.GetDataRow(pc);
2469 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2470 ························object o = r[fakeColumnName];
2471 ························r[fakeColumnName] = o;
2472 ····················}
2473
2474 ····················UpdateTypes(types, false);
2475 ················}
2476
2477 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2478 ················foreach(Cache.Entry e in addedCacheEntries)
2479 ················{
2480 ····················cache.DeregisterLockedObject(e.pc);
2481 ····················ReadId(e);
2482 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2483 ················}
2484
2485 ················// Now update all mapping tables. Because of possible subclasses, there is no
2486 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2487 ················// tables only after all other objects have been written to ensure correct foreign keys.
2488 ················UpdateCreatedMappingTableEntries();
2489
2490 ················// The rows may contain now new Ids, which should be
2491 ················// stored in the lostRowInfo's before the rows get detached
2492 ················foreach(Cache.Entry e in cache.LockedObjects)
2493 ················{
2494 ····················if (e.row.RowState != DataRowState.Detached)
2495 ····················{
2496 ························IPersistenceCapable pc = e.pc;
2497 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2498 ····················}
2499 ················}
2500
2501 ················ds.AcceptChanges();
2502 ············}
2503
2504 ············EndSave(!deferCommit);
2505
2506 ············foreach(IPersistenceCapable pc in deletedObjects)
2507 ············{
2508 ················MakeObjectTransient(pc, false);
2509 ············}
2510
2511 ············ds.Clear();
2512 ············mappingHandler.Clear();
2513 ············createdDirectObjects.Clear();
2514 ············createdMappingTableObjects.Clear();
2515 ············this.relationChanges.Clear();
2516
2517 ············if(hollowMode)
2518 ············{
2519 ················MakeHollow(hollowModeObjects);
2520 ············}
2521
2522 ············if (this.OnSavedEvent != null)
2523 ············{
2524 ················AuditSet auditSet = new AuditSet()
2525 ················{
2526 ····················ChangedObjects = changedObjects,
2527 ····················CreatedObjects = addedObjects,
2528 ····················DeletedObjects = deletedObjects
2529 ················};
2530 ················this.OnSavedEvent( auditSet );
2531 ············}
2532 ········}
2533
2534 ········private void EndSave(bool forceCommit)
2535 ········{
2536 ············foreach(Cache.Entry e in cache.LockedObjects)
2537 ············{
2538 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2539 ····················this.ReadTimeStamp(e);
2540 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2541 ············}
2542
2543 ············cache.UnlockAll();
2544
2545 ············CheckEndTransaction(forceCommit);
2546 ········}
2547
2548 ········/// <summary>
2549 ········/// Write all foreign keys for 1:1-relations.
2550 ········/// </summary>
2551 ········/// <param name="pc">The persistent object.</param>
2552 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2553 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2554 ········{
2555 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2556 ············{
2557 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2558 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2559
2560 ················if ( relObj != null )
2561 ················{
2562 ····················int i = 0;
2563 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2564 ····················{
2565 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2566 ····················}
2567 ····················if ( r.ForeignKeyTypeColumnName != null )
2568 ····················{
2569 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2570 ····················}
2571 ················}
2572 ················else
2573 ················{
2574 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2575 ····················{
2576 ························pcRow[fkColumn.Name] = DBNull.Value;
2577 ····················}
2578 ····················if ( r.ForeignKeyTypeColumnName != null )
2579 ····················{
2580 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2581 ····················}
2582 ················}
2583 ············}
2584 ········}
2585
2586
2587
2588 ········/// <summary>
2589 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2590 ········/// </summary>
2591 ········/// <param name="e">the mapping table entry</param>
2592 ········private void WriteMappingTableEntry(MappingTableEntry e)
2593 ········{
2594 ············IPersistenceCapable pc = e.ParentObject;
2595 ············IPersistenceCapable relObj = e.RelatedObject;
2596 ············Relation r = e.Relation;
2597 ············DataTable dt = GetTable(r.MappingTable.TableName);
2598 ············DataRow row = dt.NewRow();
2599 ············int i = 0;
2600 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2601 ············{
2602 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2603 ············}
2604 ············i = 0;
2605 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2606 ············{
2607 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2608 ············}
2609
2610 ············if (r.ForeignKeyTypeColumnName != null)
2611 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2612 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2613 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2614
2615 ············dt.Rows.Add(row);
2616 ············if(e.DeleteEntry)
2617 ············{
2618 ················row.AcceptChanges();
2619 ················row.Delete();
2620 ············}
2621
2622 ············IMappingTableHandler handler;
2623 ············if (!mappingHandler.TryGetValue( r, out handler ))
2624 ············{
2625 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2626 ············}
2627 ········}
2628
2629
2630 ········/// <summary>
2631 ········/// Undo changes of a certain object
2632 ········/// </summary>
2633 ········/// <param name="o">Object to undo</param>
2634 ········public void Restore(object o)
2635 ········{············
2636 ············IPersistenceCapable pc = CheckPc(o);
2637 ············Cache.Entry e = null;
2638 ············foreach (Cache.Entry entry in cache.LockedObjects)
2639 ············{
2640 ················if (entry.pc == pc)
2641 ················{
2642 ····················e = entry;
2643 ····················break;
2644 ················}
2645 ············}
2646 ············if (e == null)
2647 ················return;
2648 ············Class cl = GetClass(e.pc);
2649 ············switch (pc.NDOObjectState)
2650 ············{
2651 ················case NDOObjectState.PersistentDirty:
2652 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2653 ····················foreach(Relation r in cl.Relations)
2654 ····················{
2655 ························if (r.Multiplicity == RelationMultiplicity.Element)
2656 ························{
2657 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2658 ····························if (subPc != null && cache.IsLocked(subPc))
2659 ································Restore(subPc);
2660 ························}
2661 ························else
2662 ························{
2663 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2664 ································continue;
2665 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2666 ····························if (subList != null)
2667 ····························{
2668 ································foreach(IPersistenceCapable subPc2 in subList)
2669 ································{
2670 ····································if (cache.IsLocked(subPc2))
2671 ········································Restore(subPc2);
2672 ································}
2673 ····························}
2674 ························}
2675 ····················}
2676 ····················RestoreRelatedObjects(pc, e.relations);
2677 ····················e.row.RejectChanges();
2678 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2679 ····················cache.Unlock(pc);
2680 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2681 ····················break;
2682 ················case NDOObjectState.Created:
2683 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2684 ····················cache.Unlock(pc);
2685 ····················MakeObjectTransient(pc, true);
2686 ····················break;
2687 ················case NDOObjectState.Deleted:
2688 ····················if (!this.IsFakeRow(cl, e.row))
2689 ····················{
2690 ························e.row.RejectChanges();
2691 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2692 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2693 ····················}
2694 ····················else
2695 ····················{
2696 ························e.row.RejectChanges();
2697 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2698 ····················}
2699 ····················cache.Unlock(pc);
2700 ····················break;
2701
2702 ············}
2703 ········}
2704
2705 ········/// <summary>
2706 ········/// Aborts a pending transaction without restoring the object state.
2707 ········/// </summary>
2708 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2709 ········public virtual void AbortTransaction()
2710 ········{
2711 ············TransactionScope.Dispose();
2712 ········}
2713
2714 ········/// <summary>
2715 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2716 ········/// </summary>
2717 ········public virtual void Abort()
2718 ········{
2719 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2720 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2721 ············// individual row.
2722 ············createdDirectObjects.Clear();
2723 ············createdMappingTableObjects.Clear();
2724 ············ArrayList deletedObjects = new ArrayList();
2725 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2726
2727 ············// Read all objects from DataSet
2728 ············foreach (Cache.Entry e in cache.LockedObjects)
2729 ············{
2730 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2731
2732 ················Class cl = GetClass(e.pc);
2733 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2734 ················if (!isFakeRow)
2735 ················{
2736 ····················RestoreRelatedObjects(e.pc, e.relations);
2737 ················}
2738 ················else
2739 ················{
2740 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2741 ················}
2742
2743 ················switch(e.pc.NDOObjectState)
2744 ················{
2745 ····················case NDOObjectState.Created:
2746 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2747 ························deletedObjects.Add(e.pc);
2748 ························break;
2749
2750 ····················case NDOObjectState.PersistentDirty:
2751 ························e.row.RejectChanges();
2752 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2753 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2754 ························break;
2755
2756 ····················case NDOObjectState.Deleted:
2757 ························if (!isFakeRow)
2758 ························{
2759 ····························e.row.RejectChanges();
2760 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2761 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2762 ························}
2763 ························else
2764 ························{
2765 ····························e.row.RejectChanges();
2766 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2767 ························}
2768 ························break;
2769
2770 ····················default:
2771 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2772 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2773 ························//break;
2774 ················}
2775 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2776 ················{
2777 ····················hollowModeObjects.Add(e.pc);
2778 ················}
2779 ············}
2780 ············cache.UnlockAll();
2781 ············foreach(IPersistenceCapable pc in deletedObjects)
2782 ············{
2783 ················MakeObjectTransient(pc, true);
2784 ············}
2785 ············ds.Clear();
2786 ············mappingHandler.Clear();
2787 ············if(hollowMode)
2788 ············{
2789 ················MakeHollow(hollowModeObjects);
2790 ············}
2791
2792 ············this.relationChanges.Clear();
2793
2794
2795 ············AbortTransaction();
2796 ········}
2797
2798
2799 ········/// <summary>
2800 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2801 ········/// disable future access.
2802 ········/// </summary>
2803 ········/// <param name="pc"></param>
2804 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2805 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2806 ········{
2807 ············cache.Deregister(pc);
2808 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2809 ············if(removeId)
2810 ············{
2811 ················pc.NDOObjectId = null;
2812 ············}
2813 ············pc.NDOObjectState = NDOObjectState.Transient;
2814 ············pc.NDOStateManager = null;
2815 ········}
2816
2817 ········/// <summary>
2818 ········/// Makes an object transient.
2819 ········/// The object can be used afterwards, but changes will not be saved in the database.
2820 ········/// </summary>
2821 ········/// <remarks>
2822 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2823 ········/// </remarks>
2824 ········/// <param name="o">The object to detach.</param>
2825 ········public void MakeTransient(object o)
2826 ········{
2827 ············IPersistenceCapable pc = CheckPc(o);
2828 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2829 ············{
2830 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2831 ············}
2832
2833 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2834 ············{
2835 ················LoadData(pc);
2836 ············}
2837 ············MakeObjectTransient(pc, true);
2838 ········}
2839
2840
2841 ········/// <summary>
2842 ········/// Make all objects of a list transient.
2843 ········/// </summary>
2844 ········/// <param name="list">the list of transient objects</param>
2845 ········public void MakeTransient(System.Collections.IList list)
2846 ········{
2847 ············foreach (IPersistenceCapable pc in list)
2848 ················MakeTransient(pc);
2849 ········}
2850
2851 ········/// <summary>
2852 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2853 ········/// </summary>
2854 ········/// <param name="o">The object to remove</param>
2855 ········public void Delete(object o)
2856 ········{
2857 ············IPersistenceCapable pc = CheckPc(o);
2858 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2859 ············{
2860 ················throw new NDOException( 120, "Can't delete transient object" );
2861 ············}
2862 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2863 ············{
2864 ················Delete(pc, true);
2865 ············}
2866 ········}
2867
2868
2869 ········private void LoadAllRelations(object o)
2870 ········{
2871 ············IPersistenceCapable pc = CheckPc(o);
2872 ············Class cl = GetClass(pc);
2873 ············foreach(Relation r in cl.Relations)
2874 ············{
2875 ················if (!pc.NDOGetLoadState(r.Ordinal))
2876 ····················LoadRelation(pc, r, true);
2877 ············}
2878 ········}
2879
2880
2881 ········/// <summary>
2882 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2883 ········/// </summary>
2884 ········/// <remarks>
2885 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2886 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2887 ········/// the parent object.
2888 ········/// </remarks>
2889 ········/// <param name="pc">the object to remove</param>
2890 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2891 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2892 ········{
2893 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2894 ············//Debug.Indent();
2895 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2896 ············if (idn != null)
2897 ················idn.OnDelete();
2898
2899 ············LoadAllRelations(pc);
2900 ············DeleteRelatedObjects(pc, checkAssoziations);
2901
2902 ············switch(pc.NDOObjectState)
2903 ············{
2904 ················case NDOObjectState.Transient:
2905 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2906
2907 ················case NDOObjectState.Created:
2908 ····················DataRow row = cache.GetDataRow(pc);
2909 ····················row.Delete();
2910 ····················ArrayList cdosToDelete = new ArrayList();
2911 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2912 ························if ((object)cdo == (object)pc)
2913 ····························cdosToDelete.Add(cdo);
2914 ····················foreach (object o in cdosToDelete)
2915 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2916 ····················MakeObjectTransient(pc, true);
2917 ····················break;
2918 ················case NDOObjectState.Persistent:
2919 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2920 ························SaveObjectState(pc, true);
2921 ····················row = cache.GetDataRow(pc);
2922 ····················row.Delete();
2923 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2924 ····················break;
2925 ················case NDOObjectState.Hollow:
2926 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2927 ························SaveFakeRow(pc);
2928 ····················row = cache.GetDataRow(pc);
2929 ····················row.Delete();
2930 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2931 ····················break;
2932
2933 ················case NDOObjectState.PersistentDirty:
2934 ····················row = cache.GetDataRow(pc);
2935 ····················row.Delete();
2936 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2937 ····················break;
2938
2939 ················case NDOObjectState.Deleted:
2940 ····················break;
2941 ············}
2942
2943 ············//Debug.Unindent();
2944 ········}
2945
2946
2947 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2948 ········{
2949 ············MappingTableEntry mte = null;
2950 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2951 ············{
2952 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2953 ················{
2954 ····················mte = e;
2955 ····················break;
2956 ················}
2957 ············}
2958
2959 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2960 ············{
2961 ················if (mte != null)
2962 ····················createdMappingTableObjects.Remove(mte);
2963 ············}
2964 ············else
2965 ············{
2966 ················if (mte == null)
2967 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2968 ············}
2969 ········}
2970
2971 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2972 ········{
2973 ············// Two tasks: a) Null the foreign key
2974 ············//··············b) remove the element from the foreign container
2975
2976 ············if (!r.Bidirectional)
2977 ················return;
2978
2979 ············// this keeps the oid valid
2980 ············if (GetClass(child.GetType()).Oid.IsDependent)
2981 ················return;
2982
2983 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2984 ············{
2985 ················LoadAndMarkDirty(child);
2986 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2987 ············}
2988 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
2989 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
2990 ············{
2991 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
2992 ····················LoadRelation(child, r.ForeignRelation, true);
2993 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
2994 ················if (l == null)
2995 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
2996 ················ObjectListManipulator.Remove(l, pc);
2997 ················// Don't need to delete the mapping table entry, because that was done
2998 ················// through the parent.
2999 ············}
3000 ········}
3001
3002 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3003 ········{
3004 ············// 1) Element····nomap····ass
3005 ············// 2) Element····nomap····comp
3006 ············// 3) Element····map········ass
3007 ············// 4) Element····map········comp
3008 ············// 5) List········nomap····ass
3009 ············// 6) List········nomap····comp
3010 ············// 7) List········map········ass
3011 ············// 8) List········map········comp
3012
3013 ············// Two tasks: Null foreign key and, if Composition, delete the child
3014
3015 ············// If Mapping Table, delete the entry - 3,7
3016 ············// If List and assoziation, null the foreign key in the foreign class - 5
3017 ············// If Element, null the foreign key in the own class 1,2,3,4
3018 ············// If composition, delete the child 2,4,6,8
3019
3020 ············// If the relObj is newly created
3021 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3022 ············Class childClass = GetClass(child);
3023
3024 ············if (r.MappingTable != null)··// 3,7
3025 ············{················
3026 ················DeleteMappingTableEntry(pc, r, child);
3027 ············}
3028 ············else if (r.Multiplicity == RelationMultiplicity.List
3029 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3030 ············{················
3031 ················LoadAndMarkDirty(child);
3032 ················DataRow row = this.cache.GetDataRow(child);
3033 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3034 ················{
3035 ····················row[fkColumnn.Name] = DBNull.Value;
3036 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3037 ················}
3038 ············}
3039 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3040 ············{
3041 ················LoadAndMarkDirty(pc);
3042 ············}
3043 ············if (r.Composition || childClass.Oid.IsDependent)
3044 ············{
3045 #if DEBUG
3046 ················if (child.NDOObjectState == NDOObjectState.Transient)
3047 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3048 #endif
3049 ················// Deletes the foreign key in case of List multiplicity
3050 ················// In case of Element multiplicity, the parent is either deleted,
3051 ················// or RemoveRelatedObject is called because the relation has been nulled.
3052 ················Delete(child);··
3053 ············}
3054 ········}
3055
3056 ········/// <summary>
3057 ········/// Remove a related object
3058 ········/// </summary>
3059 ········/// <param name="pc"></param>
3060 ········/// <param name="r"></param>
3061 ········/// <param name="child"></param>
3062 ········/// <param name="calledFromStateManager"></param>
3063 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3064 ········{
3065 ············//TODO: We need a relation management, which is independent of
3066 ············//the state management of an object. At the moment the relation
3067 ············//lists or elements are cached for restore, if an object is marked dirty.
3068 ············//Thus we have to mark dirty our parent object in any case at the moment.
3069 ············if (calledFromStateManager)
3070 ················MarkDirty(pc);
3071
3072 ············// Object can be deleted in an OnDelete-Handler
3073 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3074 ················return;
3075 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3076 ············// Preconditions
3077 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3078 ············// The Object state is checked there
3079
3080 ············// If there is a composition in the opposite direction
3081 ············// && the other direction hasn't been processed
3082 ············// throw an exception.
3083 ············// If an exception is thrown at this point, have a look at IsLocked....
3084 ············if (r.Bidirectional && r.ForeignRelation.Composition
3085 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3086 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3087
3088 ············if (!removeLock.GetLock(pc, r, child))
3089 ················return;
3090
3091 ············try
3092 ············{
3093 ················// must be in this order, since the child
3094 ················// can be deleted in DeleteOrNullRelation
3095 ················//if (changeForeignRelations)
3096 ················DeleteOrNullForeignRelation(pc, r, child);
3097 ················DeleteOrNullRelation(pc, r, child);
3098 ············}
3099 ············finally
3100 ············{
3101 ················removeLock.Unlock(pc, r, child);
3102 ············}
3103
3104 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3105 ········}
3106
3107 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3108 ········{
3109 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3110 ············//············Debug.Indent();
3111 ············if (r.Multiplicity == RelationMultiplicity.Element)
3112 ············{
3113 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3114 ················if(child != null)
3115 ················{
3116 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3117 ····················//····················{
3118 ····················//························if (!r.ForeignRelation.Composition)
3119 ····················//························{
3120 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3121 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3122 ····················//························}
3123 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3124 ····················//························else
3125 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3126 ····················//····················}
3127 ····················InternalRemoveRelatedObject(pc, r, child, false);
3128 ················}
3129 ············}
3130 ············else
3131 ············{
3132 ················IList list = mappings.GetRelationContainer(pc, r);
3133 ················if(list != null && list.Count > 0)
3134 ················{
3135 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3136 ····················//····················{
3137 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3138 ····················//····················}
3139 ····················// Since RemoveRelatedObjects probably changes the list,
3140 ····················// we iterate through a copy of the list.
3141 ····················ArrayList al = new ArrayList(list);
3142 ····················foreach(IPersistenceCapable relObj in al)
3143 ····················{
3144 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3145 ····················}
3146 ················}
3147 ············}
3148 ············//············Debug.Unindent();
3149 ········}
3150
3151 ········/// <summary>
3152 ········/// Remove all related objects from a parent.
3153 ········/// </summary>
3154 ········/// <param name="pc">the parent object</param>
3155 ········/// <param name="checkAssoziations"></param>
3156 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3157 ········{
3158 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3159 ············//············Debug.Indent();
3160 ············// delete all related objects:
3161 ············Class parentClass = GetClass(pc);
3162 ············// Remove Assoziations first
3163 ············foreach(Relation r in parentClass.Relations)
3164 ············{
3165 ················if (!r.Composition)
3166 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3167 ············}
3168 ············foreach(Relation r in parentClass.Relations)
3169 ············{
3170 ················if (r.Composition)
3171 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3172 ············}
3173
3174 ············//············Debug.Unindent();
3175 ········}
3176
3177 ········/// <summary>
3178 ········/// Delete a list of objects
3179 ········/// </summary>
3180 ········/// <param name="list">the list of objects to remove</param>
3181 ········public void Delete(IList list)
3182 ········{
3183 ············for (int i = 0; i < list.Count; i++)
3184 ············{
3185 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3186 ················Delete(pc);
3187 ············}
3188 ········}
3189
3190 ········/// <summary>
3191 ········/// Make object hollow. All relations will be unloaded and object data will be
3192 ········/// newly fetched during the next touch of a persistent field.
3193 ········/// </summary>
3194 ········/// <param name="o"></param>
3195 ········public virtual void MakeHollow(object o)
3196 ········{
3197 ············IPersistenceCapable pc = CheckPc(o);
3198 ············MakeHollow(pc, false);
3199 ········}
3200
3201 ········/// <summary>
3202 ········/// Make the object hollow if it is persistent. Unload all complex data.
3203 ········/// </summary>
3204 ········/// <param name="o"></param>
3205 ········/// <param name="recursive">if true then unload related objects as well</param>
3206 ········public virtual void MakeHollow(object o, bool recursive)
3207 ········{
3208 ············IPersistenceCapable pc = CheckPc(o);
3209 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3210 ················return;
3211 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3212 ············{
3213 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3214 ············}
3215 ············pc.NDOObjectState = NDOObjectState.Hollow;
3216 ············MakeRelationsHollow(pc, recursive);
3217 ········}
3218
3219 ········/// <summary>
3220 ········/// Make all objects of a list hollow.
3221 ········/// </summary>
3222 ········/// <param name="list">the list of objects that should be made hollow</param>
3223 ········public virtual void MakeHollow(System.Collections.IList list)
3224 ········{
3225 ············MakeHollow(list, false);
3226 ········}
3227
3228 ········/// <summary>
3229 ········/// Make all objects of a list hollow.
3230 ········/// </summary>
3231 ········/// <param name="list">the list of objects that should be made hollow</param>
3232 ········/// <param name="recursive">if true then unload related objects as well</param>
3233 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3234 ········{
3235 ············foreach (IPersistenceCapable pc in list)
3236 ················MakeHollow(pc, recursive);················
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all unlocked objects in the cache hollow.
3241 ········/// </summary>
3242 ········public virtual void MakeAllHollow()
3243 ········{
3244 ············foreach(var pc in cache.UnlockedObjects)
3245 ············{
3246 ················MakeHollow(pc, false);
3247 ············}
3248 ········}
3249
3250 ········/// <summary>
3251 ········/// Make relations hollow.
3252 ········/// </summary>
3253 ········/// <param name="pc">The parent object</param>
3254 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3255 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3256 ········{
3257 ············Class c = GetClass(pc);
3258 ············foreach(Relation r in c.Relations)
3259 ············{
3260 ················if (r.Multiplicity == RelationMultiplicity.Element)
3261 ················{
3262 ····················mappings.SetRelationField(pc, r.FieldName, null);
3263 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3264 ····················//····················if((null != child) && recursive)
3265 ····················//····················{
3266 ····················//························MakeHollow(child, true);
3267 ····················//····················}
3268 ················}
3269 ················else
3270 ················{
3271 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3272 ························continue;
3273 ····················// Help GC by clearing lists
3274 ····················IList l = mappings.GetRelationContainer(pc, r);
3275 ····················if(l != null)
3276 ····················{
3277 ························if(recursive)
3278 ························{
3279 ····························MakeHollow(l, true);
3280 ························}
3281 ························l.Clear();
3282 ····················}
3283 ····················// Hollow relation
3284 ····················mappings.SetRelationContainer(pc, r, null);
3285 ················}
3286 ············}
3287 ············ClearRelationState(pc);
3288 ········}
3289
3290 ········private void ClearRelationState(IPersistenceCapable pc)
3291 ········{
3292 ············Class cl = GetClass(pc);
3293 ············foreach(Relation r in cl.Relations)
3294 ················pc.NDOSetLoadState(r.Ordinal, false);
3295 ········}
3296
3297 ········private void SetRelationState(IPersistenceCapable pc)
3298 ········{
3299 ············Class cl = GetClass(pc);
3300 ············// Due to a bug in the enhancer the constructors are not always patched right,
3301 ············// so NDOLoadState might be uninitialized
3302 ············if (pc.NDOLoadState == null)
3303 ············{
3304 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3305 ················if (fi == null)
3306 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3307 ················fi.SetValue(pc, new LoadState());
3308 ············}
3309
3310 ············// After serialization the relation load state is null
3311 ············if (pc.NDOLoadState.RelationLoadState == null)
3312 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3313 ············foreach(Relation r in cl.Relations)
3314 ················pc.NDOSetLoadState(r.Ordinal, true);
3315 ········}
3316
3317 ········/// <summary>
3318 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3319 ········/// </summary>
3320 ········/// <param name="t">The type of the persistent object</param>
3321 ········/// <returns></returns>
3322 ········public IPersistenceCapable CreateObject(Type t)
3323 ········{
3324 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3325 ········}
3326
3327 ········/// <summary>
3328 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3329 ········/// </summary>
3330 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3331 ········/// <returns></returns>
3332 ········public T CreateObject<T>()
3333 ········{
3334 ············return (T)CreateObject( typeof( T ) );
3335 ········}
3336
3337 ········/// <summary>
3338 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3339 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3340 ········/// is performed.
3341 ········/// </summary>
3342 ········/// <param name="t">The type of the object to retrieve.</param>
3343 ········/// <param name="keyData">The key value to build the object id.</param>
3344 ········/// <returns>A hollow object</returns>
3345 ········/// <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>
3346 ········public IPersistenceCapable FindObject(Type t, object keyData)
3347 ········{
3348 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3349 ············return FindObject(oid);
3350 ········}
3351
3352 ········/// <summary>
3353 ········/// Finds an object using a short id.
3354 ········/// </summary>
3355 ········/// <param name="encodedShortId"></param>
3356 ········/// <returns></returns>
3357 ········public IPersistenceCapable FindObject(string encodedShortId)
3358 ········{
3359 ············string shortId = encodedShortId.Decode();
3360 ············string[] arr = shortId.Split( '-' );
3361 ············if (arr.Length != 3)
3362 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3363 ············Type t = shortId.GetObjectType(this);··// try readable format
3364 ············if (t == null)
3365 ············{
3366 ················int typeCode = 0;
3367 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3368 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3369 ················t = this.typeManager[typeCode];
3370 ················if (t == null)
3371 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3372 ············}
3373
3374 ············Class cls = GetClass( t );
3375 ············if (cls == null)
3376 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3377
3378 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3379 ············string[] oidValues = arr[2].Split( ' ' );
3380
3381 ············int i = 0;
3382 ············foreach (var oidValue in oidValues)
3383 ············{
3384 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3385 ················if (oidType == typeof( int ))
3386 ················{
3387 ····················int key;
3388 ····················if (!int.TryParse( oidValue, out key ))
3389 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3390 ····················if (key > (int.MaxValue >> 1))
3391 ························key = -(int.MaxValue - key);
3392 ····················keydata[i] = key;
3393 ················}
3394 ················else if (oidType == typeof( Guid ))
3395 ················{
3396 ····················Guid key;
3397 ····················if (!Guid.TryParse( oidValue, out key ))
3398 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3399 ····················keydata[i] = key;
3400 ················}
3401 ················else if (oidType == typeof( string ))
3402 ················{
3403 ····················keydata[i] = oidValue;
3404 ················}
3405 ················else
3406 ················{
3407 ····················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 ) );
3408 ················}
3409
3410 ················i++;
3411 ············}
3412
3413 ············if (keydata.Length == 1)
3414 ················return FindObject( t, keydata[0] );
3415
3416 ············return FindObject( t, keydata );············
3417 ········}
3418
3419 ········/// <summary>
3420 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3421 ········/// instance of the object is returned. In either case, the DB is not accessed!
3422 ········/// </summary>
3423 ········/// <param name="id">Object id</param>
3424 ········/// <returns>The object to retrieve in hollow state</returns>········
3425 ········public IPersistenceCapable FindObject(ObjectId id)
3426 ········{
3427 ············if(id == null)
3428 ············{
3429 ················throw new ArgumentNullException("id");
3430 ············}
3431
3432 ············if(!id.IsValid())
3433 ············{
3434 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3435 ············}
3436
3437 ············IPersistenceCapable pc = cache.GetObject(id);
3438 ············if(pc == null)
3439 ············{
3440 ················pc = CreateObject(id.Id.Type);
3441 ················pc.NDOObjectId = id;
3442 ················pc.NDOStateManager = sm;
3443 ················pc.NDOObjectState = NDOObjectState.Hollow;
3444 ················cache.UpdateCache(pc);
3445 ············}
3446 ············return pc;
3447 ········}
3448
3449
3450 ········/// <summary>
3451 ········/// Reload an object from the database.
3452 ········/// </summary>
3453 ········/// <param name="o">The object to be reloaded.</param>
3454 ········public virtual void Refresh(object o)
3455 ········{
3456 ············IPersistenceCapable pc = CheckPc(o);
3457 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3458 ············{
3459 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3460 ············}
3461
3462 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3463 ················return; // Cannot update objects in current transaction
3464
3465 ············MakeHollow(pc);
3466 ············LoadData(pc);
3467 ········}
3468
3469 ········/// <summary>
3470 ········/// Refresh a list of objects.
3471 ········/// </summary>
3472 ········/// <param name="list">The list of objects to be refreshed.</param>
3473 ········public virtual void Refresh(IList list)
3474 ········{
3475 ············foreach (IPersistenceCapable pc in list)
3476 ················Refresh(pc);························
3477 ········}
3478
3479 ········/// <summary>
3480 ········/// Refreshes all unlocked objects in the cache.
3481 ········/// </summary>
3482 ········public virtual void RefreshAll()
3483 ········{
3484 ············Refresh( cache.UnlockedObjects.ToList() );
3485 ········}
3486
3487 ········/// <summary>
3488 ········/// Closes the PersistenceManager and releases all resources.
3489 ········/// </summary>
3490 ········public override void Close()
3491 ········{
3492 ············if (this.isClosing)
3493 ················return;
3494 ············this.isClosing = true;
3495 ············TransactionScope.Dispose();
3496 ············UnloadCache();
3497 ············base.Close();
3498 ········}
3499
3500 ········internal void LogIfVerbose( string msg )
3501 ········{
3502 if ( Logger != null && Logger. IsEnabled( LogLevel. Information ) )
3503 Logger. LogInformation( msg ) ;
3504 ········}
3505
3506
3507 ········#endregion
3508
3509
3510 #region Class extent
3511 ········/// <summary>
3512 ········/// Gets all objects of a given class.
3513 ········/// </summary>
3514 ········/// <param name="t">the type of the class</param>
3515 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3516 ········public virtual IList GetClassExtent(Type t)
3517 ········{
3518 ············return GetClassExtent(t, true);
3519 ········}
3520
3521 ········/// <summary>
3522 ········/// Gets all objects of a given class.
3523 ········/// </summary>
3524 ········/// <param name="t">The type of the class.</param>
3525 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3526 ········/// <returns>A list of all persistent objects of the given class.</returns>
3527 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3528 ········public virtual IList GetClassExtent(Type t, bool hollow)
3529 ········{
3530 ············IQuery q = NewQuery( t, null, hollow );
3531 ············return q.Execute();
3532 ········}
3533
3534 #endregion
3535
3536 #region Query engine
3537
3538
3539 ········/// <summary>
3540 ········/// Returns a virtual table for Linq queries.
3541 ········/// </summary>
3542 ········/// <typeparam name="T"></typeparam>
3543 ········/// <returns></returns>
3544 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3545 ········{
3546 ············return new VirtualTable<T>( this );
3547 ········}
3548
3549 ········
3550 ········/// <summary>
3551 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3552 ········/// 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
3553 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3554 ········/// not lose the foreign key it will be stored as part of the object.
3555 ········/// </summary>
3556 ········/// <param name="cl"></param>
3557 ········/// <param name="pc"></param>
3558 ········/// <param name="row"></param>
3559 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3560 ········{
3561 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3562 ············{
3563 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3564 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3565 ················foreach(string colName in cl.FKColumnNames)
3566 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3567 ················pc.NDOLoadState.LostRowInfo = kvl;················
3568 ············}
3569 ········}
3570
3571 ········/// <summary>
3572 ········/// Writes information into the data row, which cannot be stored in the object.
3573 ········/// </summary>
3574 ········/// <param name="cl"></param>
3575 ········/// <param name="pc"></param>
3576 ········/// <param name="row"></param>
3577 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3578 ········{
3579 ············if (cl.FKColumnNames != null)
3580 ············{
3581 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3582 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3583 ················if (kvl == null)
3584 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3585 ················foreach (KeyValuePair pair in kvl)
3586 ····················row[(string) pair.Key] = pair.Value;
3587 ············}
3588 ········}
3589
3590 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3591 ········{
3592 ············ReadObject(pc, row, cl.ColumnNames, 0);
3593 ············ReadTimeStamp(cl, pc, row);
3594 ············ReadLostForeignKeysFromRow(cl, pc, row);
3595 ············LoadRelated1To1Objects(pc, row);
3596 ············pc.NDOObjectState = NDOObjectState.Persistent;
3597 ········}
3598
3599
3600 ········/// <summary>
3601 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3602 ········/// </summary>
3603 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3604 ········{
3605 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3606 ············if (rows.Count == 0)
3607 ················return queryResult;
3608
3609 ············IList callbackObjects = new ArrayList();
3610 ············Class cl = GetClass(t);
3611 ············if (t.IsGenericTypeDefinition)
3612 ············{
3613 ················if (cl.TypeNameColumn == null)
3614 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3615 ················IEnumerator en = rows.GetEnumerator();
3616 ················en.MoveNext();··// Move to the first element
3617 ················DataRow testrow = (DataRow)en.Current;
3618 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3619 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3620 ············}
3621
3622 ············foreach(DataRow row in rows)
3623 ············{
3624 ················Type concreteType = t;
3625 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3626 ················{
3627 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3628 ····················{
3629 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3630 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3631 ····················}
3632 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3633 ····················concreteType = Type.GetType(typeStr);
3634 ····················if (concreteType == null)
3635 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3636 ················}
3637 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3638 ················IPersistenceCapable pc = cache.GetObject(id);················
3639 ················if(pc == null)
3640 ················{
3641 ····················pc = CreateObject( concreteType );
3642 ····················pc.NDOObjectId = id;
3643 ····················pc.NDOStateManager = sm;
3644 ····················// If the object shouldn't be hollow, this will be overwritten later.
3645 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3646 ················}
3647 ················// If we have found a non hollow object, the time stamp remains the old one.
3648 ················// In every other case we use the new time stamp.
3649 ················// Note, that there could be a hollow object in the cache.
3650 ················// We need the time stamp in hollow objects in order to correctly
3651 ················// delete objects using fake rows.
3652 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3653 ················{
3654 ····················ReadTimeStamp(cl, pc, row);
3655 ················}
3656 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3657 ················{
3658 ····················Row2Object(cl, pc, row);
3659 ····················if ((pc as IPersistenceNotifiable) != null)
3660 ························callbackObjects.Add(pc);
3661 ················}
3662
3663 ················cache.UpdateCache(pc);
3664 ················queryResult.Add(pc);
3665 ············}
3666 ············// Make shure this is the last statement before returning
3667 ············// to the caller, so the user can recursively use persistent
3668 ············// objects
3669 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3670 ················ipn.OnLoaded();
3671
3672 ············return queryResult;
3673 ········}
3674
3675 #endregion
3676
3677 #region Cache Management
3678 ········/// <summary>
3679 ········/// Remove all unused entries from the cache.
3680 ········/// </summary>
3681 ········public void CleanupCache()
3682 ········{
3683 ············GC.Collect(GC.MaxGeneration);
3684 ············GC.WaitForPendingFinalizers();
3685 ············cache.Cleanup();
3686 ········}
3687
3688 ········/// <summary>
3689 ········/// Remove all unlocked objects from the cache. Use with care!
3690 ········/// </summary>
3691 ········public void UnloadCache()
3692 ········{
3693 ············cache.Unload();
3694 ········}
3695 #endregion
3696
3697 ········/// <summary>
3698 ········/// Default encryption key for NDO
3699 ········/// </summary>
3700 ········/// <remarks>
3701 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3702 ········/// </remarks>
3703 ········public virtual byte[] EncryptionKey
3704 ········{
3705 ············get
3706 ············{
3707 ················if (this.encryptionKey == null)
3708 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3709 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3710 ················return this.encryptionKey;
3711 ············}
3712 ············set { this.encryptionKey = value; }
3713 ········}
3714
3715 ········/// <summary>
3716 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3717 ········/// </summary>
3718 ········public virtual bool HollowMode
3719 ········{
3720 ············get { return hollowMode; }
3721 ············set { hollowMode = value; }
3722 ········}
3723
3724 ········internal TypeManager TypeManager
3725 ········{
3726 ············get { return typeManager; }
3727 ········}
3728
3729 ········/// <summary>
3730 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3731 ········/// </summary>
3732 ········/// <remarks>
3733 ········/// Set this value before you start any transactions.
3734 ········/// </remarks>
3735 ········public TransactionMode TransactionMode
3736 ········{
3737 ············get { return TransactionScope.TransactionMode; }
3738 ············set { TransactionScope.TransactionMode = value; }
3739 ········}
3740
3741 ········/// <summary>
3742 ········/// Sets or gets the Isolation Level.
3743 ········/// </summary>
3744 ········/// <remarks>
3745 ········/// Set this value before you start any transactions.
3746 ········/// </remarks>
3747 ········public IsolationLevel IsolationLevel
3748 ········{
3749 ············get { return TransactionScope.IsolationLevel; }
3750 ············set { TransactionScope.IsolationLevel = value; }
3751 ········}
3752
3753 ········internal class MappingTableEntry
3754 ········{
3755 ············private IPersistenceCapable parentObject;
3756 ············private IPersistenceCapable relatedObject;
3757 ············private Relation relation;
3758 ············private bool deleteEntry;
3759 ············
3760 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3761 ············{
3762 ············}
3763 ············
3764 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3765 ············{
3766 ················parentObject = pc;
3767 ················relatedObject = relObj;
3768 ················relation = r;
3769 ················this.deleteEntry = deleteEntry;
3770 ············}
3771
3772 ············public bool DeleteEntry
3773 ············{
3774 ················get
3775 ················{
3776 ····················return deleteEntry;
3777 ················}
3778 ············}
3779
3780 ············public IPersistenceCapable ParentObject
3781 ············{
3782 ················get
3783 ················{
3784 ····················return parentObject;
3785 ················}
3786 ············}
3787
3788 ············public IPersistenceCapable RelatedObject
3789 ············{
3790 ················get
3791 ················{
3792 ····················return relatedObject;
3793 ················}
3794 ············}
3795
3796 ············public Relation Relation
3797 ············{
3798 ················get
3799 ················{
3800 ····················return relation;
3801 ················}
3802 ············}
3803 ········}
3804
3805 ········/// <summary>
3806 ········/// Get a DataRow representing the given object.
3807 ········/// </summary>
3808 ········/// <param name="o"></param>
3809 ········/// <returns></returns>
3810 ········public DataRow GetClonedDataRow( object o )
3811 ········{
3812 ············IPersistenceCapable pc = CheckPc( o );
3813
3814 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3815 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3816
3817 ············DataRow row = cache.GetDataRow( pc );
3818 ············DataTable newTable = row.Table.Clone();
3819 ············newTable.ImportRow( row );
3820 ············row = newTable.Rows[0];
3821
3822 ············Class cls = mappings.FindClass(o.GetType());
3823 ············WriteObject( pc, row, cls.ColumnNames );
3824 ············WriteForeignKeysToRow( pc, row );
3825
3826 ············return row;
3827 ········}
3828
3829 ········/// <summary>
3830 ········/// Gets an object, which shows all changes applied to the given object.
3831 ········/// This function can be used to build an audit system.
3832 ········/// </summary>
3833 ········/// <param name="o"></param>
3834 ········/// <returns>An ExpandoObject</returns>
3835 ········public ChangeLog GetChangeSet( object o )
3836 ········{
3837 ············var changeLog = new ChangeLog(this);
3838 ············changeLog.Initialize( o );
3839 ············return changeLog;
3840 ········}
3841
3842 ········/// <summary>
3843 ········/// Outputs a revision number representing the assembly version.
3844 ········/// </summary>
3845 ········/// <remarks>This can be used for debugging purposes</remarks>
3846 ········public int Revision
3847 ········{
3848 ············get
3849 ············{
3850 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3851 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3852 ················return int.Parse( vstring );
3853 ············}
3854 ········}
3855 ····}
3856 }
3857
New Commit (0a71280)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the ServiceScopeEvent event of the PersistenceManager.
59 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
60 ····/// </summary>
61 ····public delegate IServiceProvider ServiceScopeHandler( Type t, ObjectId oid );
62
63 ····/// <summary>
64 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
65 ····/// </summary>
66 ····public delegate void OnSavingHandler(ICollection l);
67 ····/// <summary>
68 ····/// Delegate type for the OnSavedEvent.
69 ····/// </summary>
70 ····/// <param name="auditSet"></param>
71 ····public delegate void OnSavedHandler(AuditSet auditSet);
72
73 ····/// <summary>
74 ····/// 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.
75 ····/// </summary>
76 ····/// <param name="pc"></param>
77 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
78 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
79 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
80
81 ····/// <summary>
82 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
83 ····/// 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.
84 ····/// </summary>
85 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
86 ····{········
87 ········private bool hollowMode = false;
88 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
89
90 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
91 ········private ObjectLock removeLock = new ObjectLock();
92 ········private ObjectLock addLock = new ObjectLock();
93 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
94 ········// List of created objects that use mapping table and need to be stored in mapping table
95 ········// after they have been stored to the database to update foreign keys.
96 ········private ArrayList createdMappingTableObjects = new ArrayList();··
97 ········private TypeManager typeManager;
98 ········internal bool DeferredMode { get; private set; }
99 ········private INDOTransactionScope transactionScope;
100 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
101
102 ········private OpenConnectionListener openConnectionListener;
103
104 ········/// <summary>
105 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
106 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
107 ········/// </summary>
108 ········public event CollisionHandler CollisionEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
112 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
113 ········/// </summary>
114 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
115
116 ········/// <summary>
117 ········/// Register a listener to this event, if you want to be notified about the end
118 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
119 ········/// during the transaction and are to be saved or deleted.
120 ········/// </summary>
121 ········public event OnSavingHandler OnSavingEvent;
122 ········/// <summary>
123 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
124 ········/// </summary>
125 ········public event OnSavedHandler OnSavedEvent;
126 ········
127 ········private const string hollowMarker = "Hollow";
128 ········private byte[] encryptionKey;
129 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
130 ········private bool isClosing = false;
131
132 ········/// <summary>
133 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
134 ········/// </summary>
135 ········protected internal List<RelationChangeRecord> RelationChanges
136 ········{
137 ············get { return this.relationChanges; }
138 ········}
139
140 ········/// <summary>
141 ········/// Initializes a new PersistenceManager instance.
142 ········/// </summary>
143 ········/// <param name="mappingFileName"></param>
144 ········protected override void Init(string mappingFileName)
145 ········{
146 ············try
147 ············{
148 ················base.Init(mappingFileName);
149 ············}
150 ············catch (Exception ex)
151 ············{
152 ················if (ex is NDOException)
153 ····················throw;
154 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
155 ············}
156
157 ········}
158
159 ········/// <summary>
160 ········/// Initializes the persistence manager
161 ········/// </summary>
162 ········/// <remarks>
163 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
164 ········/// </remarks>
165 ········/// <param name="mapping"></param>
166 ········internal override void Init( Mappings mapping )
167 ········{
168 ············base.Init( mapping );
169
170 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
171
172 ············string dir = Path.GetDirectoryName( mapping.FileName );
173
174 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
175 ············typeManager = new TypeManager( typesFile, this.mappings );
176
177 ············sm = new StateManager( this );
178
179 ············InitClasses();
180 ········}
181
182
183 ········/// <summary>
184 ········/// Standard Constructor.
185 ········/// </summary>
186 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
187 ········/// <remarks>
188 ········/// Searches for a mapping file in the application directory.
189 ········/// The constructor tries to find a file with the same name as
190 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
191 ········/// file called AssemblyName.ndo.mapping in the application directory.
192 ········/// </remarks>
193 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
194 ········{
195 ········}
196
197 ········/// <summary>
198 ········/// Loads the mapping file from the specified location. This allows to use
199 ········/// different mapping files with different classes mapped in it.
200 ········/// </summary>
201 ········/// <param name="mappingFile">Path to the mapping file.</param>
202 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
203 ········/// <remarks>Only the Professional and Enterprise
204 ········/// Editions can handle more than one mapping file.</remarks>
205 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
206 ········{
207 ········}
208
209 ········/// <summary>
210 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
211 ········/// </summary>
212 ········/// <param name="mapping">The cached mapping object</param>
213 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
214 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
215 ········{
216 ········}
217
218 ········#region Object Container Stuff
219 ········/// <summary>
220 ········/// Gets a container of all loaded objects and tries to load all child objects,
221 ········/// which are reachable through composite relations.
222 ········/// </summary>
223 ········/// <returns>An ObjectContainer object.</returns>
224 ········/// <remarks>
225 ········/// It is not recommended, to transfer objects with a state other than Hollow,
226 ········/// Persistent, or Transient.
227 ········/// The transfer format is binary.
228 ········/// </remarks>
229 ········public ObjectContainer GetObjectContainer()
230 ········{
231 ············IList l = this.cache.AllObjects;
232 ············foreach(IPersistenceCapable pc in l)
233 ············{
234 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
235 ················{
236 ····················if (Logger != null)
237 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
 
238 ················}
239 ············}
240
241 ············ObjectContainer oc = new ObjectContainer();
242 ············oc.AddList(l);
243 ············return oc;
244 ········}
245
246 ········/// <summary>
247 ········/// Returns a container of all objects provided in the objects list and searches for
248 ········/// child objects according to the serFlags.
249 ········/// </summary>
250 ········/// <param name="objects">The list of the root objects to add to the container.</param>
251 ········/// <returns>An ObjectContainer object.</returns>
252 ········/// <remarks>
253 ········/// It is not recommended, to transfer objects with a state other than Hollow,
254 ········/// Persistent, or Transient.
255 ········/// </remarks>
256 ········public ObjectContainer GetObjectContainer(IList objects)
257 ········{
258 ············foreach(object o in objects)
259 ············{
260 ················CheckPc(o);
261 ················IPersistenceCapable pc = o as IPersistenceCapable;
262 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
263 ····················LoadData(pc);
264 ············}
265 ············ObjectContainer oc = new ObjectContainer();
266 ············oc.AddList(objects);
267 ············return oc;
268 ········}
269
270
271 ········/// <summary>
272 ········/// Returns a container containing the provided object
273 ········/// and tries to load all child objects
274 ········/// reachable through composite relations.
275 ········/// </summary>
276 ········/// <param name="obj">The object to be added to the container.</param>
277 ········/// <returns>An ObjectContainer object.</returns>
278 ········/// <remarks>
279 ········/// It is not recommended, to transfer objects with a state other than Hollow,
280 ········/// Persistent, or Transient.
281 ········/// The transfer format is binary.
282 ········/// </remarks>
283 ········public ObjectContainer GetObjectContainer(Object obj)
284 ········{
285 ············CheckPc(obj);
286 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
287 ················LoadData(obj);
288 ············ObjectContainer oc = new ObjectContainer();
289 ············oc.AddObject(obj);
290 ············return oc;
291 ········}
292
293 ········/// <summary>
294 ········/// Merges an object container to the active objects in the pm. All changes and the state
295 ········/// of the objects will be taken over by the pm.
296 ········/// </summary>
297 ········/// <remarks>
298 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
299 ········/// The flag MarkAsTransient can be used to perform a kind
300 ········/// of object based replication using the ObjectContainer class.
301 ········/// Objects, which are persistent at one machine, can be transfered
302 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
303 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
304 ········/// transient object tree.
305 ········/// There is one difference to freshly created objects: If an object id exists, it will be
306 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
307 ········/// oids will be reused by the receiving PersistenceManager.
308 ········/// </remarks>
309 ········/// <param name="ocb">The object container to be merged.</param>
310 ········public void MergeObjectContainer(ObjectContainerBase ocb)
311 ········{
312 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
313 ············if (csc != null)
314 ············{
315 ················MergeChangeSet(csc);
316 ················return;
317 ············}
318 ············ObjectContainer oc = ocb as ObjectContainer;
319 ············if (oc != null)
320 ············{
321 ················InternalMergeObjectContainer(oc);
322 ················return;
323 ············}
324 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
325 ········}
326
327
328 ········void InternalMergeObjectContainer(ObjectContainer oc)
329 ········{
330 ············// TODO: Check, if other states are useful. Find use scenarios.
331 ············foreach(IPersistenceCapable pc in oc.RootObjects)
332 ············{
333 ················if (pc.NDOObjectState == NDOObjectState.Transient)
334 ····················MakePersistent(pc);
335 ············}
336 ············foreach(IPersistenceCapable pc in oc.RootObjects)
337 ············{
338 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
339 ············}
340 ········}
341
342 ········void MergeChangeSet(ChangeSetContainer cs)
343 ········{
344 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
345 ············{
346 ················InternalMakePersistent(pc, false);
347 ············}
348 ············foreach(ObjectId oid in cs.DeletedObjects)
349 ············{
350 ················IPersistenceCapable pc2 = FindObject(oid);
351 ················Delete(pc2);
352 ············}
353 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
354 ············{
355 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
356 ················Class pcClass = GetClass(pc);
357 ················// Make sure, the object is loaded.
358 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
359 ····················LoadData(pc2);
360 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
361 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
362 ················DataRow row = entry.row;
363 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
364 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
365 ············}
366 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
367 ············{
368 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
369 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
370 ················Class pcClass = GetClass(parent);
371 ················Relation r = pcClass.FindRelation(rcr.RelationName);
372 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
373 ····················LoadRelation(parent, r, true);
374 ················if (rcr.IsAdded)
375 ················{
376 ····················InternalAddRelatedObject(parent, r, child, true);
377 ····················if (r.Multiplicity == RelationMultiplicity.Element)
378 ····················{
379 ························mappings.SetRelationField(parent, r.FieldName, child);
380 ····················}
381 ····················else
382 ····················{
383 ························IList l = mappings.GetRelationContainer(parent, r);
384 ························l.Add(child);
385 ····················}
386 ················}
387 ················else
388 ················{
389 ····················RemoveRelatedObject(parent, r.FieldName, child);
390 ····················if (r.Multiplicity == RelationMultiplicity.Element)
391 ····················{
392 ························mappings.SetRelationField(parent, r.FieldName, null);
393 ····················}
394 ····················else
395 ····················{
396 ························IList l = mappings.GetRelationContainer(parent, r);
397 ························try
398 ························{
399 ····························ObjectListManipulator.Remove(l, child);
400 ························}
401 ························catch
402 ························{
403 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
404 ························}
405 ····················}
406 ················}
407 ············}
408
409 ········}
410 ········#endregion
411
412 ········#region Implementation of IPersistenceManager
413
414 ········// Complete documentation can be found in IPersistenceManager
415
416
417 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
418 ········{
419 ············if (!cl.Oid.IsDependent)
420 ················return;
421 ············WriteForeignKeysToRow(pc, row);
422 ········}
423
424 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
425 ········{
426 ············// Object is now under control of the state manager
427 ············pc.NDOStateManager = sm;
428
429 ············Type pcType = pc.GetType();
430 ············Class pcClass = GetClass(pc);
431
432 ············// Create new object
433 ············DataTable dt = GetTable(pcType);
434 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
435
436 ············// In case of a Guid oid the value will be computed now.
437 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
438 ············{
439 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
440 ················{
441 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
442 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
443 ····················else
444 ························row[oidColumn.Name] = Guid.NewGuid();
445 ················}
446 ············}
447
448 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
449
450 ············// If the object is merged from an ObjectContainer, the id should be reused,
451 ············// if the id is client generated (not Autoincremented).
452 ············// In every other case, the oid is set to null, to force generating a new oid.
453 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
454 ············if ((object)pc.NDOObjectId != null)
455 ············{
456 ················bool hasAutoincrement = false;
457 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
458 ················{
459 ····················if (oidColumn.AutoIncremented)
460 ····················{
461 ························hasAutoincrement = true;
462 ························break;
463 ····················}
464 ················}
465 ················if (hasAutoincrement) // can't store existing id
466 ················{
467 ····················pc.NDOObjectId = null;
468 ····················fireIdGeneration = true;
469 ················}
470 ············}
471
472 ············// In case of a dependent class the oid has to be read from the fields according to the relations
473 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
474
475 ············if ((object)pc.NDOObjectId == null)
476 ············{
477 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
478 ············}
479
480 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
481 ············{
482 ················if (fireIdGeneration)
483 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
484 ················// At this place the oid might have been
485 ················// - deserialized (MergeObjectContainer)
486 ················// - created using NewObjectId
487 ················// - defined by the IdGenerationEvent
488
489 ················// At this point we have a valid oid.
490 ················// If the object has a field mapped to the oid we have
491 ················// to write back the oid to the field
492 ················int i = 0;
493 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
494 ················{
495 ····················if (oidColumn.FieldName != null)
496 ····················{
497 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
498 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
499 ····················}
500 ····················i++;
501 ················});
502
503
504
505 ················// Now write back the data into the row
506 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
507 ············}
508
509 ············
510 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
511 ············dt.Rows.Add(row);
512
513 ············cache.Register(pc);
514
515 ············// new object that has never been written to the DS
516 ············pc.NDOObjectState = NDOObjectState.Created;
517 ············// Mark all Relations as loaded
518 ············SetRelationState(pc);
519
520 ············if (checkRelations)
521 ············{
522 ················// Handle related objects:
523 ················foreach(Relation r in pcClass.Relations)
524 ················{
525 ····················if (r.Multiplicity == RelationMultiplicity.Element)
526 ····················{
527 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
528 ························if(child != null)
529 ························{
530 ····························AddRelatedObject(pc, r, child);
531 ························}
532 ····················}
533 ····················else
534 ····················{
535 ························IList list = mappings.GetRelationContainer(pc, r);
536 ························if(list != null)
537 ························{
538 ····························foreach(IPersistenceCapable relObj in list)
539 ····························{
540 ································if (relObj != null)
541 ····································AddRelatedObject(pc, r, relObj);
542 ····························}
543 ························}
544 ····················}
545 ················}
546 ············}
547
548 ············var relations··= CollectRelationStates(pc);
549 ············cache.Lock(pc, row, relations);
550 ········}
551
552
553 ········/// <summary>
554 ········/// Make an object persistent.
555 ········/// </summary>
556 ········/// <param name="o">the transient object that should be made persistent</param>
557 ········public void MakePersistent(object o)
558 ········{
559 ············IPersistenceCapable pc = CheckPc(o);
560
561 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
562 ············//Debug.Indent();
563
564 ············if (pc.NDOObjectState != NDOObjectState.Transient)
565 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
566
567 ············InternalMakePersistent(pc, true);
568
569 ········}
570
571
572
573 ········//········/// <summary>
574 ········//········/// Checks, if an object has a valid id, which was created by the database
575 ········//········/// </summary>
576 ········//········/// <param name="pc"></param>
577 ········//········/// <returns></returns>
578 ········//········private bool HasValidId(IPersistenceCapable pc)
579 ········//········{
580 ········//············if (this.IdGenerationEvent != null)
581 ········//················return true;
582 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
583 ········//········}
584
585
586 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
587 ········{
588 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
589 ············if(r.HasSubclasses)
590 ············{
591 ················// we don't support 1:n with foreign fields in subclasses because we would have to
592 ················// search for objects in all subclasses! Instead use a mapping table.
593 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
594 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
595 ············}
596
597 ············if (!makeRelObjPersistent)
598 ················MarkDirty(relObj);
599 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
600 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
601
602 ············if (relObjRow == null)
603 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
604
605 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
606
607 ············if (relObj.NDOLoadState.LostRowInfo == null)
608 ············{
609 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
610 ············}
611 ············else
612 ············{
613 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
614 ············}
615 ········}
616
617 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
618 ········{
619 ············switch(relObj.NDOObjectState)
620 ············{
621 ················case NDOObjectState.Persistent:
622 ····················MarkDirty(relObj);
623 ····················break;
624 ················case NDOObjectState.Hollow:
625 ····················LoadData(relObj);
626 ····················MarkDirty(relObj);
627 ····················break;
628 ············}
629
630 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
631 ············{
632 ················IPersistenceCapable newpc;
633 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
634 ················{
635 ····················if (newpc != pc)
636 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
637 ················}
638 ················else
639 ················{
640 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
641 ················}
642 ············}
643 ············else
644 ············{
645 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
646 ····················LoadRelation(relObj, r.ForeignRelation, true);
647 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
648 ················if(l == null)
649 ················{
650 ····················try
651 ····················{
652 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
653 ····················}
654 ····················catch
655 ····················{
656 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
657 ····················}
658 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
659 ················}
660 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
661 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
662 ················if (!ObjectListManipulator.Contains(l, pc))
663 ····················l.Add(pc);
664 ············}
665 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
666 ········}
667
668
669 ········/// <summary>
670 ········/// Add a related object to the specified object.
671 ········/// </summary>
672 ········/// <param name="pc">the parent object</param>
673 ········/// <param name="fieldName">the field name of the relation</param>
674 ········/// <param name="relObj">the related object that should be added</param>
675 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
676 ········{
677 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
678 ············Relation r = mappings.FindRelation(pc, fieldName);
679 ············AddRelatedObject(pc, r, relObj);
680 ········}
681
682 ········/// <summary>
683 ········/// Core functionality to add an object to a relation container or relation field.
684 ········/// </summary>
685 ········/// <param name="pc"></param>
686 ········/// <param name="r"></param>
687 ········/// <param name="relObj"></param>
688 ········/// <param name="isMerging"></param>
689 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
690 ········{
691 ············
692 ············// avoid recursion
693 ············if (!addLock.GetLock(relObj))
694 ················return;
695
696 ············try
697 ············{
698 ················//TODO: We need a relation management, which is independent of
699 ················//the state management of an object. Currently the relation
700 ················//lists or elements are cached for restore, if an object is marked dirty.
701 ················//Thus we have to mark dirty our parent object in any case at the moment.
702 ················MarkDirty(pc);
703
704 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
705 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
706 ················//The latter happens in CreateAddedObjectRow
707
708 ················Class relClass = GetClass(relObj);
709
710 ················if (r.Multiplicity == RelationMultiplicity.Element
711 ····················&& r.HasSubclasses
712 ····················&& r.MappingTable == null················
713 ····················&& !this.HasOwnerCreatedIds
714 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
715 ····················&& !relClass.HasGuidOid)
716 ················{
717 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
718 ························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.");
719 ····················if (r.Composition)
720 ························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.");
721 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
722 ························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." );
723 ················}
724
725 ················bool isDependent = relClass.Oid.IsDependent;
726
727 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
728 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
729
730 ················// Need to patch pc into the relation relObj->pc, because
731 ················// the oid is built on base of this information
732 ················if (isDependent)
733 ················{
734 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
735 ················}
736
737 ················if (r.Composition || isDependent)
738 ················{
739 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
740 ························MakePersistent(relObj);
741 ················}
742
743 ················if(r.MappingTable == null)
744 ················{
745 ····················if (r.Bidirectional)
746 ····················{
747 ························// This object hasn't been saved yet, so the key is wrong.
748 ························// Therefore, the child must be written twice to update the foreign key.
749 #if trace
750 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
751 ························if (r.Multiplicity == RelationMultiplicity.Element)
752 ····························sb.Append("1");
753 ························else
754 ····························sb.Append("n");
755 ························sb.Append(":");
756 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
757 ····························sb.Append("1");
758 ························else
759 ····························sb.Append("n");
760 ························sb.Append ("OwnCreatedOther");
761 ························sb.Append(relObj.NDOObjectState.ToString());
762 ························sb.Append(' ');
763
764 ························sb.Append(types[0].ToString());
765 ························sb.Append(' ');
766 ························sb.Append(types[1].ToString());
767 ························Debug.WriteLine(sb.ToString());
768 #endif
769 ························//························if (r.Multiplicity == RelationMultiplicity.Element
770 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
771 ························//························{
772 ························// Element means:
773 ························// pc is keyholder
774 ························// -> relObj is saved first
775 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
776 ························// Both are Created - use type sort order
777 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
778 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
779 ························{
780 ····························if (mappings.GetUpdateOrder(pc.GetType())
781 ································< mappings.GetUpdateOrder(relObj.GetType()))
782 ································createdDirectObjects.Add(pc);
783 ····························else
784 ································createdDirectObjects.Add( relObj );
785 ························}
786 ····················}
787 ····················if (r.Multiplicity == RelationMultiplicity.List)
788 ····················{
789 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
790 ····················}
791 ················}
792 ················else
793 ················{
794 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
795 ················}
796 ················if(r.Bidirectional)
797 ················{
798 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
799 ····················{
800 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
801 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
802 ····················}
803 ····················else if ( !addLock.IsLocked( pc ) )
804 ····················{
805 ························PatchForeignRelation( pc, r, relObj );
806 ····················}
807 ················}
808
809 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
810 ············}
811 ············finally
812 ············{
813 ················addLock.Unlock(relObj);
814 ················//Debug.Unindent();
815 ············}
816 ········}
817
818 ········/// <summary>
819 ········/// Returns an integer value which determines the rank of the given type in the update order list.
820 ········/// </summary>
821 ········/// <param name="t">The type to determine the update order.</param>
822 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
823 ········/// <remarks>
824 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
825 ········/// </remarks>
826 ········public int GetUpdateRank(Type t)
827 ········{
828 ············return mappings.GetUpdateOrder(t);
829 ········}
830
831 ········/// <summary>
832 ········/// Add a related object to the specified object.
833 ········/// </summary>
834 ········/// <param name="pc">the parent object</param>
835 ········/// <param name="r">the relation mapping info</param>
836 ········/// <param name="relObj">the related object that should be added</param>
837 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
838 ········{
839 ············//············string idstr;
840 ············//············if (relObj.NDOObjectId == null)
841 ············//················idstr = relObj.GetType().ToString();
842 ············//············else
843 ············//················idstr = relObj.NDOObjectId.Dump();
844 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
845 ············//Debug.Indent();
846
847 ············Class relClass = GetClass(relObj);
848 ············bool isDependent = relClass.Oid.IsDependent;
849
850 ············// Do some checks to guarantee that the assignment is correct
851 ············if(r.Composition)
852 ············{
853 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
854 ················{
855 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
856 ················}
857 ············}
858 ············else
859 ············{
860 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
861 ················{
862 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
863 ················}
864 ············}
865
866 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
867 ············{
868 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
869 ············}
870
871 ············InternalAddRelatedObject(pc, r, relObj, false);
872
873 ········}
874
875 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
876 ········{
877 ············// Need to patch pc into the relation relObj->pc, because
878 ············// the oid is built on base of this information
879 ············// The second relation has to be set before adding relObj
880 ············// to the relation list.
881 ············PatchForeignRelation(pc, r, relObj);
882 ············IPersistenceCapable parent;
883 ············foreach (Relation oidRelation in relClass.Oid.Relations)
884 ············{
885 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
886 ················if (parent == null)
887 ····················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.");
888 ················if (parent.NDOObjectState == NDOObjectState.Transient)
889 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
890
891 ············}
892 ········}
893
894
895 ········/// <summary>
896 ········/// Remove a related object from the specified object.
897 ········/// </summary>
898 ········/// <param name="pc">the parent object</param>
899 ········/// <param name="fieldName">Field name of the relation</param>
900 ········/// <param name="relObj">the related object that should be removed</param>
901 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
902 ········{
903 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
904 ············Relation r = mappings.FindRelation(pc, fieldName);
905 ············InternalRemoveRelatedObject(pc, r, relObj, true);
906 ········}
907
908 ········/// <summary>
909 ········/// Registers a listener which will be notified, if a new connection is opened.
910 ········/// </summary>
911 ········/// <param name="listener">Delegate of a listener function</param>
912 ········/// <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>
913 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
914 ········{
915 ············this.openConnectionListener = listener;
916 ········}
917
918 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
919 ········{
920 ············if (openConnectionListener != null)
921 ················return openConnectionListener(conn);
922 ············return conn.Name;
923 ········}
924
925
926 ········/*
927 ········doCommit should be:
928 ········
929 ····················Query····Save····Save(true)
930 ········Optimistic····1········1········0
931 ········Pessimistic····0········1········0
932 ············
933 ········Deferred Mode············
934 ····················Query····Save····Save(true)
935 ········Optimistic····0········1········0
936 ········Pessimistic····0········1········0
937 ········ */
938
939 ········internal void CheckEndTransaction(bool doCommit)
940 ········{
941 ············if (doCommit)
942 ············{
943 ················TransactionScope.Complete();
944 ············}
945 ········}
946
947 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
948 ········{
949 ············CheckTransaction(handler, this.GetClass(t).Connection);
950 ········}
951
952 ········/// <summary>
953 ········/// Each and every database operation has to be preceded by a call to this function.
954 ········/// </summary>
955 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
956 ········{
957 ············TransactionScope.CheckTransaction();
958 ············
959 ············if (handler.Connection == null)
960 ············{
961 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
962 ················{
963 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
964 ····················string connStr = this.OnNewConnection( ndoConn );
965 ····················var connection = p.NewConnection( connStr );
966 ····················if (connection == null)
967 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
968 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
969 ····················return connection;
970 ················} );
971 ············}
972
973 ············if (TransactionMode != TransactionMode.None)
974 ············{
975 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
976 ············}
977
978 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
979 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
980 ············{
981 ················handler.Connection.Open();
982 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
983 ············}
984 ········}
985
986 ········/// <summary>
987 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
988 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
989 ········/// This is possible if there is a listener for the CollisionEvent.
990 ········/// Else we throw an exception.
991 ········/// </summary>
992 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
993 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
994 ········{
995 ············DataRow row = ex.Row;
996 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
997 ················throw(ex);
998 ············if (row.RowState == DataRowState.Detached)
999 ················return;
1000 ············foreach (Cache.Entry e in cache.LockedObjects)
1001 ············{
1002 ················if (e.row == row)
1003 ················{
1004 ····················CollisionEvent(e.pc);
1005 ····················return;
1006 ················}
1007 ············}
1008 ············throw ex;
1009 ········}
1010
1011
1012 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1013 ········{
1014 ············Class cl = GetClass(pc);
1015 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1016 ············Dictionary<string,MemberInfo> persistentFields = null;
1017 ············if (etypes.Length > 0)
1018 ············{
1019 ················FieldMap fm = new FieldMap(cl);
1020 ················persistentFields = fm.PersistentFields;
1021 ············}
1022 ············foreach(string s in etypes)
1023 ············{
1024 ················try
1025 ················{
1026 ····················NDO.Mapping.Field f = cl.FindField(s);
1027 ····················if (f == null)
1028 ························continue;
1029 ····················object o = row[f.Column.Name];
1030 ····················string[] arr = s.Split('.');
1031 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1032 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1033 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1034 ····················// Hole das Embedded Object
1035 ····················object parentOb = parentFi.GetValue(pc);
1036
1037 ····················if (parentOb == null)
1038 ························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]));
1039
1040 ····················// Suche darin das Feld mit Namen Arr[1]
1041
1042 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1043 ····················Type childType = childFi.FieldType;
1044
1045 ····················// Don't initialize value types, if DBNull is stored in the field.
1046 ····················// Exception: DateTime and Guid.
1047 ····················if (o == DBNull.Value && childType.IsValueType
1048 ························&& childType != typeof(Guid)
1049 ························&& childType != typeof(DateTime))
1050 ························continue;
1051
1052 ····················if (childType == typeof(DateTime))
1053 ····················{
1054 ························if (o == DBNull.Value)
1055 ····························o = DateTime.MinValue;
1056 ····················}
1057 ····················if (childType.IsClass)
1058 ····················{
1059 ························if (o == DBNull.Value)
1060 ····························o = null;
1061 ····················}
1062
1063 ····················if (childType == typeof (Guid))
1064 ····················{
1065 ························if (o == DBNull.Value)
1066 ····························o = Guid.Empty;
1067 ························if (o is string)
1068 ························{
1069 ····························childFi.SetValue(parentOb, new Guid((string)o));
1070 ························}
1071 ························else if (o is Guid)
1072 ························{
1073 ····························childFi.SetValue(parentOb, o);
1074 ························}
1075 ························else if (o is byte[])
1076 ························{
1077 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1078 ························}
1079 ························else
1080 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1081 ····················}
1082 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1083 ····················{
1084 ························object childOb = childFi.GetValue(parentOb);
1085 ························FieldInfo valueFi = childType.GetField("value__");
1086 ························valueFi.SetValue(childOb, o);
1087 ························childFi.SetValue(parentOb, childOb);
1088 ····················}
1089 ····················else
1090 ····················{
1091 ························childFi.SetValue(parentOb, o);
1092 ····················}
1093 ················}
1094 ················catch (Exception ex)
1095 ················{
1096 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1097
1098 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1099 ················}
1100
1101 ············}
1102 ············
1103 ············try
1104 ············{
1105 ················if (cl.HasEncryptedFields)
1106 ················{
1107 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1108 ····················{
1109 ························string name = field.Column.Name;
1110 ························string s = (string) row[name];
1111 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1112 ························row[name] = es;
1113 ····················}
1114 ················}
1115 ················pc.NDORead(row, fieldNames, startIndex);
1116 ············}
1117 ············catch (Exception ex)
1118 ············{
1119 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1120 ····················+ ex.Message);
1121 ············}
1122 ········}
1123
1124 ········/// <summary>
1125 ········/// Executes a sql script to generate the database tables.
1126 ········/// The function will execute any sql statements in the script
1127 ········/// which are valid according to the
1128 ········/// rules of the underlying database. Result sets are ignored.
1129 ········/// </summary>
1130 ········/// <param name="scriptFile">The script file to execute.</param>
1131 ········/// <param name="conn">A connection object, containing the connection
1132 ········/// string to the database, which should be altered.</param>
1133 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1134 ········/// <remarks>
1135 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1136 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1137 ········/// Their message property will appear in the result array.
1138 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1139 ········/// </remarks>
1140 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1141 ········{
1142 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1143 ········}
1144
1145 ········/// <summary>
1146 ········/// Executes a sql script to generate the database tables.
1147 ········/// The function will execute any sql statements in the script
1148 ········/// which are valid according to the
1149 ········/// rules of the underlying database. Result sets are ignored.
1150 ········/// </summary>
1151 ········/// <param name="scriptFile">The script file to execute.</param>
1152 ········/// <param name="conn">A connection object, containing the connection
1153 ········/// string to the database, which should be altered.</param>
1154 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1155 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1156 ········/// <remarks>
1157 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1158 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1159 ········/// Their message property will appear in the result array.
1160 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1161 ········/// </remarks>
1162 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1163 ········{
1164 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1165 ············string s = sr.ReadToEnd();
1166 ············sr.Close();
1167 ············string[] arr = s.Split(';');
1168 ············string last = arr[arr.Length - 1];
1169 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1170 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1171 ············using (var handler = GetSqlPassThroughHandler())
1172 ············{
1173 ················int i = 0;
1174 ················string ok = "OK";
1175 ················foreach (string statement in arr)
1176 ················{
1177 ····················if (statement != null && statement.Trim() != string.Empty)
1178 ····················{
1179 ························try
1180 ························{
1181 ····························handler.Execute(statement);
1182 ····························result[i] = ok;
1183 ························}
1184 ························catch (Exception ex)
1185 ························{
1186 ····························result[i] = ex.Message;
1187 ························}
1188 ····················}
1189 ····················i++;
1190 ················}
1191 ················CheckEndTransaction(true);
1192 ············}
1193 ············return result;
1194 ········}
1195
1196 ········/// <summary>
1197 ········/// Executes a sql script to generate the database tables.
1198 ········/// The function will execute any sql statements in the script
1199 ········/// which are valid according to the
1200 ········/// rules of the underlying database. Result sets are ignored.
1201 ········/// </summary>
1202 ········/// <param name="scriptFile">The script file to execute.</param>
1203 ········/// <returns></returns>
1204 ········/// <remarks>
1205 ········/// This function takes the first Connection object in the Connections list
1206 ········/// of the Mapping file und executes the script using that connection.
1207 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1208 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1209 ········/// Their message property will appear in the result array.
1210 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1211 ········/// </remarks>
1212 ········public string[] BuildDatabase(string scriptFile)
1213 ········{
1214 ············if (!File.Exists(scriptFile))
1215 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1216 ············if (!this.mappings.Connections.Any())
1217 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1218 ············Connection conn = new Connection( this.mappings );
1219 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1220 ············conn.Name = OnNewConnection( originalConnection );
1221 ············conn.Type = originalConnection.Type;
1222 ············//Connection conn = (Connection) this.mappings.Connections[0];
1223 ············return BuildDatabase(scriptFile, conn);
1224 ········}
1225
1226 ········/// <summary>
1227 ········/// Executes a sql script to generate the database tables.
1228 ········/// The function will execute any sql statements in the script
1229 ········/// which are valid according to the
1230 ········/// rules of the underlying database. Result sets are ignored.
1231 ········/// </summary>
1232 ········/// <returns>
1233 ········/// A string array, containing the error messages produced by the statements
1234 ········/// contained in the script.
1235 ········/// </returns>
1236 ········/// <remarks>
1237 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1238 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1239 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1240 ········/// This function takes the first Connection object in the Connections list
1241 ········/// of the Mapping file und executes the script using that connection.
1242 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1243 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1244 ········/// Their message property will appear in the result array.
1245 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1246 ········/// </remarks>
1247 ········public string[] BuildDatabase()
1248 ········{
1249 ············Assembly ass = Assembly.GetEntryAssembly();
1250 ············if (ass == null)
1251 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1252 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1253 ············return BuildDatabase(file);
1254 ········}
1255
1256 ········/// <summary>
1257 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1258 ········/// </summary>
1259 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1260 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1261 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1262 ········{
1263 ············if (!this.mappings.Connections.Any())
1264 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1265 ············if (conn == null)
1266 ············{
1267 ················conn = new Connection( this.mappings );
1268 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1269 ················conn.Name = OnNewConnection( originalConnection );
1270 ················conn.Type = originalConnection.Type;
1271 ············}
1272
1273 ············return new SqlPassThroughHandler( this, conn );
1274 ········}
1275
1276 ········/// <summary>
1277 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1278 ········/// </summary>
1279 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1280 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1281 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1282 ········{
1283 ············if (!this.mappings.Connections.Any())
1284 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1285 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1286 ············if (conn == null)
1287 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1288 ············return GetSqlPassThroughHandler( conn );
1289 ········}
1290
1291 ········/// <summary>
1292 ········/// Executes a xml script to generate the database tables.
1293 ········/// The function will generate and execute sql statements to perform
1294 ········/// the changes described by the xml.
1295 ········/// </summary>
1296 ········/// <returns></returns>
1297 ········/// <remarks>
1298 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1299 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1300 ········/// This function takes the first Connection object in the Connections list
1301 ········/// of the Mapping file und executes the script using that connection.
1302 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1303 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1304 ········/// Their message property will appear in the result string array.
1305 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1306 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1307 ········/// </remarks>
1308 ········public string[] PerformSchemaTransitions()
1309 ········{
1310 ············Assembly ass = Assembly.GetEntryAssembly();
1311 ············if (ass == null)
1312 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1313 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1314 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1315 ············if (fileNames.Count == 0)
1316 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1317 ············if (fileNames.Count > 1)
1318 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1319 ············return PerformSchemaTransitions( fileNames[0] );
1320 ········}
1321
1322
1323 ········/// <summary>
1324 ········/// Executes a xml script to generate the database tables.
1325 ········/// The function will generate and execute sql statements to perform
1326 ········/// the changes described by the xml.
1327 ········/// </summary>
1328 ········/// <param name="scriptFile">The script file to execute.</param>
1329 ········/// <returns></returns>
1330 ········/// <remarks>
1331 ········/// This function takes the first Connection object in the Connections list
1332 ········/// of the Mapping file und executes the script using that connection.
1333 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1334 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1335 ········/// Their message property will appear in the result string array.
1336 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1337 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1338 ········/// </remarks>
1339 ········public string[] PerformSchemaTransitions(string scriptFile)
1340 ········{
1341 ············if (!File.Exists(scriptFile))
1342 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1343
1344 ············if (!this.mappings.Connections.Any())
1345 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1346 ············Connection conn = new Connection( mappings );
1347 ············Connection originalConnection = mappings.Connections.First();
1348 ············conn.Name = OnNewConnection( originalConnection );
1349 ············conn.Type = originalConnection.Type;
1350 ············return PerformSchemaTransitions(scriptFile, conn);
1351 ········}
1352
1353
1354 ········int CompareFileName( string fn1, string fn2)
1355 ········{
1356 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1357 ············Match match = regex.Match( fn1 );
1358 ············string v1 = match.Groups[1].Value;
1359 ············match = regex.Match( fn2 );
1360 ············string v2 = match.Groups[1].Value;
1361 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1362 ········}
1363
1364 ········string GetSchemaVersion(Connection ndoConn, string schemaName)
1365 ········{
1366 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1367 ············string version = "0.0";··// Initial value
1368 ············var connection = provider.NewConnection( ndoConn.Name );
1369 ············using (var handler = GetSqlPassThroughHandler())
1370 ············{
1371 ················string[] TableNames = provider.GetTableNames( connection );
1372 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaVersion", true ) == 0 ))
1373 ················{
1374 ····················string sql = "SELECT Version from NDOSchemaVersion WHERE SchemaName ";
1375 ····················if (schemaName == null)
1376 ························sql += "IS NULL;";
1377 ····················else
1378 ························sql += "LIKE '" + schemaName + "'";
1379 ····················using(IDataReader dr = handler.Execute(sql, true))
1380 ····················{
1381 ························if (dr.Read())
1382 ····························version = dr.GetString( 0 );
1383 ····················}
1384 ················}
1385 ················else
1386 ················{
1387 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1388 ····················string transition = @"<NdoSchemaTransition>
1389 ····<CreateTable name=""NDOSchemaVersion"">
1390 ······<CreateColumn name=""SchemaName"" type=""System.String,mscorlib"" allowNull=""True"" />
1391 ······<CreateColumn name=""Version"" type=""System.String,mscorlib"" size=""50"" />
1392 ····</CreateTable>
1393 </NdoSchemaTransition>";
1394 ····················XElement transitionElement = XElement.Parse(transition);
1395
1396 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1397 ····················handler.Execute(sql);
1398 ····················var colSchemaName = provider.GetQuotedName("SchemaName");
1399 ····················var colVersion = provider.GetQuotedName("Version");
1400 ····················var schemaVal = schemaName == null ? "NULL" : provider.GetSqlLiteral( schemaName );
1401 ····················sql = String.Format( $"INSERT INTO NDOSchemaVersion({colSchemaName},{colVersion}) VALUES({schemaVal},'0')" );
1402 ····················handler.Execute( sql );
1403 ····················handler.CommitTransaction();
1404 ················}
1405 ············}
1406
1407 ············return version;
1408 ········}
1409
1410 ········/// <summary>
1411 ········/// Executes a xml script to generate the database tables.
1412 ········/// The function will generate and execute sql statements to perform
1413 ········/// the changes described by the xml.
1414 ········/// </summary>
1415 ········/// <param name="scriptFile">The xml script file.</param>
1416 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1417 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1418 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1419 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1420 ········{
1421 ············string schemaName = null;
1422 ············// Gespeicherte Version ermitteln.
1423 ············XElement transitionElements = XElement.Load( scriptFile );
1424 ············if (transitionElements.Attribute( "schemaName" ) != null)
1425 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1426 ············Version version = new Version( GetSchemaVersion( ndoConn, schemaName ) );
1427 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1428 ············MemoryStream ms = new MemoryStream();
1429 ············StreamWriter sw = new StreamWriter(ms, System.Text.Encoding.UTF8);
1430 ············bool hasChanges = false;
1431
1432 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition").Where(e=>new Version(e.Attribute("schemaVersion").Value).CompareTo(version) > 0))
1433 ············{
1434 ················hasChanges = true;
1435 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1436 ············}
1437
1438 ············if (!hasChanges)
1439 ················return new string[] { };
1440
1441 ············sw.Write( "UPDATE NDOSchemaVersion SET Version = '" );
1442 ············sw.Write( transitionElements.Attribute( "schemaVersion" ).Value );
1443 ············sw.Write( "' WHERE SchemaName " );
1444 ············if (schemaName == null)
1445 ················sw.WriteLine( "IS NULL;" );
1446 ············else
1447 ················sw.WriteLine( "LIKE '" + schemaName + "'" );············
1448
1449 ············sw.Flush();
1450 ············ms.Position = 0L;
1451
1452 ············StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);
1453 ············string s = sr.ReadToEnd();
1454 ············sr.Close();
1455
1456 ············return InternalPerformSchemaTransitions( ndoConn, s );
1457 ········}
1458
1459 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1460 ········{
1461 ············string[] arr = sql.Split( ';' );
1462 ············string last = arr[arr.Length - 1];
1463 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1464 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1465 ············int i = 0;
1466 ············string ok = "OK";
1467 ············using (var handler = GetSqlPassThroughHandler())
1468 ············{
1469 ················foreach (string statement in arr)
1470 ················{
1471 ····················if (statement != null && statement.Trim() != string.Empty)
1472 ····················{
1473 ························try
1474 ························{
1475 ····························handler.Execute( statement );
1476 ····························result[i] = ok;
1477 ························}
1478 ························catch (Exception ex)
1479 ························{
1480 ····························result[i] = ex.Message;
1481 ························}
1482 ····················}
1483 ····················i++;
1484 ················}
1485
1486 ················handler.CommitTransaction();
1487 ············}
1488 ············return result;
1489 ········}
1490 ········
1491 ········/// <summary>
1492 ········/// Transfers Data from the object to the DataRow
1493 ········/// </summary>
1494 ········/// <param name="pc"></param>
1495 ········/// <param name="row"></param>
1496 ········/// <param name="fieldNames"></param>
1497 ········/// <param name="startIndex"></param>
1498 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1499 ········{
1500 ············Class cl = GetClass( pc );
1501 ············try
1502 ············{
1503 ················pc.NDOWrite(row, fieldNames, startIndex);
1504 ················if (cl.HasEncryptedFields)
1505 ················{
1506 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1507 ····················{
1508 ························string name = field.Column.Name;
1509 ························string s = (string) row[name];
1510 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1511 ························row[name] = es;
1512 ····················}
1513 ················}
1514 ············}
1515 ············catch (Exception ex)
1516 ············{
1517 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1518 ····················+ ex.Message);
1519 ············}
1520
1521 ············if (cl.TypeNameColumn != null)
1522 ············{
1523 ················Type t = pc.GetType();
1524 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1525 ············}
1526
1527 ············var etypes = cl.EmbeddedTypes;
1528 ············foreach(string s in etypes)
1529 ············{
1530 ················try
1531 ················{
1532 ····················NDO.Mapping.Field f = cl.FindField(s);
1533 ····················if (f == null)
1534 ························continue;
1535 ····················string[] arr = s.Split('.');
1536 ····················// Suche Feld mit Namen arr[0] als object
1537 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1538 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1539 ····················Object parentOb = parentFi.GetValue(pc);
1540 ····················if (parentOb == null)
1541 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1542 ····················// Suche darin das Feld mit Namen Arr[1]
1543 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1544 ····················object o = childFi.GetValue(parentOb);
1545 ····················if (o == null
1546 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1547 ························|| o is Guid && (Guid) o == Guid.Empty)
1548 ························o = DBNull.Value;
1549 ····················row[f.Column.Name] = o;
1550 ················}
1551 ················catch (Exception ex)
1552 ················{
1553 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1554
1555 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1556 ················}
1557 ············}
1558 ········}
1559
1560 ········/// <summary>
1561 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1562 ········/// </summary>
1563 ········/// <param name="o">The parent object.</param>
1564 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1565 ········public virtual void LoadField(object o, int fieldOrdinal)
1566 ········{
1567 ············IPersistenceCapable pc = CheckPc(o);
1568 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1569 ············{
1570 ················LoadState ls = pc.NDOLoadState;
1571 ················if (ls.FieldLoadState != null)
1572 ················{
1573 ····················if (ls.FieldLoadState[fieldOrdinal])
1574 ························return;
1575 ················}
1576 ················else
1577 ················{
1578 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1579 ················}
1580 ················LoadData(o);
1581 ········}
1582 ········}
1583
1584 #pragma warning disable 419
1585 ········/// <summary>
1586 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1587 ········/// </summary>
1588 ········/// <param name="o">The hollow object.</param>
1589 ········/// <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>
1590 #pragma warning restore 419
1591 ········public virtual void LoadData( object o )
1592 ········{
1593 ············IPersistenceCapable pc = CheckPc(o);
1594 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1595 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1596 ················return;
1597 ············Class cl = GetClass(pc);
1598 ············IQuery q;
1599 ············q = CreateOidQuery(pc, cl);
1600 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1601
1602 ············var objects = q.Execute();
1603 ············var count = objects.Count;
1604
1605 ············if (count > 1)
1606 ············{
1607 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1608 ············}
1609 ············else if (count == 0)
1610 ············{
1611 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1612 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1613 ············}
1614 ········}
1615
1616 ········/// <summary>
1617 ········/// Creates a new IQuery object for the given type
1618 ········/// </summary>
1619 ········/// <param name="t"></param>
1620 ········/// <param name="oql"></param>
1621 ········/// <param name="hollow"></param>
1622 ········/// <param name="queryLanguage"></param>
1623 ········/// <returns></returns>
1624 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1625 ········{
1626 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1627 ············Type qt = template.MakeGenericType( t );
1628 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1629 ········}
1630
1631 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1632 ········{
1633 ············ArrayList parameters = new ArrayList();
1634 ············string oql = "oid = {0}";
1635 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1636 ············q.Parameters.Add( pc.NDOObjectId );
1637 ············q.AllowSubclasses = false;
1638 ············return q;
1639 ········}
1640
1641 ········/// <summary>
1642 ········/// Mark the object dirty. The current state is
1643 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1644 ········/// object is locked in the cache.
1645 ········/// </summary>
1646 ········/// <param name="pc"></param>
1647 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1648 ········{
1649 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1650 ················return;
1651 ············SaveObjectState(pc);
1652 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1653 ········}
1654
1655 ········/// <summary>
1656 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1657 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1658 ········/// This is done, to allow easy rollback later. Also, the
1659 ········/// object is locked in the cache.
1660 ········/// </summary>
1661 ········/// <param name="pc"></param>
1662 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1663 ········{
1664 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1665
1666 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1667 ············{
1668 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1669 ············}
1670
1671 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1672 ················LoadData(pc);
1673
1674 ············// state is either (Created), Persistent, PersistentDirty
1675 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1676 ············{
1677 ················MarkDirty(pc);
1678 ············}
1679 ········}
1680
1681
1682
1683 ········/// <summary>
1684 ········/// Save current object state in DS and lock the object in the cache.
1685 ········/// The saved state can be used later to retrieve the original object value if the
1686 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1687 ········/// </summary>
1688 ········/// <param name="pc">The object that should be saved</param>
1689 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1690 ········/// <remarks>
1691 ········/// In a data row there are the following things:
1692 ········/// Item································Responsible for writing
1693 ········/// State (own, inherited, embedded)····WriteObject
1694 ········/// TimeStamp····························NDOPersistenceHandler
1695 ········/// Oid····································WriteId
1696 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1697 ········/// </remarks>
1698 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1699 ········{
1700 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1701 ············
1702 ············DataTable table = GetTable(pc);
1703 ············DataRow row = table.NewRow();
1704 ············Class cl = GetClass(pc);
1705 ············WriteObject(pc, row, cl.ColumnNames, 0);
1706 ············WriteIdToRow(pc, row);
1707 ············if (!isDeleting)
1708 ················WriteLostForeignKeysToRow(cl, pc, row);
1709 ············table.Rows.Add(row);
1710 ············row.AcceptChanges();
1711 ············
1712 ············var relations = CollectRelationStates(pc);
1713 ············cache.Lock(pc, row, relations);
1714 ········}
1715
1716 ········private void SaveFakeRow(IPersistenceCapable pc)
1717 ········{
1718 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1719 ············
1720 ············DataTable table = GetTable(pc);
1721 ············DataRow row = table.NewRow();
1722 ············Class pcClass = GetClass(pc);
1723 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1724 ············Class cl = GetClass(pc);
1725 ············//WriteObject(pc, row, cl.FieldNames, 0);
1726 ············WriteIdToRow(pc, row);
1727 ············table.Rows.Add(row);
1728 ············row.AcceptChanges();
1729 ············
1730 ············cache.Lock(pc, row, null);
1731 ········}
1732
1733 ········/// <summary>
1734 ········/// This defines one column of the row, in which we use the
1735 ········/// ColumnError property to determine, if the row is a fake row.
1736 ········/// </summary>
1737 ········/// <param name="pcClass"></param>
1738 ········/// <returns></returns>
1739 ········private string GetFakeRowOidColumnName(Class pcClass)
1740 ········{
1741 ············// In case of several OidColumns the first column defined in the mapping
1742 ············// will be the one, holding the fake row info.
1743 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1744 ········}
1745
1746 ········private bool IsFakeRow(Class cl, DataRow row)
1747 ········{
1748 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1749 ········}
1750
1751 ········/// <summary>
1752 ········/// Make a list of objects persistent.
1753 ········/// </summary>
1754 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1755 ········public void MakePersistent(System.Collections.IList list)
1756 ········{
1757 ············foreach (IPersistenceCapable pc in list)
1758 ············{
1759 ················MakePersistent(pc);
1760 ············}
1761 ········}
1762
1763 ········/// <summary>
1764 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1765 ········/// not duplicated.
1766 ········/// </summary>
1767 ········/// <param name="pc">the parent object of all relations</param>
1768 ········/// <returns></returns>
1769 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1770 ········{
1771 ············// Save state of relations
1772 ············Class c = GetClass(pc);
1773 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1774 ············foreach(Relation r in c.Relations)
1775 ············{
1776 ················if (r.Multiplicity == RelationMultiplicity.Element)
1777 ················{
1778 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1779 ················}
1780 ················else
1781 ················{
1782 ····················IList l = mappings.GetRelationContainer(pc, r);
1783 ····················if(l != null)
1784 ····················{
1785 ························l = (IList) ListCloner.CloneList(l);
1786 ····················}
1787 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1788 ················}
1789 ············}
1790
1791 ············return relations;
1792 ········}
1793
1794
1795 ········/// <summary>
1796 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1797 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1798 ········/// the list had at the beginning of the transaction.
1799 ········/// </summary>
1800 ········/// <param name="pc"></param>
1801 ········/// <param name="relations"></param>
1802 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1803 ········{
1804 ············Class c = GetClass(pc);
1805
1806 ············foreach(var entry in relations)
1807 ············{
1808 ················var r = entry.Key;
1809 ················if (r.Multiplicity == RelationMultiplicity.Element)
1810 ················{
1811 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1812 ················}
1813 ················else
1814 ················{
1815 ····················if (pc.NDOGetLoadState(r.Ordinal))
1816 ····················{
1817 ························// Help GC by clearing lists
1818 ························IList l = mappings.GetRelationContainer(pc, r);
1819 ························if(l != null)
1820 ························{
1821 ····························l.Clear();
1822 ························}
1823 ························// Restore relation
1824 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1825 ····················}
1826 ················}
1827 ············}
1828 ········}
1829
1830
1831 ········/// <summary>
1832 ········/// Generates a query for related objects without mapping table.
1833 ········/// Note: this function can't be called in polymorphic scenarios,
1834 ········/// since they need a mapping table.
1835 ········/// </summary>
1836 ········/// <returns></returns>
1837 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1838 ········{
1839 ············// At this point of execution we know,
1840 ············// that the target type is not polymorphic and is not 1:1.
1841
1842 ············// We can't fetch these objects with an NDOql query
1843 ············// since this would require a relation in the opposite direction
1844
1845 ············IList relatedObjects;
1846 ············if (l != null)
1847 ················relatedObjects = l;
1848 ············else
1849 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1850
1851 ············Type t = r.ReferencedType;
1852 ············Class cl = GetClass( t );
1853 ············var provider = cl.Provider;
1854
1855 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1856 ············var relClass = GetClass( r.ReferencedType );
1857 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1858 ············sb.Append( " WHERE " );
1859 ············int i = 0;
1860 ············List<object> parameters = new List<object>();
1861 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1862 ·············· {
1863 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1864 ·················· sb.Append( " = {" );
1865 ·················· sb.Append(i);
1866 ·················· sb.Append( '}' );
1867 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1868 ·················· if (!isLastElement)
1869 ······················ sb.Append( " AND " );
1870 ·················· i++;
1871 ·············· } );
1872
1873 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1874 ············{
1875 ················sb.Append( " AND " );
1876 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1877 ················sb.Append( " = " );
1878 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1879 ············}
1880
1881 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1882
1883 ············foreach (var p in parameters)
1884 ············{
1885 ················q.Parameters.Add( p );
1886 ············}
1887
1888 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1889
1890 ············IList l2 = q.Execute();
1891
1892 ············foreach (object o in l2)
1893 ················relatedObjects.Add( o );
1894
1895 ············return relatedObjects;
1896 ········}
1897
1898
1899 ········/// <summary>
1900 ········/// Resolves an relation. The loaded objects will be hollow.
1901 ········/// </summary>
1902 ········/// <param name="o">The parent object.</param>
1903 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1904 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1905 ········/// <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>
1906 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1907 ········{
1908 ············IPersistenceCapable pc = CheckPc(o);
1909 ············LoadRelationInternal(pc, fieldName, hollow);
1910 ········}
1911
1912 ········
1913
1914 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1915 ········{
1916 ············IList result = null;
1917
1918 ············if (pc.NDOObjectState == NDOObjectState.Created)
1919 ················return null;
1920
1921 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1922 ················LoadData(pc);
1923
1924 ············if(r.MappingTable == null)
1925 ············{
1926 ················// 1:1 are loaded with LoadData
1927 ················if (r.Multiplicity == RelationMultiplicity.List)
1928 ················{
1929 ····················// Help GC by clearing lists
1930 ····················IList l = mappings.GetRelationContainer(pc, r);
1931 ····················if(l != null)
1932 ························l.Clear();
1933 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1934 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1935 ····················result = relatedObjects;
1936 ················}
1937 ············}
1938 ············else
1939 ············{
1940 ················DataTable dt = null;
1941
1942 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1943 ················{
1944 ····················CheckTransaction( handler, r.MappingTable.Connection );
1945 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1946 ················}
1947
1948 ················IList relatedObjects;
1949 ················if(r.Multiplicity == RelationMultiplicity.Element)
1950 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1951 ················else
1952 ················{
1953 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1954 ····················if(relatedObjects != null)
1955 ························relatedObjects.Clear();··// Objects will be reread
1956 ····················else
1957 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1958 ················}
1959 ····················
1960 ················foreach(DataRow objRow in dt.Rows)
1961 ················{
1962 ····················Type relType;
1963
1964 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1965 ····················{
1966 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1967 ························if (typeCodeObj is System.DBNull)
1968 ····························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() ) );
1969 ························relType = typeManager[(int)typeCodeObj];
1970 ························if (relType == null)
1971 ····························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));
1972 ····················}························
1973 ····················else
1974 ····················{
1975 ························relType = r.ReferencedType;
1976 ····················}
1977
1978 ····················//TODO: Generic Types: Exctract the type description from the type name column
1979 ····················if (relType.IsGenericTypeDefinition)
1980 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1981 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1982 ····················IPersistenceCapable relObj = FindObject(id);
1983 ····················relatedObjects.Add(relObj);
1984 ················}····
1985 ················if (r.Multiplicity == RelationMultiplicity.Element)
1986 ················{
1987 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
1988 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
1989 ················}
1990 ················else
1991 ················{
1992 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1993 ····················result = relatedObjects;
1994 ················}
1995 ············}
1996 ············// Mark relation as loaded
1997 ············pc.NDOSetLoadState(r.Ordinal, true);
1998 ············return result;
1999 ········}
2000
2001 ········/// <summary>
2002 ········/// Loads elements of a relation
2003 ········/// </summary>
2004 ········/// <param name="pc">The object which needs to load the relation</param>
2005 ········/// <param name="relationName">The name of the relation</param>
2006 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2007 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2008 ········{
2009 ············if (pc.NDOObjectState == NDOObjectState.Created)
2010 ················return null;
2011 ············Class cl = GetClass(pc);
2012
2013 ············Relation r = cl.FindRelation(relationName);
2014
2015 ············if ( r == null )
2016 ················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 ) );
2017
2018 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2019 ················return null;
2020
2021 ············return LoadRelation(pc, r, hollow);
2022 ········}
2023
2024 ········/// <summary>
2025 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2026 ········/// a list of objects that has been read from the DB.
2027 ········/// </summary>
2028 ········/// <param name="pc">The parent object of the relations</param>
2029 ········/// <param name="row">A data row containing the state of the object</param>
2030 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2031 ········{
2032 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2033 ············Class cl = GetClass(pc);
2034 ············foreach(Relation r in cl.Relations)
2035 ············{
2036 ················if(r.MappingTable == null)
2037 ················{
2038 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2039 ····················{
2040 ························bool isNull = false;
2041 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2042 ························{
2043 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2044 ························}
2045 ························if (isNull)
2046 ························{
2047 ····························mappings.SetRelationField(pc, r.FieldName, null);
2048 ························}
2049 ························else
2050 ························{
2051 ····························Type relType;
2052 ····························if (r.HasSubclasses)
2053 ····························{
2054 ································object o = row[r.ForeignKeyTypeColumnName];
2055 ································if (o == DBNull.Value)
2056 ····································throw new NDOException(75, String.Format(
2057 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2058 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2059 ································relType = typeManager[(int)o];
2060 ····························}
2061 ····························else
2062 ····························{
2063 ································relType = r.ReferencedType;
2064 ····························}
2065 ····························if (relType == null)
2066 ····························{
2067 ································throw new NDOException(75, String.Format(
2068 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2069 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2070 ····························}
2071 ····
2072 ····························int count = r.ForeignKeyColumns.Count();
2073 ····························object[] keydata = new object[count];
2074 ····························int i = 0;
2075 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2076 ····························{
2077 ································keydata[i++] = row[fkColumn.Name];
2078 ····························}
2079
2080 ····························Type oidType = relType;
2081 ····························if (oidType.IsGenericTypeDefinition)
2082 ································oidType = mappings.GetRelationFieldType(r);
2083
2084 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2085 ····························if(childOid.IsValid())
2086 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2087 ····························else
2088 ································mappings.SetRelationField(pc, r.FieldName, null);
2089
2090 ························}
2091 ························pc.NDOSetLoadState(r.Ordinal, true);
2092 ····················}
2093 ················}
2094 ············}
2095 ········}
2096
2097 ········
2098 ········/// <summary>
2099 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2100 ········/// </summary>
2101 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2102 ········/// <param name="t">The type of the object, the id will belong to.</param>
2103 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2104 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2105 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2106 ········{
2107 ············return new ObjectId(oid.Id, t);
2108 ········}
2109
2110 ········/*
2111 ········/// <summary>
2112 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2113 ········/// </summary>
2114 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2115 ········/// <param name="t">The type of the object.</param>
2116 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2117 ········/// <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>
2118 ········public ObjectId NewObjectId(object keyData, Type t)
2119 ········{
2120 ············if (keyData == null || keyData == DBNull.Value)
2121 ················return ObjectId.InvalidId;
2122
2123 ············Class cl =··GetClass(t);············
2124 ············
2125 ············if (cl.Oid.FieldType == typeof(int))
2126 ················return new ObjectId(new Int32Key(t, (int)keyData));
2127 ············else if (cl.Oid.FieldType == typeof(string))
2128 ················return new ObjectId(new StringKey(t, (String) keyData));
2129 ············else if (cl.Oid.FieldType == typeof(Guid))
2130 ················if (keyData is string)
2131 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2132 ················else
2133 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2134 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2135 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2136 ············else
2137 ················return ObjectId.InvalidId;
2138 ········}
2139 ········*/
2140
2141
2142 ········/*
2143 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2144 ····················{
2145 ························// The column, which hold the oid gets overwritten, if
2146 ························// Oid.FieldName contains a value.
2147 */
2148 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2149 ········{
2150 ············Class cl = GetClass(pc);
2151
2152 ············if (cl.Oid.IsDependent)
2153 ················return;
2154
2155 ············Key key = pc.NDOObjectId.Id;
2156
2157 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2158 ············{
2159 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2160 ················if (oidColumn.FieldName != null)
2161 ················{
2162 ····················row[oidColumn.Name] = key[i];
2163 ················}
2164 ············}
2165 ········}
2166
2167 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2168 ········{
2169 ············NDO.Mapping.Class cl = GetClass(pc);
2170 ············ObjectId oid = pc.NDOObjectId;
2171
2172 ············if (cl.TimeStampColumn != null)
2173 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2174
2175 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2176 ················return;
2177
2178 ············Key key = oid.Id;
2179
2180 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2181 ············{
2182 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2183 ················row[oidColumn.Name] = key[i];
2184 ············}
2185 ········}
2186
2187 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2188 ········{
2189 ············ObjectId oid = pc.NDOObjectId;
2190 ············NDO.Mapping.Class cl = GetClass(pc);
2191
2192 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2193 ················return;
2194
2195 ············Key key = oid.Id;
2196
2197 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2198 ············{
2199 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2200 ················object o = row[oidColumn.Name];
2201 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2202 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2203 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2204 ····················key[i] = new Guid((string)o);
2205 ················else
2206 ····················key[i] = o;
2207 ············}
2208
2209 ········}
2210
2211 ········private void ReadId (Cache.Entry e)
2212 ········{
2213 ············ReadIdFromRow(e.pc, e.row);
2214 ········}
2215
2216 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2217 ········{
2218 ············WriteObject(pc, row, fieldNames, 0);
2219 ········}
2220
2221 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2222 ········{
2223 ············if (cl.TimeStampColumn == null)
2224 ················return;
2225 ············object col = row[cl.TimeStampColumn];
2226 ············if (col is String)
2227 ················pc.NDOTimeStamp = new Guid(col.ToString());
2228 ············else
2229 ················pc.NDOTimeStamp = (Guid) col;
2230 ········}
2231
2232
2233
2234 ········private void ReadTimeStamp(Cache.Entry e)
2235 ········{
2236 ············IPersistenceCapable pc = e.pc;
2237 ············NDO.Mapping.Class cl = GetClass(pc);
2238 ············Debug.Assert(!IsFakeRow(cl, e.row));
2239 ············if (cl.TimeStampColumn == null)
2240 ················return;
2241 ············if (e.row[cl.TimeStampColumn] is String)
2242 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2243 ············else
2244 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2245 ········}
2246
2247 ········/// <summary>
2248 ········/// Determines, if any objects are new, changed or deleted.
2249 ········/// </summary>
2250 ········public virtual bool HasChanges
2251 ········{
2252 ············get
2253 ············{
2254 ················return cache.LockedObjects.Count > 0;
2255 ············}
2256 ········}
2257
2258 ········/// <summary>
2259 ········/// Do the update for all rows in the ds.
2260 ········/// </summary>
2261 ········/// <param name="types">Types with changes.</param>
2262 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2263 ········/// <remarks>
2264 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2265 ········/// </remarks>
2266 ········private void UpdateTypes(IList types, bool delete)
2267 ········{
2268 ············foreach(Type t in types)
2269 ············{
2270 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2271 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2272 ················{
2273 ····················CheckTransaction( handler, t );
2274 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2275 ····················handler.ConcurrencyError += ceh;
2276 ····················try
2277 ····················{
2278 ························DataTable dt = GetTable(t);
2279 ························if (delete)
2280 ····························handler.UpdateDeletedObjects( dt );
2281 ························else
2282 ····························handler.Update( dt );
2283 ····················}
2284 ····················finally
2285 ····················{
2286 ························handler.ConcurrencyError -= ceh;
2287 ····················}
2288 ················}
2289 ············}
2290 ········}
2291
2292 ········internal void UpdateCreatedMappingTableEntries()
2293 ········{
2294 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2295 ············{
2296 ················if (!e.DeleteEntry)
2297 ····················WriteMappingTableEntry(e);
2298 ············}
2299 ············// Now update all mapping tables
2300 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2301 ············{
2302 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2303 ················handler.Update(ds);
2304 ············}
2305 ········}
2306
2307 ········internal void UpdateDeletedMappingTableEntries()
2308 ········{
2309 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2310 ············{
2311 ················if (e.DeleteEntry)
2312 ····················WriteMappingTableEntry(e);
2313 ············}
2314 ············// Now update all mapping tables
2315 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2316 ············{
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 (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
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 ( Logger != null && Logger. IsEnabled( LogLevel. Debug ) )
3502 Logger. LogDebug( msg ) ;
3503 ········}
3504
3505
3506 ········#endregion
3507
3508
3509 #region Class extent
3510 ········/// <summary>
3511 ········/// Gets all objects of a given class.
3512 ········/// </summary>
3513 ········/// <param name="t">the type of the class</param>
3514 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3515 ········public virtual IList GetClassExtent(Type t)
3516 ········{
3517 ············return GetClassExtent(t, true);
3518 ········}
3519
3520 ········/// <summary>
3521 ········/// Gets all objects of a given class.
3522 ········/// </summary>
3523 ········/// <param name="t">The type of the class.</param>
3524 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3525 ········/// <returns>A list of all persistent objects of the given class.</returns>
3526 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3527 ········public virtual IList GetClassExtent(Type t, bool hollow)
3528 ········{
3529 ············IQuery q = NewQuery( t, null, hollow );
3530 ············return q.Execute();
3531 ········}
3532
3533 #endregion
3534
3535 #region Query engine
3536
3537
3538 ········/// <summary>
3539 ········/// Returns a virtual table for Linq queries.
3540 ········/// </summary>
3541 ········/// <typeparam name="T"></typeparam>
3542 ········/// <returns></returns>
3543 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3544 ········{
3545 ············return new VirtualTable<T>( this );
3546 ········}
3547
3548 ········
3549 ········/// <summary>
3550 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3551 ········/// 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
3552 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3553 ········/// not lose the foreign key it will be stored as part of the object.
3554 ········/// </summary>
3555 ········/// <param name="cl"></param>
3556 ········/// <param name="pc"></param>
3557 ········/// <param name="row"></param>
3558 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3559 ········{
3560 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3561 ············{
3562 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3563 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3564 ················foreach(string colName in cl.FKColumnNames)
3565 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3566 ················pc.NDOLoadState.LostRowInfo = kvl;················
3567 ············}
3568 ········}
3569
3570 ········/// <summary>
3571 ········/// Writes information into the data row, which cannot be stored in the object.
3572 ········/// </summary>
3573 ········/// <param name="cl"></param>
3574 ········/// <param name="pc"></param>
3575 ········/// <param name="row"></param>
3576 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3577 ········{
3578 ············if (cl.FKColumnNames != null)
3579 ············{
3580 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3581 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3582 ················if (kvl == null)
3583 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3584 ················foreach (KeyValuePair pair in kvl)
3585 ····················row[(string) pair.Key] = pair.Value;
3586 ············}
3587 ········}
3588
3589 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3590 ········{
3591 ············ReadObject(pc, row, cl.ColumnNames, 0);
3592 ············ReadTimeStamp(cl, pc, row);
3593 ············ReadLostForeignKeysFromRow(cl, pc, row);
3594 ············LoadRelated1To1Objects(pc, row);
3595 ············pc.NDOObjectState = NDOObjectState.Persistent;
3596 ········}
3597
3598
3599 ········/// <summary>
3600 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3601 ········/// </summary>
3602 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3603 ········{
3604 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3605 ············if (rows.Count == 0)
3606 ················return queryResult;
3607
3608 ············IList callbackObjects = new ArrayList();
3609 ············Class cl = GetClass(t);
3610 ············if (t.IsGenericTypeDefinition)
3611 ············{
3612 ················if (cl.TypeNameColumn == null)
3613 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3614 ················IEnumerator en = rows.GetEnumerator();
3615 ················en.MoveNext();··// Move to the first element
3616 ················DataRow testrow = (DataRow)en.Current;
3617 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3618 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3619 ············}
3620
3621 ············foreach(DataRow row in rows)
3622 ············{
3623 ················Type concreteType = t;
3624 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3625 ················{
3626 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3627 ····················{
3628 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3629 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3630 ····················}
3631 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3632 ····················concreteType = Type.GetType(typeStr);
3633 ····················if (concreteType == null)
3634 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3635 ················}
3636 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3637 ················IPersistenceCapable pc = cache.GetObject(id);················
3638 ················if(pc == null)
3639 ················{
3640 ····················pc = CreateObject( concreteType );
3641 ····················pc.NDOObjectId = id;
3642 ····················pc.NDOStateManager = sm;
3643 ····················// If the object shouldn't be hollow, this will be overwritten later.
3644 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3645 ················}
3646 ················// If we have found a non hollow object, the time stamp remains the old one.
3647 ················// In every other case we use the new time stamp.
3648 ················// Note, that there could be a hollow object in the cache.
3649 ················// We need the time stamp in hollow objects in order to correctly
3650 ················// delete objects using fake rows.
3651 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3652 ················{
3653 ····················ReadTimeStamp(cl, pc, row);
3654 ················}
3655 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3656 ················{
3657 ····················Row2Object(cl, pc, row);
3658 ····················if ((pc as IPersistenceNotifiable) != null)
3659 ························callbackObjects.Add(pc);
3660 ················}
3661
3662 ················cache.UpdateCache(pc);
3663 ················queryResult.Add(pc);
3664 ············}
3665 ············// Make shure this is the last statement before returning
3666 ············// to the caller, so the user can recursively use persistent
3667 ············// objects
3668 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3669 ················ipn.OnLoaded();
3670
3671 ············return queryResult;
3672 ········}
3673
3674 #endregion
3675
3676 #region Cache Management
3677 ········/// <summary>
3678 ········/// Remove all unused entries from the cache.
3679 ········/// </summary>
3680 ········public void CleanupCache()
3681 ········{
3682 ············GC.Collect(GC.MaxGeneration);
3683 ············GC.WaitForPendingFinalizers();
3684 ············cache.Cleanup();
3685 ········}
3686
3687 ········/// <summary>
3688 ········/// Remove all unlocked objects from the cache. Use with care!
3689 ········/// </summary>
3690 ········public void UnloadCache()
3691 ········{
3692 ············cache.Unload();
3693 ········}
3694 #endregion
3695
3696 ········/// <summary>
3697 ········/// Default encryption key for NDO
3698 ········/// </summary>
3699 ········/// <remarks>
3700 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3701 ········/// </remarks>
3702 ········public virtual byte[] EncryptionKey
3703 ········{
3704 ············get
3705 ············{
3706 ················if (this.encryptionKey == null)
3707 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3708 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3709 ················return this.encryptionKey;
3710 ············}
3711 ············set { this.encryptionKey = value; }
3712 ········}
3713
3714 ········/// <summary>
3715 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3716 ········/// </summary>
3717 ········public virtual bool HollowMode
3718 ········{
3719 ············get { return hollowMode; }
3720 ············set { hollowMode = value; }
3721 ········}
3722
3723 ········internal TypeManager TypeManager
3724 ········{
3725 ············get { return typeManager; }
3726 ········}
3727
3728 ········/// <summary>
3729 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3730 ········/// </summary>
3731 ········/// <remarks>
3732 ········/// Set this value before you start any transactions.
3733 ········/// </remarks>
3734 ········public TransactionMode TransactionMode
3735 ········{
3736 ············get { return TransactionScope.TransactionMode; }
3737 ············set { TransactionScope.TransactionMode = value; }
3738 ········}
3739
3740 ········/// <summary>
3741 ········/// Sets or gets the Isolation Level.
3742 ········/// </summary>
3743 ········/// <remarks>
3744 ········/// Set this value before you start any transactions.
3745 ········/// </remarks>
3746 ········public IsolationLevel IsolationLevel
3747 ········{
3748 ············get { return TransactionScope.IsolationLevel; }
3749 ············set { TransactionScope.IsolationLevel = value; }
3750 ········}
3751
3752 ········internal class MappingTableEntry
3753 ········{
3754 ············private IPersistenceCapable parentObject;
3755 ············private IPersistenceCapable relatedObject;
3756 ············private Relation relation;
3757 ············private bool deleteEntry;
3758 ············
3759 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3760 ············{
3761 ············}
3762 ············
3763 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3764 ············{
3765 ················parentObject = pc;
3766 ················relatedObject = relObj;
3767 ················relation = r;
3768 ················this.deleteEntry = deleteEntry;
3769 ············}
3770
3771 ············public bool DeleteEntry
3772 ············{
3773 ················get
3774 ················{
3775 ····················return deleteEntry;
3776 ················}
3777 ············}
3778
3779 ············public IPersistenceCapable ParentObject
3780 ············{
3781 ················get
3782 ················{
3783 ····················return parentObject;
3784 ················}
3785 ············}
3786
3787 ············public IPersistenceCapable RelatedObject
3788 ············{
3789 ················get
3790 ················{
3791 ····················return relatedObject;
3792 ················}
3793 ············}
3794
3795 ············public Relation Relation
3796 ············{
3797 ················get
3798 ················{
3799 ····················return relation;
3800 ················}
3801 ············}
3802 ········}
3803
3804 ········/// <summary>
3805 ········/// Get a DataRow representing the given object.
3806 ········/// </summary>
3807 ········/// <param name="o"></param>
3808 ········/// <returns></returns>
3809 ········public DataRow GetClonedDataRow( object o )
3810 ········{
3811 ············IPersistenceCapable pc = CheckPc( o );
3812
3813 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3814 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3815
3816 ············DataRow row = cache.GetDataRow( pc );
3817 ············DataTable newTable = row.Table.Clone();
3818 ············newTable.ImportRow( row );
3819 ············row = newTable.Rows[0];
3820
3821 ············Class cls = mappings.FindClass(o.GetType());
3822 ············WriteObject( pc, row, cls.ColumnNames );
3823 ············WriteForeignKeysToRow( pc, row );
3824
3825 ············return row;
3826 ········}
3827
3828 ········/// <summary>
3829 ········/// Gets an object, which shows all changes applied to the given object.
3830 ········/// This function can be used to build an audit system.
3831 ········/// </summary>
3832 ········/// <param name="o"></param>
3833 ········/// <returns>An ExpandoObject</returns>
3834 ········public ChangeLog GetChangeSet( object o )
3835 ········{
3836 ············var changeLog = new ChangeLog(this);
3837 ············changeLog.Initialize( o );
3838 ············return changeLog;
3839 ········}
3840
3841 ········/// <summary>
3842 ········/// Outputs a revision number representing the assembly version.
3843 ········/// </summary>
3844 ········/// <remarks>This can be used for debugging purposes</remarks>
3845 ········public int Revision
3846 ········{
3847 ············get
3848 ············{
3849 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3850 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3851 ················return int.Parse( vstring );
3852 ············}
3853 ········}
3854 ····}
3855 }
3856