Datei: NDODLL/PersistenceManager.cs

Last Commit (273289e)
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 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1365 ········{
1366 ············var connection = provider.NewConnection( ndoConn.Name );
1367 ············var resultList = new List<Guid>();
1368
1369 ············using (var handler = GetSqlPassThroughHandler())
1370 ············{
1371 ················string[] TableNames = provider.GetTableNames( connection );
1372 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1373 ················{
1374 ····················string sql = "SELECT Id from NDOSchemaIds WHERE SchemaName ";
1375 ····················if (String.IsNullOrEmpty(schemaName))
1376 ························sql += "IS NULL;";
1377 ····················else
1378 ························sql += $"LIKE '{schemaName}'";
1379
1380 ····················using(IDataReader dr = handler.Execute(sql, true))
1381 ····················{
1382 ························while (dr.Read())
1383 ····························resultList.Add( dr.GetGuid( 0 ) );
1384 ····················}
1385 ················}
1386 ················else
1387 ················{
1388 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1389 ····················var gt = typeof(Guid);
1390 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1391 ····················var st = typeof(String);
1392 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1393 ····················var dt = typeof(DateTime);
1394 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1395 ····················string transition = $@"<NdoSchemaTransition>
1396 <CreateTable name=""NDOSchemaVersion"">
1397 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1398 ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" />
1399 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1400 ····</CreateTable>
1401 </NdoSchemaTransition>";
1402 ····················XElement transitionElement = XElement.Parse(transition);
1403
1404 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1405 ····················handler.Execute(sql);
1406 ················}
1407 ············}
1408
1409 ············return resultList.ToArray();
1410 ········}
1411
1412 ········/// <summary>
1413 ········/// Executes a xml script to generate the database tables.
1414 ········/// The function will generate and execute sql statements to perform
1415 ········/// the changes described by the xml.
1416 ········/// </summary>
1417 ········/// <param name="scriptFile">The xml script file.</param>
1418 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1419 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1420 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1421 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1422 ········{
1423 ············string schemaName = null;
1424 ············// Gespeicherte Version ermitteln.
1425 ············XElement transitionElements = XElement.Load( scriptFile );
1426 ············if (transitionElements.Attribute( "schemaName" ) != null)
1427 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1428
1429 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1430 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1431 ············var newIds = new List<Guid>();
1432 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1433 ············MemoryStream ms = new MemoryStream();
1434 ············StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
1435 ············bool hasChanges = false;
1436
1437 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition"))
1438 ············{
1439 ················var id = transitionElement.Attribute("id")?.Value;
1440 ················if (id == null)
1441 ····················continue;
1442 ················var gid = new Guid(id);
1443 ················if (installedIds.Contains( gid ))
1444 ····················continue;
1445 ················hasChanges = true;
1446 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1447 ················newIds.Add( gid );
1448 ············}
1449
1450 ············if (!hasChanges)
1451 ················return new string[] { };
1452
1453 ············// dtLiteral contains the leading and trailing quotes
1454 ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now );
1455
1456 ············foreach (var id in newIds)
1457 ············{
1458 ················sw.WriteLine( $"INSERT INTO NDOSchemaIds ('SchemaName','Id','InsertTime') VALUES ('{schemaName}','{id}',{dtLiteral});" );
1459 ············}
1460
1461 ············sw.Flush();
1462 ············ms.Position = 0L;
1463
1464 ············StreamReader sr = new StreamReader(ms, Encoding.UTF8);
1465 ············string s = sr.ReadToEnd();
1466 ············sr.Close();
1467
1468 ············return InternalPerformSchemaTransitions( ndoConn, s );
1469 ········}
1470
1471 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1472 ········{
1473 ············string[] arr = sql.Split( ';' );
1474 ············string last = arr[arr.Length - 1];
1475 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1476 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1477 ············int i = 0;
1478 ············string ok = "OK";
1479 ············using (var handler = GetSqlPassThroughHandler())
1480 ············{
1481 ················foreach (string statement in arr)
1482 ················{
1483 ····················if (statement != null && statement.Trim() != string.Empty)
1484 ····················{
1485 ························try
1486 ························{
1487 ····························handler.Execute( statement );
1488 ····························result[i] = ok;
1489 ························}
1490 ························catch (Exception ex)
1491 ························{
1492 ····························result[i] = ex.Message;
1493 ························}
1494 ····················}
1495 ····················i++;
1496 ················}
1497
1498 ················handler.CommitTransaction();
1499 ············}
1500 ············return result;
1501 ········}
1502 ········
1503 ········/// <summary>
1504 ········/// Transfers Data from the object to the DataRow
1505 ········/// </summary>
1506 ········/// <param name="pc"></param>
1507 ········/// <param name="row"></param>
1508 ········/// <param name="fieldNames"></param>
1509 ········/// <param name="startIndex"></param>
1510 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1511 ········{
1512 ············Class cl = GetClass( pc );
1513 ············try
1514 ············{
1515 ················pc.NDOWrite(row, fieldNames, startIndex);
1516 ················if (cl.HasEncryptedFields)
1517 ················{
1518 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1519 ····················{
1520 ························string name = field.Column.Name;
1521 ························string s = (string) row[name];
1522 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1523 ························row[name] = es;
1524 ····················}
1525 ················}
1526 ············}
1527 ············catch (Exception ex)
1528 ············{
1529 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1530 ····················+ ex.Message);
1531 ············}
1532
1533 ············if (cl.TypeNameColumn != null)
1534 ············{
1535 ················Type t = pc.GetType();
1536 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1537 ············}
1538
1539 ············var etypes = cl.EmbeddedTypes;
1540 ············foreach(string s in etypes)
1541 ············{
1542 ················try
1543 ················{
1544 ····················NDO.Mapping.Field f = cl.FindField(s);
1545 ····················if (f == null)
1546 ························continue;
1547 ····················string[] arr = s.Split('.');
1548 ····················// Suche Feld mit Namen arr[0] als object
1549 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1550 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1551 ····················Object parentOb = parentFi.GetValue(pc);
1552 ····················if (parentOb == null)
1553 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1554 ····················// Suche darin das Feld mit Namen Arr[1]
1555 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1556 ····················object o = childFi.GetValue(parentOb);
1557 ····················if (o == null
1558 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1559 ························|| o is Guid && (Guid) o == Guid.Empty)
1560 ························o = DBNull.Value;
1561 ····················row[f.Column.Name] = o;
1562 ················}
1563 ················catch (Exception ex)
1564 ················{
1565 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1566
1567 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1568 ················}
1569 ············}
1570 ········}
1571
1572 ········/// <summary>
1573 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1574 ········/// </summary>
1575 ········/// <param name="o">The parent object.</param>
1576 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1577 ········public virtual void LoadField(object o, int fieldOrdinal)
1578 ········{
1579 ············IPersistenceCapable pc = CheckPc(o);
1580 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1581 ············{
1582 ················LoadState ls = pc.NDOLoadState;
1583 ················if (ls.FieldLoadState != null)
1584 ················{
1585 ····················if (ls.FieldLoadState[fieldOrdinal])
1586 ························return;
1587 ················}
1588 ················else
1589 ················{
1590 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1591 ················}
1592 ················LoadData(o);
1593 ········}
1594 ········}
1595
1596 #pragma warning disable 419
1597 ········/// <summary>
1598 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1599 ········/// </summary>
1600 ········/// <param name="o">The hollow object.</param>
1601 ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks>
1602 #pragma warning restore 419
1603 ········public virtual void LoadData( object o )
1604 ········{
1605 ············IPersistenceCapable pc = CheckPc(o);
1606 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1607 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1608 ················return;
1609 ············Class cl = GetClass(pc);
1610 ············IQuery q;
1611 ············q = CreateOidQuery(pc, cl);
1612 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1613
1614 ············var objects = q.Execute();
1615 ············var count = objects.Count;
1616
1617 ············if (count > 1)
1618 ············{
1619 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1620 ············}
1621 ············else if (count == 0)
1622 ············{
1623 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1624 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1625 ············}
1626 ········}
1627
1628 ········/// <summary>
1629 ········/// Creates a new IQuery object for the given type
1630 ········/// </summary>
1631 ········/// <param name="t"></param>
1632 ········/// <param name="oql"></param>
1633 ········/// <param name="hollow"></param>
1634 ········/// <param name="queryLanguage"></param>
1635 ········/// <returns></returns>
1636 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1637 ········{
1638 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1639 ············Type qt = template.MakeGenericType( t );
1640 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1641 ········}
1642
1643 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1644 ········{
1645 ············ArrayList parameters = new ArrayList();
1646 ············string oql = "oid = {0}";
1647 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1648 ············q.Parameters.Add( pc.NDOObjectId );
1649 ············q.AllowSubclasses = false;
1650 ············return q;
1651 ········}
1652
1653 ········/// <summary>
1654 ········/// Mark the object dirty. The current state is
1655 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1656 ········/// object is locked in the cache.
1657 ········/// </summary>
1658 ········/// <param name="pc"></param>
1659 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1660 ········{
1661 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1662 ················return;
1663 ············SaveObjectState(pc);
1664 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1665 ········}
1666
1667 ········/// <summary>
1668 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1669 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1670 ········/// This is done, to allow easy rollback later. Also, the
1671 ········/// object is locked in the cache.
1672 ········/// </summary>
1673 ········/// <param name="pc"></param>
1674 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1675 ········{
1676 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1677
1678 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1679 ············{
1680 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1681 ············}
1682
1683 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1684 ················LoadData(pc);
1685
1686 ············// state is either (Created), Persistent, PersistentDirty
1687 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1688 ············{
1689 ················MarkDirty(pc);
1690 ············}
1691 ········}
1692
1693
1694
1695 ········/// <summary>
1696 ········/// Save current object state in DS and lock the object in the cache.
1697 ········/// The saved state can be used later to retrieve the original object value if the
1698 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1699 ········/// </summary>
1700 ········/// <param name="pc">The object that should be saved</param>
1701 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1702 ········/// <remarks>
1703 ········/// In a data row there are the following things:
1704 ········/// Item································Responsible for writing
1705 ········/// State (own, inherited, embedded)····WriteObject
1706 ········/// TimeStamp····························NDOPersistenceHandler
1707 ········/// Oid····································WriteId
1708 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1709 ········/// </remarks>
1710 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1711 ········{
1712 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1713 ············
1714 ············DataTable table = GetTable(pc);
1715 ············DataRow row = table.NewRow();
1716 ············Class cl = GetClass(pc);
1717 ············WriteObject(pc, row, cl.ColumnNames, 0);
1718 ············WriteIdToRow(pc, row);
1719 ············if (!isDeleting)
1720 ················WriteLostForeignKeysToRow(cl, pc, row);
1721 ············table.Rows.Add(row);
1722 ············row.AcceptChanges();
1723 ············
1724 ············var relations = CollectRelationStates(pc);
1725 ············cache.Lock(pc, row, relations);
1726 ········}
1727
1728 ········private void SaveFakeRow(IPersistenceCapable pc)
1729 ········{
1730 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1731 ············
1732 ············DataTable table = GetTable(pc);
1733 ············DataRow row = table.NewRow();
1734 ············Class pcClass = GetClass(pc);
1735 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1736 ············Class cl = GetClass(pc);
1737 ············//WriteObject(pc, row, cl.FieldNames, 0);
1738 ············WriteIdToRow(pc, row);
1739 ············table.Rows.Add(row);
1740 ············row.AcceptChanges();
1741 ············
1742 ············cache.Lock(pc, row, null);
1743 ········}
1744
1745 ········/// <summary>
1746 ········/// This defines one column of the row, in which we use the
1747 ········/// ColumnError property to determine, if the row is a fake row.
1748 ········/// </summary>
1749 ········/// <param name="pcClass"></param>
1750 ········/// <returns></returns>
1751 ········private string GetFakeRowOidColumnName(Class pcClass)
1752 ········{
1753 ············// In case of several OidColumns the first column defined in the mapping
1754 ············// will be the one, holding the fake row info.
1755 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1756 ········}
1757
1758 ········private bool IsFakeRow(Class cl, DataRow row)
1759 ········{
1760 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1761 ········}
1762
1763 ········/// <summary>
1764 ········/// Make a list of objects persistent.
1765 ········/// </summary>
1766 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1767 ········public void MakePersistent(System.Collections.IList list)
1768 ········{
1769 ············foreach (IPersistenceCapable pc in list)
1770 ············{
1771 ················MakePersistent(pc);
1772 ············}
1773 ········}
1774
1775 ········/// <summary>
1776 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1777 ········/// not duplicated.
1778 ········/// </summary>
1779 ········/// <param name="pc">the parent object of all relations</param>
1780 ········/// <returns></returns>
1781 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1782 ········{
1783 ············// Save state of relations
1784 ············Class c = GetClass(pc);
1785 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1786 ············foreach(Relation r in c.Relations)
1787 ············{
1788 ················if (r.Multiplicity == RelationMultiplicity.Element)
1789 ················{
1790 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1791 ················}
1792 ················else
1793 ················{
1794 ····················IList l = mappings.GetRelationContainer(pc, r);
1795 ····················if(l != null)
1796 ····················{
1797 ························l = (IList) ListCloner.CloneList(l);
1798 ····················}
1799 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1800 ················}
1801 ············}
1802
1803 ············return relations;
1804 ········}
1805
1806
1807 ········/// <summary>
1808 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1809 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1810 ········/// the list had at the beginning of the transaction.
1811 ········/// </summary>
1812 ········/// <param name="pc"></param>
1813 ········/// <param name="relations"></param>
1814 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1815 ········{
1816 ············Class c = GetClass(pc);
1817
1818 ············foreach(var entry in relations)
1819 ············{
1820 ················var r = entry.Key;
1821 ················if (r.Multiplicity == RelationMultiplicity.Element)
1822 ················{
1823 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1824 ················}
1825 ················else
1826 ················{
1827 ····················if (pc.NDOGetLoadState(r.Ordinal))
1828 ····················{
1829 ························// Help GC by clearing lists
1830 ························IList l = mappings.GetRelationContainer(pc, r);
1831 ························if(l != null)
1832 ························{
1833 ····························l.Clear();
1834 ························}
1835 ························// Restore relation
1836 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1837 ····················}
1838 ················}
1839 ············}
1840 ········}
1841
1842
1843 ········/// <summary>
1844 ········/// Generates a query for related objects without mapping table.
1845 ········/// Note: this function can't be called in polymorphic scenarios,
1846 ········/// since they need a mapping table.
1847 ········/// </summary>
1848 ········/// <returns></returns>
1849 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1850 ········{
1851 ············// At this point of execution we know,
1852 ············// that the target type is not polymorphic and is not 1:1.
1853
1854 ············// We can't fetch these objects with an NDOql query
1855 ············// since this would require a relation in the opposite direction
1856
1857 ············IList relatedObjects;
1858 ············if (l != null)
1859 ················relatedObjects = l;
1860 ············else
1861 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1862
1863 ············Type t = r.ReferencedType;
1864 ············Class cl = GetClass( t );
1865 ············var provider = cl.Provider;
1866
1867 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1868 ············var relClass = GetClass( r.ReferencedType );
1869 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1870 ············sb.Append( " WHERE " );
1871 ············int i = 0;
1872 ············List<object> parameters = new List<object>();
1873 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1874 ·············· {
1875 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1876 ·················· sb.Append( " = {" );
1877 ·················· sb.Append(i);
1878 ·················· sb.Append( '}' );
1879 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1880 ·················· if (!isLastElement)
1881 ······················ sb.Append( " AND " );
1882 ·················· i++;
1883 ·············· } );
1884
1885 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1886 ············{
1887 ················sb.Append( " AND " );
1888 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1889 ················sb.Append( " = " );
1890 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1891 ············}
1892
1893 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1894
1895 ············foreach (var p in parameters)
1896 ············{
1897 ················q.Parameters.Add( p );
1898 ············}
1899
1900 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1901
1902 ············IList l2 = q.Execute();
1903
1904 ············foreach (object o in l2)
1905 ················relatedObjects.Add( o );
1906
1907 ············return relatedObjects;
1908 ········}
1909
1910
1911 ········/// <summary>
1912 ········/// Resolves an relation. The loaded objects will be hollow.
1913 ········/// </summary>
1914 ········/// <param name="o">The parent object.</param>
1915 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1916 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1917 ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks>
1918 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1919 ········{
1920 ············IPersistenceCapable pc = CheckPc(o);
1921 ············LoadRelationInternal(pc, fieldName, hollow);
1922 ········}
1923
1924 ········
1925
1926 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1927 ········{
1928 ············IList result = null;
1929
1930 ············if (pc.NDOObjectState == NDOObjectState.Created)
1931 ················return null;
1932
1933 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1934 ················LoadData(pc);
1935
1936 ············if(r.MappingTable == null)
1937 ············{
1938 ················// 1:1 are loaded with LoadData
1939 ················if (r.Multiplicity == RelationMultiplicity.List)
1940 ················{
1941 ····················// Help GC by clearing lists
1942 ····················IList l = mappings.GetRelationContainer(pc, r);
1943 ····················if(l != null)
1944 ························l.Clear();
1945 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1946 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1947 ····················result = relatedObjects;
1948 ················}
1949 ············}
1950 ············else
1951 ············{
1952 ················DataTable dt = null;
1953
1954 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1955 ················{
1956 ····················CheckTransaction( handler, r.MappingTable.Connection );
1957 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1958 ················}
1959
1960 ················IList relatedObjects;
1961 ················if(r.Multiplicity == RelationMultiplicity.Element)
1962 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1963 ················else
1964 ················{
1965 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1966 ····················if(relatedObjects != null)
1967 ························relatedObjects.Clear();··// Objects will be reread
1968 ····················else
1969 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1970 ················}
1971 ····················
1972 ················foreach(DataRow objRow in dt.Rows)
1973 ················{
1974 ····················Type relType;
1975
1976 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1977 ····················{
1978 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1979 ························if (typeCodeObj is System.DBNull)
1980 ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) );
1981 ························relType = typeManager[(int)typeCodeObj];
1982 ························if (relType == null)
1983 ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName));
1984 ····················}························
1985 ····················else
1986 ····················{
1987 ························relType = r.ReferencedType;
1988 ····················}
1989
1990 ····················//TODO: Generic Types: Exctract the type description from the type name column
1991 ····················if (relType.IsGenericTypeDefinition)
1992 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1993 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1994 ····················IPersistenceCapable relObj = FindObject(id);
1995 ····················relatedObjects.Add(relObj);
1996 ················}····
1997 ················if (r.Multiplicity == RelationMultiplicity.Element)
1998 ················{
1999 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
2000 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
2001 ················}
2002 ················else
2003 ················{
2004 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
2005 ····················result = relatedObjects;
2006 ················}
2007 ············}
2008 ············// Mark relation as loaded
2009 ············pc.NDOSetLoadState(r.Ordinal, true);
2010 ············return result;
2011 ········}
2012
2013 ········/// <summary>
2014 ········/// Loads elements of a relation
2015 ········/// </summary>
2016 ········/// <param name="pc">The object which needs to load the relation</param>
2017 ········/// <param name="relationName">The name of the relation</param>
2018 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2019 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2020 ········{
2021 ············if (pc.NDOObjectState == NDOObjectState.Created)
2022 ················return null;
2023 ············Class cl = GetClass(pc);
2024
2025 ············Relation r = cl.FindRelation(relationName);
2026
2027 ············if ( r == null )
2028 ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) );
2029
2030 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2031 ················return null;
2032
2033 ············return LoadRelation(pc, r, hollow);
2034 ········}
2035
2036 ········/// <summary>
2037 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2038 ········/// a list of objects that has been read from the DB.
2039 ········/// </summary>
2040 ········/// <param name="pc">The parent object of the relations</param>
2041 ········/// <param name="row">A data row containing the state of the object</param>
2042 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2043 ········{
2044 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2045 ············Class cl = GetClass(pc);
2046 ············foreach(Relation r in cl.Relations)
2047 ············{
2048 ················if(r.MappingTable == null)
2049 ················{
2050 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2051 ····················{
2052 ························bool isNull = false;
2053 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2054 ························{
2055 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2056 ························}
2057 ························if (isNull)
2058 ························{
2059 ····························mappings.SetRelationField(pc, r.FieldName, null);
2060 ························}
2061 ························else
2062 ························{
2063 ····························Type relType;
2064 ····························if (r.HasSubclasses)
2065 ····························{
2066 ································object o = row[r.ForeignKeyTypeColumnName];
2067 ································if (o == DBNull.Value)
2068 ····································throw new NDOException(75, String.Format(
2069 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2070 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2071 ································relType = typeManager[(int)o];
2072 ····························}
2073 ····························else
2074 ····························{
2075 ································relType = r.ReferencedType;
2076 ····························}
2077 ····························if (relType == null)
2078 ····························{
2079 ································throw new NDOException(75, String.Format(
2080 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2081 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2082 ····························}
2083 ····
2084 ····························int count = r.ForeignKeyColumns.Count();
2085 ····························object[] keydata = new object[count];
2086 ····························int i = 0;
2087 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2088 ····························{
2089 ································keydata[i++] = row[fkColumn.Name];
2090 ····························}
2091
2092 ····························Type oidType = relType;
2093 ····························if (oidType.IsGenericTypeDefinition)
2094 ································oidType = mappings.GetRelationFieldType(r);
2095
2096 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2097 ····························if(childOid.IsValid())
2098 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2099 ····························else
2100 ································mappings.SetRelationField(pc, r.FieldName, null);
2101
2102 ························}
2103 ························pc.NDOSetLoadState(r.Ordinal, true);
2104 ····················}
2105 ················}
2106 ············}
2107 ········}
2108
2109 ········
2110 ········/// <summary>
2111 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2112 ········/// </summary>
2113 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2114 ········/// <param name="t">The type of the object, the id will belong to.</param>
2115 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2116 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2117 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2118 ········{
2119 ············return new ObjectId(oid.Id, t);
2120 ········}
2121
2122 ········/*
2123 ········/// <summary>
2124 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2125 ········/// </summary>
2126 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2127 ········/// <param name="t">The type of the object.</param>
2128 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2129 ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks>
2130 ········public ObjectId NewObjectId(object keyData, Type t)
2131 ········{
2132 ············if (keyData == null || keyData == DBNull.Value)
2133 ················return ObjectId.InvalidId;
2134
2135 ············Class cl =··GetClass(t);············
2136 ············
2137 ············if (cl.Oid.FieldType == typeof(int))
2138 ················return new ObjectId(new Int32Key(t, (int)keyData));
2139 ············else if (cl.Oid.FieldType == typeof(string))
2140 ················return new ObjectId(new StringKey(t, (String) keyData));
2141 ············else if (cl.Oid.FieldType == typeof(Guid))
2142 ················if (keyData is string)
2143 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2144 ················else
2145 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2146 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2147 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2148 ············else
2149 ················return ObjectId.InvalidId;
2150 ········}
2151 ········*/
2152
2153
2154 ········/*
2155 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2156 ····················{
2157 ························// The column, which hold the oid gets overwritten, if
2158 ························// Oid.FieldName contains a value.
2159 */
2160 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2161 ········{
2162 ············Class cl = GetClass(pc);
2163
2164 ············if (cl.Oid.IsDependent)
2165 ················return;
2166
2167 ············Key key = pc.NDOObjectId.Id;
2168
2169 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2170 ············{
2171 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2172 ················if (oidColumn.FieldName != null)
2173 ················{
2174 ····················row[oidColumn.Name] = key[i];
2175 ················}
2176 ············}
2177 ········}
2178
2179 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2180 ········{
2181 ············NDO.Mapping.Class cl = GetClass(pc);
2182 ············ObjectId oid = pc.NDOObjectId;
2183
2184 ············if (cl.TimeStampColumn != null)
2185 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2186
2187 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2188 ················return;
2189
2190 ············Key key = oid.Id;
2191
2192 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2193 ············{
2194 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2195 ················row[oidColumn.Name] = key[i];
2196 ············}
2197 ········}
2198
2199 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2200 ········{
2201 ············ObjectId oid = pc.NDOObjectId;
2202 ············NDO.Mapping.Class cl = GetClass(pc);
2203
2204 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2205 ················return;
2206
2207 ············Key key = oid.Id;
2208
2209 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2210 ············{
2211 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2212 ················object o = row[oidColumn.Name];
2213 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2214 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2215 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2216 ····················key[i] = new Guid((string)o);
2217 ················else
2218 ····················key[i] = o;
2219 ············}
2220
2221 ········}
2222
2223 ········private void ReadId (Cache.Entry e)
2224 ········{
2225 ············ReadIdFromRow(e.pc, e.row);
2226 ········}
2227
2228 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2229 ········{
2230 ············WriteObject(pc, row, fieldNames, 0);
2231 ········}
2232
2233 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2234 ········{
2235 ············if (cl.TimeStampColumn == null)
2236 ················return;
2237 ············object col = row[cl.TimeStampColumn];
2238 ············if (col is String)
2239 ················pc.NDOTimeStamp = new Guid(col.ToString());
2240 ············else
2241 ················pc.NDOTimeStamp = (Guid) col;
2242 ········}
2243
2244
2245
2246 ········private void ReadTimeStamp(Cache.Entry e)
2247 ········{
2248 ············IPersistenceCapable pc = e.pc;
2249 ············NDO.Mapping.Class cl = GetClass(pc);
2250 ············Debug.Assert(!IsFakeRow(cl, e.row));
2251 ············if (cl.TimeStampColumn == null)
2252 ················return;
2253 ············if (e.row[cl.TimeStampColumn] is String)
2254 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2255 ············else
2256 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2257 ········}
2258
2259 ········/// <summary>
2260 ········/// Determines, if any objects are new, changed or deleted.
2261 ········/// </summary>
2262 ········public virtual bool HasChanges
2263 ········{
2264 ············get
2265 ············{
2266 ················return cache.LockedObjects.Count > 0;
2267 ············}
2268 ········}
2269
2270 ········/// <summary>
2271 ········/// Do the update for all rows in the ds.
2272 ········/// </summary>
2273 ········/// <param name="types">Types with changes.</param>
2274 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2275 ········/// <remarks>
2276 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2277 ········/// </remarks>
2278 ········private void UpdateTypes(IList types, bool delete)
2279 ········{
2280 ············foreach(Type t in types)
2281 ············{
2282 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2283 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2284 ················{
2285 ····················CheckTransaction( handler, t );
2286 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2287 ····················handler.ConcurrencyError += ceh;
2288 ····················try
2289 ····················{
2290 ························DataTable dt = GetTable(t);
2291 ························if (delete)
2292 ····························handler.UpdateDeletedObjects( dt );
2293 ························else
2294 ····························handler.Update( dt );
2295 ····················}
2296 ····················finally
2297 ····················{
2298 ························handler.ConcurrencyError -= ceh;
2299 ····················}
2300 ················}
2301 ············}
2302 ········}
2303
2304 ········internal void UpdateCreatedMappingTableEntries()
2305 ········{
2306 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2307 ············{
2308 ················if (!e.DeleteEntry)
2309 ····················WriteMappingTableEntry(e);
2310 ············}
2311 ············// Now update all mapping tables
2312 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2313 ············{
2314 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2315 ················handler.Update(ds);
2316 ············}
2317 ········}
2318
2319 ········internal void UpdateDeletedMappingTableEntries()
2320 ········{
2321 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2322 ············{
2323 ················if (e.DeleteEntry)
2324 ····················WriteMappingTableEntry(e);
2325 ············}
2326 ············// Now update all mapping tables
2327 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2328 ············{
2329 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2330 ················handler.Update(ds);
2331 ············}
2332 ········}
2333
2334 ········/// <summary>
2335 ········/// Save all changed object into the DataSet and update the DB.
2336 ········/// When a newly created object is written to DB, the key might change. Therefore,
2337 ········/// the id is updated and the object is removed and re-inserted into the cache.
2338 ········/// </summary>
2339 ········public virtual void Save(bool deferCommit = false)
2340 ········{
2341 ············this.DeferredMode = deferCommit;
2342 ············var htOnSaving = new HashSet<ObjectId>();
2343 ············for(;;)
2344 ············{
2345 ················// We need to work on a copy of the locked objects list,
2346 ················// since the handlers might add more objects to the cache
2347 ················var lockedObjects = cache.LockedObjects.ToList();
2348 ················int count = lockedObjects.Count;
2349 ················foreach(Cache.Entry e in lockedObjects)
2350 ················{
2351 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2352 ····················{
2353 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2354 ························if (ipn != null)
2355 ························{
2356 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2357 ····························{
2358 ································ipn.OnSaving();
2359 ································htOnSaving.Add(e.pc.NDOObjectId);
2360 ····························}
2361 ························}
2362 ····················}
2363 ················}
2364 ················// The system is stable, if the count doesn't change
2365 ················if (cache.LockedObjects.Count == count)
2366 ····················break;
2367 ············}
2368
2369 ············if (this.OnSavingEvent != null)
2370 ············{
2371 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2372 ················foreach(Cache.Entry e in cache.LockedObjects)
2373 ····················onSavingObjects.Add(e.pc);
2374 ················OnSavingEvent(onSavingObjects);
2375 ············}
2376
2377 ············List<Type> types = new List<Type>();
2378 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2379 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2380 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2381 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2382 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2383
2384 ············// Save current state in DataSet
2385 ············foreach (Cache.Entry e in cache.LockedObjects)
2386 ············{
2387 ················Type objType = e.pc.GetType();
2388
2389 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2390 ····················objType = objType.GetGenericTypeDefinition();
2391
2392 ················Class cl = GetClass(e.pc);
2393 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2394 ················if(!types.Contains(objType))
2395 ················{
2396 ····················//Debug.WriteLine("Added··type " + objType.Name);
2397 ····················types.Add(objType);
2398 ················}
2399 ················NDOObjectState objectState = e.pc.NDOObjectState;
2400 ················if(objectState == NDOObjectState.Deleted)
2401 ················{
2402 ····················deletedObjects.Add(e.pc);
2403 ················}
2404 ················else if(objectState == NDOObjectState.Created)
2405 ················{
2406 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2407 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2408 ····················WriteForeignKeysToRow(e.pc, e.row);
2409 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2410 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2411 ····················//····················i = 0;
2412 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2413 ····················//····················{
2414 ····················//························rows[i++] = e2.row;
2415 ····················//····················}
2416 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2417 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2418
2419 ····················addedCacheEntries.Add(e);
2420 ····················addedObjects.Add( e.pc );
2421 ····················//····················objectState = NDOObjectState.Persistent;
2422 ················}
2423 ················else
2424 ················{
2425 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2426 ························changedObjects.Add( e.pc );
2427 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2428 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2429 ················}
2430 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2431 ················{
2432 ····················hollowModeObjects.Add(e.pc);
2433 ················}
2434 ············}
2435
2436 ············// Before we delete any db rows, we have to make sure, to delete mapping
2437 ············// table entries first, which might have relations to the db rows to be deleted
2438 ············UpdateDeletedMappingTableEntries();
2439
2440 ············// Update DB
2441 ············if (ds.HasChanges())
2442 ············{
2443
2444 ················// We need the reversed update order for deletions.
2445 ················types.Sort( ( t1, t2 ) =>
2446 ················{
2447 ····················int i1 = mappings.GetUpdateOrder( t1 );
2448 ····················int i2 = mappings.GetUpdateOrder( t2 );
2449 ····················if (i1 < i2)
2450 ····················{
2451 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2452 ····························i1 += 100000;
2453 ····················}
2454 ····················else
2455 ····················{
2456 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2457 ····························i2 += 100000;
2458 ····················}
2459 ····················return i2 - i1;
2460 ················} );
2461
2462 ················// Delete records first
2463
2464 ················UpdateTypes(types, true);
2465
2466 ················// Now do all other updates in correct order.
2467 ················types.Reverse();
2468
2469 ················UpdateTypes(types, false);
2470 ················
2471 ················ds.AcceptChanges();
2472 ················if(createdDirectObjects.Count > 0)
2473 ················{
2474 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2475 ····················// They must be written again to store the correct foreign keys.
2476 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2477 ····················{
2478 ························Class cl = GetClass(pc);
2479 ························DataRow r = this.cache.GetDataRow(pc);
2480 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2481 ························object o = r[fakeColumnName];
2482 ························r[fakeColumnName] = o;
2483 ····················}
2484
2485 ····················UpdateTypes(types, false);
2486 ················}
2487
2488 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2489 ················foreach(Cache.Entry e in addedCacheEntries)
2490 ················{
2491 ····················cache.DeregisterLockedObject(e.pc);
2492 ····················ReadId(e);
2493 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2494 ················}
2495
2496 ················// Now update all mapping tables. Because of possible subclasses, there is no
2497 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2498 ················// tables only after all other objects have been written to ensure correct foreign keys.
2499 ················UpdateCreatedMappingTableEntries();
2500
2501 ················// The rows may contain now new Ids, which should be
2502 ················// stored in the lostRowInfo's before the rows get detached
2503 ················foreach(Cache.Entry e in cache.LockedObjects)
2504 ················{
2505 ····················if (e.row.RowState != DataRowState.Detached)
2506 ····················{
2507 ························IPersistenceCapable pc = e.pc;
2508 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2509 ····················}
2510 ················}
2511
2512 ················ds.AcceptChanges();
2513 ············}
2514
2515 ············EndSave(!deferCommit);
2516
2517 ············foreach(IPersistenceCapable pc in deletedObjects)
2518 ············{
2519 ················MakeObjectTransient(pc, false);
2520 ············}
2521
2522 ············ds.Clear();
2523 ············mappingHandler.Clear();
2524 ············createdDirectObjects.Clear();
2525 ············createdMappingTableObjects.Clear();
2526 ············this.relationChanges.Clear();
2527
2528 ············if(hollowMode)
2529 ············{
2530 ················MakeHollow(hollowModeObjects);
2531 ············}
2532
2533 ············if (this.OnSavedEvent != null)
2534 ············{
2535 ················AuditSet auditSet = new AuditSet()
2536 ················{
2537 ····················ChangedObjects = changedObjects,
2538 ····················CreatedObjects = addedObjects,
2539 ····················DeletedObjects = deletedObjects
2540 ················};
2541 ················this.OnSavedEvent( auditSet );
2542 ············}
2543 ········}
2544
2545 ········private void EndSave(bool forceCommit)
2546 ········{
2547 ············foreach(Cache.Entry e in cache.LockedObjects)
2548 ············{
2549 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2550 ····················this.ReadTimeStamp(e);
2551 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2552 ············}
2553
2554 ············cache.UnlockAll();
2555
2556 ············CheckEndTransaction(forceCommit);
2557 ········}
2558
2559 ········/// <summary>
2560 ········/// Write all foreign keys for 1:1-relations.
2561 ········/// </summary>
2562 ········/// <param name="pc">The persistent object.</param>
2563 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2564 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2565 ········{
2566 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2567 ············{
2568 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2569 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2570
2571 ················if ( relObj != null )
2572 ················{
2573 ····················int i = 0;
2574 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2575 ····················{
2576 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2577 ····················}
2578 ····················if ( r.ForeignKeyTypeColumnName != null )
2579 ····················{
2580 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2581 ····················}
2582 ················}
2583 ················else
2584 ················{
2585 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2586 ····················{
2587 ························pcRow[fkColumn.Name] = DBNull.Value;
2588 ····················}
2589 ····················if ( r.ForeignKeyTypeColumnName != null )
2590 ····················{
2591 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2592 ····················}
2593 ················}
2594 ············}
2595 ········}
2596
2597
2598
2599 ········/// <summary>
2600 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2601 ········/// </summary>
2602 ········/// <param name="e">the mapping table entry</param>
2603 ········private void WriteMappingTableEntry(MappingTableEntry e)
2604 ········{
2605 ············IPersistenceCapable pc = e.ParentObject;
2606 ············IPersistenceCapable relObj = e.RelatedObject;
2607 ············Relation r = e.Relation;
2608 ············DataTable dt = GetTable(r.MappingTable.TableName);
2609 ············DataRow row = dt.NewRow();
2610 ············int i = 0;
2611 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2612 ············{
2613 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2614 ············}
2615 ············i = 0;
2616 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2617 ············{
2618 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2619 ············}
2620
2621 ············if (r.ForeignKeyTypeColumnName != null)
2622 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2623 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2624 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2625
2626 ············dt.Rows.Add(row);
2627 ············if(e.DeleteEntry)
2628 ············{
2629 ················row.AcceptChanges();
2630 ················row.Delete();
2631 ············}
2632
2633 ············IMappingTableHandler handler;
2634 ············if (!mappingHandler.TryGetValue( r, out handler ))
2635 ············{
2636 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2637 ············}
2638 ········}
2639
2640
2641 ········/// <summary>
2642 ········/// Undo changes of a certain object
2643 ········/// </summary>
2644 ········/// <param name="o">Object to undo</param>
2645 ········public void Restore(object o)
2646 ········{············
2647 ············IPersistenceCapable pc = CheckPc(o);
2648 ············Cache.Entry e = null;
2649 ············foreach (Cache.Entry entry in cache.LockedObjects)
2650 ············{
2651 ················if (entry.pc == pc)
2652 ················{
2653 ····················e = entry;
2654 ····················break;
2655 ················}
2656 ············}
2657 ············if (e == null)
2658 ················return;
2659 ············Class cl = GetClass(e.pc);
2660 ············switch (pc.NDOObjectState)
2661 ············{
2662 ················case NDOObjectState.PersistentDirty:
2663 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2664 ····················foreach(Relation r in cl.Relations)
2665 ····················{
2666 ························if (r.Multiplicity == RelationMultiplicity.Element)
2667 ························{
2668 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2669 ····························if (subPc != null && cache.IsLocked(subPc))
2670 ································Restore(subPc);
2671 ························}
2672 ························else
2673 ························{
2674 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2675 ································continue;
2676 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2677 ····························if (subList != null)
2678 ····························{
2679 ································foreach(IPersistenceCapable subPc2 in subList)
2680 ································{
2681 ····································if (cache.IsLocked(subPc2))
2682 ········································Restore(subPc2);
2683 ································}
2684 ····························}
2685 ························}
2686 ····················}
2687 ····················RestoreRelatedObjects(pc, e.relations);
2688 ····················e.row.RejectChanges();
2689 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2690 ····················cache.Unlock(pc);
2691 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2692 ····················break;
2693 ················case NDOObjectState.Created:
2694 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2695 ····················cache.Unlock(pc);
2696 ····················MakeObjectTransient(pc, true);
2697 ····················break;
2698 ················case NDOObjectState.Deleted:
2699 ····················if (!this.IsFakeRow(cl, e.row))
2700 ····················{
2701 ························e.row.RejectChanges();
2702 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2703 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2704 ····················}
2705 ····················else
2706 ····················{
2707 ························e.row.RejectChanges();
2708 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2709 ····················}
2710 ····················cache.Unlock(pc);
2711 ····················break;
2712
2713 ············}
2714 ········}
2715
2716 ········/// <summary>
2717 ········/// Aborts a pending transaction without restoring the object state.
2718 ········/// </summary>
2719 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2720 ········public virtual void AbortTransaction()
2721 ········{
2722 ············TransactionScope.Dispose();
2723 ········}
2724
2725 ········/// <summary>
2726 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2727 ········/// </summary>
2728 ········public virtual void Abort()
2729 ········{
2730 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2731 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2732 ············// individual row.
2733 ············createdDirectObjects.Clear();
2734 ············createdMappingTableObjects.Clear();
2735 ············ArrayList deletedObjects = new ArrayList();
2736 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2737
2738 ············// Read all objects from DataSet
2739 ············foreach (Cache.Entry e in cache.LockedObjects)
2740 ············{
2741 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2742
2743 ················Class cl = GetClass(e.pc);
2744 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2745 ················if (!isFakeRow)
2746 ················{
2747 ····················RestoreRelatedObjects(e.pc, e.relations);
2748 ················}
2749 ················else
2750 ················{
2751 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2752 ················}
2753
2754 ················switch(e.pc.NDOObjectState)
2755 ················{
2756 ····················case NDOObjectState.Created:
2757 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2758 ························deletedObjects.Add(e.pc);
2759 ························break;
2760
2761 ····················case NDOObjectState.PersistentDirty:
2762 ························e.row.RejectChanges();
2763 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2764 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2765 ························break;
2766
2767 ····················case NDOObjectState.Deleted:
2768 ························if (!isFakeRow)
2769 ························{
2770 ····························e.row.RejectChanges();
2771 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2772 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2773 ························}
2774 ························else
2775 ························{
2776 ····························e.row.RejectChanges();
2777 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2778 ························}
2779 ························break;
2780
2781 ····················default:
2782 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2783 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2784 ························//break;
2785 ················}
2786 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2787 ················{
2788 ····················hollowModeObjects.Add(e.pc);
2789 ················}
2790 ············}
2791 ············cache.UnlockAll();
2792 ············foreach(IPersistenceCapable pc in deletedObjects)
2793 ············{
2794 ················MakeObjectTransient(pc, true);
2795 ············}
2796 ············ds.Clear();
2797 ············mappingHandler.Clear();
2798 ············if(hollowMode)
2799 ············{
2800 ················MakeHollow(hollowModeObjects);
2801 ············}
2802
2803 ············this.relationChanges.Clear();
2804
2805
2806 ············AbortTransaction();
2807 ········}
2808
2809
2810 ········/// <summary>
2811 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2812 ········/// disable future access.
2813 ········/// </summary>
2814 ········/// <param name="pc"></param>
2815 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2816 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2817 ········{
2818 ············cache.Deregister(pc);
2819 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2820 ············if(removeId)
2821 ············{
2822 ················pc.NDOObjectId = null;
2823 ············}
2824 ············pc.NDOObjectState = NDOObjectState.Transient;
2825 ············pc.NDOStateManager = null;
2826 ········}
2827
2828 ········/// <summary>
2829 ········/// Makes an object transient.
2830 ········/// The object can be used afterwards, but changes will not be saved in the database.
2831 ········/// </summary>
2832 ········/// <remarks>
2833 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2834 ········/// </remarks>
2835 ········/// <param name="o">The object to detach.</param>
2836 ········public void MakeTransient(object o)
2837 ········{
2838 ············IPersistenceCapable pc = CheckPc(o);
2839 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2840 ············{
2841 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2842 ············}
2843
2844 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2845 ············{
2846 ················LoadData(pc);
2847 ············}
2848 ············MakeObjectTransient(pc, true);
2849 ········}
2850
2851
2852 ········/// <summary>
2853 ········/// Make all objects of a list transient.
2854 ········/// </summary>
2855 ········/// <param name="list">the list of transient objects</param>
2856 ········public void MakeTransient(System.Collections.IList list)
2857 ········{
2858 ············foreach (IPersistenceCapable pc in list)
2859 ················MakeTransient(pc);
2860 ········}
2861
2862 ········/// <summary>
2863 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2864 ········/// </summary>
2865 ········/// <param name="o">The object to remove</param>
2866 ········public void Delete(object o)
2867 ········{
2868 ············IPersistenceCapable pc = CheckPc(o);
2869 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2870 ············{
2871 ················throw new NDOException( 120, "Can't delete transient object" );
2872 ············}
2873 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2874 ············{
2875 ················Delete(pc, true);
2876 ············}
2877 ········}
2878
2879
2880 ········private void LoadAllRelations(object o)
2881 ········{
2882 ············IPersistenceCapable pc = CheckPc(o);
2883 ············Class cl = GetClass(pc);
2884 ············foreach(Relation r in cl.Relations)
2885 ············{
2886 ················if (!pc.NDOGetLoadState(r.Ordinal))
2887 ····················LoadRelation(pc, r, true);
2888 ············}
2889 ········}
2890
2891
2892 ········/// <summary>
2893 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2894 ········/// </summary>
2895 ········/// <remarks>
2896 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2897 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2898 ········/// the parent object.
2899 ········/// </remarks>
2900 ········/// <param name="pc">the object to remove</param>
2901 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2902 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2903 ········{
2904 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2905 ············//Debug.Indent();
2906 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2907 ············if (idn != null)
2908 ················idn.OnDelete();
2909
2910 ············LoadAllRelations(pc);
2911 ············DeleteRelatedObjects(pc, checkAssoziations);
2912
2913 ············switch(pc.NDOObjectState)
2914 ············{
2915 ················case NDOObjectState.Transient:
2916 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2917
2918 ················case NDOObjectState.Created:
2919 ····················DataRow row = cache.GetDataRow(pc);
2920 ····················row.Delete();
2921 ····················ArrayList cdosToDelete = new ArrayList();
2922 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2923 ························if ((object)cdo == (object)pc)
2924 ····························cdosToDelete.Add(cdo);
2925 ····················foreach (object o in cdosToDelete)
2926 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2927 ····················MakeObjectTransient(pc, true);
2928 ····················break;
2929 ················case NDOObjectState.Persistent:
2930 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2931 ························SaveObjectState(pc, true);
2932 ····················row = cache.GetDataRow(pc);
2933 ····················row.Delete();
2934 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2935 ····················break;
2936 ················case NDOObjectState.Hollow:
2937 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2938 ························SaveFakeRow(pc);
2939 ····················row = cache.GetDataRow(pc);
2940 ····················row.Delete();
2941 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2942 ····················break;
2943
2944 ················case NDOObjectState.PersistentDirty:
2945 ····················row = cache.GetDataRow(pc);
2946 ····················row.Delete();
2947 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2948 ····················break;
2949
2950 ················case NDOObjectState.Deleted:
2951 ····················break;
2952 ············}
2953
2954 ············//Debug.Unindent();
2955 ········}
2956
2957
2958 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2959 ········{
2960 ············MappingTableEntry mte = null;
2961 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2962 ············{
2963 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2964 ················{
2965 ····················mte = e;
2966 ····················break;
2967 ················}
2968 ············}
2969
2970 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2971 ············{
2972 ················if (mte != null)
2973 ····················createdMappingTableObjects.Remove(mte);
2974 ············}
2975 ············else
2976 ············{
2977 ················if (mte == null)
2978 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2979 ············}
2980 ········}
2981
2982 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2983 ········{
2984 ············// Two tasks: a) Null the foreign key
2985 ············//··············b) remove the element from the foreign container
2986
2987 ············if (!r.Bidirectional)
2988 ················return;
2989
2990 ············// this keeps the oid valid
2991 ············if (GetClass(child.GetType()).Oid.IsDependent)
2992 ················return;
2993
2994 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2995 ············{
2996 ················LoadAndMarkDirty(child);
2997 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2998 ············}
2999 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
3000 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
3001 ············{
3002 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
3003 ····················LoadRelation(child, r.ForeignRelation, true);
3004 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
3005 ················if (l == null)
3006 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3007 ················ObjectListManipulator.Remove(l, pc);
3008 ················// Don't need to delete the mapping table entry, because that was done
3009 ················// through the parent.
3010 ············}
3011 ········}
3012
3013 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3014 ········{
3015 ············// 1) Element····nomap····ass
3016 ············// 2) Element····nomap····comp
3017 ············// 3) Element····map········ass
3018 ············// 4) Element····map········comp
3019 ············// 5) List········nomap····ass
3020 ············// 6) List········nomap····comp
3021 ············// 7) List········map········ass
3022 ············// 8) List········map········comp
3023
3024 ············// Two tasks: Null foreign key and, if Composition, delete the child
3025
3026 ············// If Mapping Table, delete the entry - 3,7
3027 ············// If List and assoziation, null the foreign key in the foreign class - 5
3028 ············// If Element, null the foreign key in the own class 1,2,3,4
3029 ············// If composition, delete the child 2,4,6,8
3030
3031 ············// If the relObj is newly created
3032 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3033 ············Class childClass = GetClass(child);
3034
3035 ············if (r.MappingTable != null)··// 3,7
3036 ············{················
3037 ················DeleteMappingTableEntry(pc, r, child);
3038 ············}
3039 ············else if (r.Multiplicity == RelationMultiplicity.List
3040 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3041 ············{················
3042 ················LoadAndMarkDirty(child);
3043 ················DataRow row = this.cache.GetDataRow(child);
3044 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3045 ················{
3046 ····················row[fkColumnn.Name] = DBNull.Value;
3047 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3048 ················}
3049 ············}
3050 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3051 ············{
3052 ················LoadAndMarkDirty(pc);
3053 ············}
3054 ············if (r.Composition || childClass.Oid.IsDependent)
3055 ············{
3056 #if DEBUG
3057 ················if (child.NDOObjectState == NDOObjectState.Transient)
3058 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3059 #endif
3060 ················// Deletes the foreign key in case of List multiplicity
3061 ················// In case of Element multiplicity, the parent is either deleted,
3062 ················// or RemoveRelatedObject is called because the relation has been nulled.
3063 ················Delete(child);··
3064 ············}
3065 ········}
3066
3067 ········/// <summary>
3068 ········/// Remove a related object
3069 ········/// </summary>
3070 ········/// <param name="pc"></param>
3071 ········/// <param name="r"></param>
3072 ········/// <param name="child"></param>
3073 ········/// <param name="calledFromStateManager"></param>
3074 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3075 ········{
3076 ············//TODO: We need a relation management, which is independent of
3077 ············//the state management of an object. At the moment the relation
3078 ············//lists or elements are cached for restore, if an object is marked dirty.
3079 ············//Thus we have to mark dirty our parent object in any case at the moment.
3080 ············if (calledFromStateManager)
3081 ················MarkDirty(pc);
3082
3083 ············// Object can be deleted in an OnDelete-Handler
3084 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3085 ················return;
3086 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3087 ············// Preconditions
3088 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3089 ············// The Object state is checked there
3090
3091 ············// If there is a composition in the opposite direction
3092 ············// && the other direction hasn't been processed
3093 ············// throw an exception.
3094 ············// If an exception is thrown at this point, have a look at IsLocked....
3095 ············if (r.Bidirectional && r.ForeignRelation.Composition
3096 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3097 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3098
3099 ············if (!removeLock.GetLock(pc, r, child))
3100 ················return;
3101
3102 ············try
3103 ············{
3104 ················// must be in this order, since the child
3105 ················// can be deleted in DeleteOrNullRelation
3106 ················//if (changeForeignRelations)
3107 ················DeleteOrNullForeignRelation(pc, r, child);
3108 ················DeleteOrNullRelation(pc, r, child);
3109 ············}
3110 ············finally
3111 ············{
3112 ················removeLock.Unlock(pc, r, child);
3113 ············}
3114
3115 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3116 ········}
3117
3118 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3119 ········{
3120 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3121 ············//············Debug.Indent();
3122 ············if (r.Multiplicity == RelationMultiplicity.Element)
3123 ············{
3124 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3125 ················if(child != null)
3126 ················{
3127 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3128 ····················//····················{
3129 ····················//························if (!r.ForeignRelation.Composition)
3130 ····················//························{
3131 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3132 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3133 ····················//························}
3134 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3135 ····················//························else
3136 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3137 ····················//····················}
3138 ····················InternalRemoveRelatedObject(pc, r, child, false);
3139 ················}
3140 ············}
3141 ············else
3142 ············{
3143 ················IList list = mappings.GetRelationContainer(pc, r);
3144 ················if(list != null && list.Count > 0)
3145 ················{
3146 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3147 ····················//····················{
3148 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3149 ····················//····················}
3150 ····················// Since RemoveRelatedObjects probably changes the list,
3151 ····················// we iterate through a copy of the list.
3152 ····················ArrayList al = new ArrayList(list);
3153 ····················foreach(IPersistenceCapable relObj in al)
3154 ····················{
3155 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3156 ····················}
3157 ················}
3158 ············}
3159 ············//············Debug.Unindent();
3160 ········}
3161
3162 ········/// <summary>
3163 ········/// Remove all related objects from a parent.
3164 ········/// </summary>
3165 ········/// <param name="pc">the parent object</param>
3166 ········/// <param name="checkAssoziations"></param>
3167 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3168 ········{
3169 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3170 ············//············Debug.Indent();
3171 ············// delete all related objects:
3172 ············Class parentClass = GetClass(pc);
3173 ············// Remove Assoziations first
3174 ············foreach(Relation r in parentClass.Relations)
3175 ············{
3176 ················if (!r.Composition)
3177 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3178 ············}
3179 ············foreach(Relation r in parentClass.Relations)
3180 ············{
3181 ················if (r.Composition)
3182 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3183 ············}
3184
3185 ············//············Debug.Unindent();
3186 ········}
3187
3188 ········/// <summary>
3189 ········/// Delete a list of objects
3190 ········/// </summary>
3191 ········/// <param name="list">the list of objects to remove</param>
3192 ········public void Delete(IList list)
3193 ········{
3194 ············for (int i = 0; i < list.Count; i++)
3195 ············{
3196 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3197 ················Delete(pc);
3198 ············}
3199 ········}
3200
3201 ········/// <summary>
3202 ········/// Make object hollow. All relations will be unloaded and object data will be
3203 ········/// newly fetched during the next touch of a persistent field.
3204 ········/// </summary>
3205 ········/// <param name="o"></param>
3206 ········public virtual void MakeHollow(object o)
3207 ········{
3208 ············IPersistenceCapable pc = CheckPc(o);
3209 ············MakeHollow(pc, false);
3210 ········}
3211
3212 ········/// <summary>
3213 ········/// Make the object hollow if it is persistent. Unload all complex data.
3214 ········/// </summary>
3215 ········/// <param name="o"></param>
3216 ········/// <param name="recursive">if true then unload related objects as well</param>
3217 ········public virtual void MakeHollow(object o, bool recursive)
3218 ········{
3219 ············IPersistenceCapable pc = CheckPc(o);
3220 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3221 ················return;
3222 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3223 ············{
3224 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3225 ············}
3226 ············pc.NDOObjectState = NDOObjectState.Hollow;
3227 ············MakeRelationsHollow(pc, recursive);
3228 ········}
3229
3230 ········/// <summary>
3231 ········/// Make all objects of a list hollow.
3232 ········/// </summary>
3233 ········/// <param name="list">the list of objects that should be made hollow</param>
3234 ········public virtual void MakeHollow(System.Collections.IList list)
3235 ········{
3236 ············MakeHollow(list, false);
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all objects of a list hollow.
3241 ········/// </summary>
3242 ········/// <param name="list">the list of objects that should be made hollow</param>
3243 ········/// <param name="recursive">if true then unload related objects as well</param>
3244 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3245 ········{
3246 ············foreach (IPersistenceCapable pc in list)
3247 ················MakeHollow(pc, recursive);················
3248 ········}
3249
3250 ········/// <summary>
3251 ········/// Make all unlocked objects in the cache hollow.
3252 ········/// </summary>
3253 ········public virtual void MakeAllHollow()
3254 ········{
3255 ············foreach(var pc in cache.UnlockedObjects)
3256 ············{
3257 ················MakeHollow(pc, false);
3258 ············}
3259 ········}
3260
3261 ········/// <summary>
3262 ········/// Make relations hollow.
3263 ········/// </summary>
3264 ········/// <param name="pc">The parent object</param>
3265 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3266 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3267 ········{
3268 ············Class c = GetClass(pc);
3269 ············foreach(Relation r in c.Relations)
3270 ············{
3271 ················if (r.Multiplicity == RelationMultiplicity.Element)
3272 ················{
3273 ····················mappings.SetRelationField(pc, r.FieldName, null);
3274 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3275 ····················//····················if((null != child) && recursive)
3276 ····················//····················{
3277 ····················//························MakeHollow(child, true);
3278 ····················//····················}
3279 ················}
3280 ················else
3281 ················{
3282 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3283 ························continue;
3284 ····················// Help GC by clearing lists
3285 ····················IList l = mappings.GetRelationContainer(pc, r);
3286 ····················if(l != null)
3287 ····················{
3288 ························if(recursive)
3289 ························{
3290 ····························MakeHollow(l, true);
3291 ························}
3292 ························l.Clear();
3293 ····················}
3294 ····················// Hollow relation
3295 ····················mappings.SetRelationContainer(pc, r, null);
3296 ················}
3297 ············}
3298 ············ClearRelationState(pc);
3299 ········}
3300
3301 ········private void ClearRelationState(IPersistenceCapable pc)
3302 ········{
3303 ············Class cl = GetClass(pc);
3304 ············foreach(Relation r in cl.Relations)
3305 ················pc.NDOSetLoadState(r.Ordinal, false);
3306 ········}
3307
3308 ········private void SetRelationState(IPersistenceCapable pc)
3309 ········{
3310 ············Class cl = GetClass(pc);
3311 ············// Due to a bug in the enhancer the constructors are not always patched right,
3312 ············// so NDOLoadState might be uninitialized
3313 ············if (pc.NDOLoadState == null)
3314 ············{
3315 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3316 ················if (fi == null)
3317 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3318 ················fi.SetValue(pc, new LoadState());
3319 ············}
3320
3321 ············// After serialization the relation load state is null
3322 ············if (pc.NDOLoadState.RelationLoadState == null)
3323 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3324 ············foreach(Relation r in cl.Relations)
3325 ················pc.NDOSetLoadState(r.Ordinal, true);
3326 ········}
3327
3328 ········/// <summary>
3329 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3330 ········/// </summary>
3331 ········/// <param name="t">The type of the persistent object</param>
3332 ········/// <returns></returns>
3333 ········public IPersistenceCapable CreateObject(Type t)
3334 ········{
3335 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3336 ········}
3337
3338 ········/// <summary>
3339 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3340 ········/// </summary>
3341 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3342 ········/// <returns></returns>
3343 ········public T CreateObject<T>()
3344 ········{
3345 ············return (T)CreateObject( typeof( T ) );
3346 ········}
3347
3348 ········/// <summary>
3349 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3350 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3351 ········/// is performed.
3352 ········/// </summary>
3353 ········/// <param name="t">The type of the object to retrieve.</param>
3354 ········/// <param name="keyData">The key value to build the object id.</param>
3355 ········/// <returns>A hollow object</returns>
3356 ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks>
3357 ········public IPersistenceCapable FindObject(Type t, object keyData)
3358 ········{
3359 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3360 ············return FindObject(oid);
3361 ········}
3362
3363 ········/// <summary>
3364 ········/// Finds an object using a short id.
3365 ········/// </summary>
3366 ········/// <param name="encodedShortId"></param>
3367 ········/// <returns></returns>
3368 ········public IPersistenceCapable FindObject(string encodedShortId)
3369 ········{
3370 ············string shortId = encodedShortId.Decode();
3371 ············string[] arr = shortId.Split( '-' );
3372 ············if (arr.Length != 3)
3373 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3374 ············Type t = shortId.GetObjectType(this);··// try readable format
3375 ············if (t == null)
3376 ············{
3377 ················int typeCode = 0;
3378 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3379 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3380 ················t = this.typeManager[typeCode];
3381 ················if (t == null)
3382 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3383 ············}
3384
3385 ············Class cls = GetClass( t );
3386 ············if (cls == null)
3387 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3388
3389 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3390 ············string[] oidValues = arr[2].Split( ' ' );
3391
3392 ············int i = 0;
3393 ············foreach (var oidValue in oidValues)
3394 ············{
3395 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3396 ················if (oidType == typeof( int ))
3397 ················{
3398 ····················int key;
3399 ····················if (!int.TryParse( oidValue, out key ))
3400 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3401 ····················if (key > (int.MaxValue >> 1))
3402 ························key = -(int.MaxValue - key);
3403 ····················keydata[i] = key;
3404 ················}
3405 ················else if (oidType == typeof( Guid ))
3406 ················{
3407 ····················Guid key;
3408 ····················if (!Guid.TryParse( oidValue, out key ))
3409 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3410 ····················keydata[i] = key;
3411 ················}
3412 ················else if (oidType == typeof( string ))
3413 ················{
3414 ····················keydata[i] = oidValue;
3415 ················}
3416 ················else
3417 ················{
3418 ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) );
3419 ················}
3420
3421 ················i++;
3422 ············}
3423
3424 ············if (keydata.Length == 1)
3425 ················return FindObject( t, keydata[0] );
3426
3427 ············return FindObject( t, keydata );············
3428 ········}
3429
3430 ········/// <summary>
3431 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3432 ········/// instance of the object is returned. In either case, the DB is not accessed!
3433 ········/// </summary>
3434 ········/// <param name="id">Object id</param>
3435 ········/// <returns>The object to retrieve in hollow state</returns>········
3436 ········public IPersistenceCapable FindObject(ObjectId id)
3437 ········{
3438 ············if(id == null)
3439 ············{
3440 ················throw new ArgumentNullException("id");
3441 ············}
3442
3443 ············if(!id.IsValid())
3444 ············{
3445 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3446 ············}
3447
3448 ············IPersistenceCapable pc = cache.GetObject(id);
3449 ············if(pc == null)
3450 ············{
3451 ················pc = CreateObject(id.Id.Type);
3452 ················pc.NDOObjectId = id;
3453 ················pc.NDOStateManager = sm;
3454 ················pc.NDOObjectState = NDOObjectState.Hollow;
3455 ················cache.UpdateCache(pc);
3456 ············}
3457 ············return pc;
3458 ········}
3459
3460
3461 ········/// <summary>
3462 ········/// Reload an object from the database.
3463 ········/// </summary>
3464 ········/// <param name="o">The object to be reloaded.</param>
3465 ········public virtual void Refresh(object o)
3466 ········{
3467 ············IPersistenceCapable pc = CheckPc(o);
3468 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3469 ············{
3470 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3471 ············}
3472
3473 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3474 ················return; // Cannot update objects in current transaction
3475
3476 ············MakeHollow(pc);
3477 ············LoadData(pc);
3478 ········}
3479
3480 ········/// <summary>
3481 ········/// Refresh a list of objects.
3482 ········/// </summary>
3483 ········/// <param name="list">The list of objects to be refreshed.</param>
3484 ········public virtual void Refresh(IList list)
3485 ········{
3486 ············foreach (IPersistenceCapable pc in list)
3487 ················Refresh(pc);························
3488 ········}
3489
3490 ········/// <summary>
3491 ········/// Refreshes all unlocked objects in the cache.
3492 ········/// </summary>
3493 ········public virtual void RefreshAll()
3494 ········{
3495 ············Refresh( cache.UnlockedObjects.ToList() );
3496 ········}
3497
3498 ········/// <summary>
3499 ········/// Closes the PersistenceManager and releases all resources.
3500 ········/// </summary>
3501 ········public override void Close()
3502 ········{
3503 ············if (this.isClosing)
3504 ················return;
3505 ············this.isClosing = true;
3506 ············TransactionScope.Dispose();
3507 ············UnloadCache();
3508 ············base.Close();
3509 ········}
3510
3511 ········internal void LogIfVerbose( string msg )
3512 ········{
3513 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3514 ················Logger.LogDebug( msg );
3515 ········}
3516
3517
3518 ········#endregion
3519
3520
3521 #region Class extent
3522 ········/// <summary>
3523 ········/// Gets all objects of a given class.
3524 ········/// </summary>
3525 ········/// <param name="t">the type of the class</param>
3526 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3527 ········public virtual IList GetClassExtent(Type t)
3528 ········{
3529 ············return GetClassExtent(t, true);
3530 ········}
3531
3532 ········/// <summary>
3533 ········/// Gets all objects of a given class.
3534 ········/// </summary>
3535 ········/// <param name="t">The type of the class.</param>
3536 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3537 ········/// <returns>A list of all persistent objects of the given class.</returns>
3538 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3539 ········public virtual IList GetClassExtent(Type t, bool hollow)
3540 ········{
3541 ············IQuery q = NewQuery( t, null, hollow );
3542 ············return q.Execute();
3543 ········}
3544
3545 #endregion
3546
3547 #region Query engine
3548
3549
3550 ········/// <summary>
3551 ········/// Returns a virtual table for Linq queries.
3552 ········/// </summary>
3553 ········/// <typeparam name="T"></typeparam>
3554 ········/// <returns></returns>
3555 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3556 ········{
3557 ············return new VirtualTable<T>( this );
3558 ········}
3559
3560 ········
3561 ········/// <summary>
3562 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3563 ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have
3564 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3565 ········/// not lose the foreign key it will be stored as part of the object.
3566 ········/// </summary>
3567 ········/// <param name="cl"></param>
3568 ········/// <param name="pc"></param>
3569 ········/// <param name="row"></param>
3570 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3571 ········{
3572 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3573 ············{
3574 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3575 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3576 ················foreach(string colName in cl.FKColumnNames)
3577 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3578 ················pc.NDOLoadState.LostRowInfo = kvl;················
3579 ············}
3580 ········}
3581
3582 ········/// <summary>
3583 ········/// Writes information into the data row, which cannot be stored in the object.
3584 ········/// </summary>
3585 ········/// <param name="cl"></param>
3586 ········/// <param name="pc"></param>
3587 ········/// <param name="row"></param>
3588 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3589 ········{
3590 ············if (cl.FKColumnNames != null)
3591 ············{
3592 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3593 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3594 ················if (kvl == null)
3595 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3596 ················foreach (KeyValuePair pair in kvl)
3597 ····················row[(string) pair.Key] = pair.Value;
3598 ············}
3599 ········}
3600
3601 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3602 ········{
3603 ············ReadObject(pc, row, cl.ColumnNames, 0);
3604 ············ReadTimeStamp(cl, pc, row);
3605 ············ReadLostForeignKeysFromRow(cl, pc, row);
3606 ············LoadRelated1To1Objects(pc, row);
3607 ············pc.NDOObjectState = NDOObjectState.Persistent;
3608 ········}
3609
3610
3611 ········/// <summary>
3612 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3613 ········/// </summary>
3614 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3615 ········{
3616 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3617 ············if (rows.Count == 0)
3618 ················return queryResult;
3619
3620 ············IList callbackObjects = new ArrayList();
3621 ············Class cl = GetClass(t);
3622 ············if (t.IsGenericTypeDefinition)
3623 ············{
3624 ················if (cl.TypeNameColumn == null)
3625 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3626 ················IEnumerator en = rows.GetEnumerator();
3627 ················en.MoveNext();··// Move to the first element
3628 ················DataRow testrow = (DataRow)en.Current;
3629 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3630 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3631 ············}
3632
3633 ············foreach(DataRow row in rows)
3634 ············{
3635 ················Type concreteType = t;
3636 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3637 ················{
3638 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3639 ····················{
3640 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3641 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3642 ····················}
3643 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3644 ····················concreteType = Type.GetType(typeStr);
3645 ····················if (concreteType == null)
3646 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3647 ················}
3648 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3649 ················IPersistenceCapable pc = cache.GetObject(id);················
3650 ················if(pc == null)
3651 ················{
3652 ····················pc = CreateObject( concreteType );
3653 ····················pc.NDOObjectId = id;
3654 ····················pc.NDOStateManager = sm;
3655 ····················// If the object shouldn't be hollow, this will be overwritten later.
3656 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3657 ················}
3658 ················// If we have found a non hollow object, the time stamp remains the old one.
3659 ················// In every other case we use the new time stamp.
3660 ················// Note, that there could be a hollow object in the cache.
3661 ················// We need the time stamp in hollow objects in order to correctly
3662 ················// delete objects using fake rows.
3663 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3664 ················{
3665 ····················ReadTimeStamp(cl, pc, row);
3666 ················}
3667 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3668 ················{
3669 ····················Row2Object(cl, pc, row);
3670 ····················if ((pc as IPersistenceNotifiable) != null)
3671 ························callbackObjects.Add(pc);
3672 ················}
3673
3674 ················cache.UpdateCache(pc);
3675 ················queryResult.Add(pc);
3676 ············}
3677 ············// Make shure this is the last statement before returning
3678 ············// to the caller, so the user can recursively use persistent
3679 ············// objects
3680 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3681 ················ipn.OnLoaded();
3682
3683 ············return queryResult;
3684 ········}
3685
3686 #endregion
3687
3688 #region Cache Management
3689 ········/// <summary>
3690 ········/// Remove all unused entries from the cache.
3691 ········/// </summary>
3692 ········public void CleanupCache()
3693 ········{
3694 ············GC.Collect(GC.MaxGeneration);
3695 ············GC.WaitForPendingFinalizers();
3696 ············cache.Cleanup();
3697 ········}
3698
3699 ········/// <summary>
3700 ········/// Remove all unlocked objects from the cache. Use with care!
3701 ········/// </summary>
3702 ········public void UnloadCache()
3703 ········{
3704 ············cache.Unload();
3705 ········}
3706 #endregion
3707
3708 ········/// <summary>
3709 ········/// Default encryption key for NDO
3710 ········/// </summary>
3711 ········/// <remarks>
3712 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3713 ········/// </remarks>
3714 ········public virtual byte[] EncryptionKey
3715 ········{
3716 ············get
3717 ············{
3718 ················if (this.encryptionKey == null)
3719 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3720 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3721 ················return this.encryptionKey;
3722 ············}
3723 ············set { this.encryptionKey = value; }
3724 ········}
3725
3726 ········/// <summary>
3727 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3728 ········/// </summary>
3729 ········public virtual bool HollowMode
3730 ········{
3731 ············get { return hollowMode; }
3732 ············set { hollowMode = value; }
3733 ········}
3734
3735 ········internal TypeManager TypeManager
3736 ········{
3737 ············get { return typeManager; }
3738 ········}
3739
3740 ········/// <summary>
3741 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3742 ········/// </summary>
3743 ········/// <remarks>
3744 ········/// Set this value before you start any transactions.
3745 ········/// </remarks>
3746 ········public TransactionMode TransactionMode
3747 ········{
3748 ············get { return TransactionScope.TransactionMode; }
3749 ············set { TransactionScope.TransactionMode = value; }
3750 ········}
3751
3752 ········/// <summary>
3753 ········/// Sets or gets the Isolation Level.
3754 ········/// </summary>
3755 ········/// <remarks>
3756 ········/// Set this value before you start any transactions.
3757 ········/// </remarks>
3758 ········public IsolationLevel IsolationLevel
3759 ········{
3760 ············get { return TransactionScope.IsolationLevel; }
3761 ············set { TransactionScope.IsolationLevel = value; }
3762 ········}
3763
3764 ········internal class MappingTableEntry
3765 ········{
3766 ············private IPersistenceCapable parentObject;
3767 ············private IPersistenceCapable relatedObject;
3768 ············private Relation relation;
3769 ············private bool deleteEntry;
3770 ············
3771 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3772 ············{
3773 ············}
3774 ············
3775 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3776 ············{
3777 ················parentObject = pc;
3778 ················relatedObject = relObj;
3779 ················relation = r;
3780 ················this.deleteEntry = deleteEntry;
3781 ············}
3782
3783 ············public bool DeleteEntry
3784 ············{
3785 ················get
3786 ················{
3787 ····················return deleteEntry;
3788 ················}
3789 ············}
3790
3791 ············public IPersistenceCapable ParentObject
3792 ············{
3793 ················get
3794 ················{
3795 ····················return parentObject;
3796 ················}
3797 ············}
3798
3799 ············public IPersistenceCapable RelatedObject
3800 ············{
3801 ················get
3802 ················{
3803 ····················return relatedObject;
3804 ················}
3805 ············}
3806
3807 ············public Relation Relation
3808 ············{
3809 ················get
3810 ················{
3811 ····················return relation;
3812 ················}
3813 ············}
3814 ········}
3815
3816 ········/// <summary>
3817 ········/// Get a DataRow representing the given object.
3818 ········/// </summary>
3819 ········/// <param name="o"></param>
3820 ········/// <returns></returns>
3821 ········public DataRow GetClonedDataRow( object o )
3822 ········{
3823 ············IPersistenceCapable pc = CheckPc( o );
3824
3825 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3826 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3827
3828 ············DataRow row = cache.GetDataRow( pc );
3829 ············DataTable newTable = row.Table.Clone();
3830 ············newTable.ImportRow( row );
3831 ············row = newTable.Rows[0];
3832
3833 ············Class cls = mappings.FindClass(o.GetType());
3834 ············WriteObject( pc, row, cls.ColumnNames );
3835 ············WriteForeignKeysToRow( pc, row );
3836
3837 ············return row;
3838 ········}
3839
3840 ········/// <summary>
3841 ········/// Gets an object, which shows all changes applied to the given object.
3842 ········/// This function can be used to build an audit system.
3843 ········/// </summary>
3844 ········/// <param name="o"></param>
3845 ········/// <returns>An ExpandoObject</returns>
3846 ········public ChangeLog GetChangeSet( object o )
3847 ········{
3848 ············var changeLog = new ChangeLog(this);
3849 ············changeLog.Initialize( o );
3850 ············return changeLog;
3851 ········}
3852
3853 ········/// <summary>
3854 ········/// Outputs a revision number representing the assembly version.
3855 ········/// </summary>
3856 ········/// <remarks>This can be used for debugging purposes</remarks>
3857 ········public int Revision
3858 ········{
3859 ············get
3860 ············{
3861 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3862 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3863 ················return int.Parse( vstring );
3864 ············}
3865 ········}
3866 ····}
3867 }
3868
New Commit (e4584c5)
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 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1365 ········{
1366 ············var connection = provider.NewConnection( ndoConn.Name );
1367 ············var resultList = new List<Guid>();
1368
1369 ············using (var handler = GetSqlPassThroughHandler())
1370 ············{
1371 ················string[] TableNames = provider.GetTableNames( connection );
1372 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1373 ················{
1374 ····················string sql = "SELECT Id from NDOSchemaIds WHERE SchemaName ";
1375 ····················if (String.IsNullOrEmpty(schemaName))
1376 ························sql += "IS NULL;";
1377 ····················else
1378 ························sql += $"LIKE '{schemaName}'";
1379
1380 ····················using(IDataReader dr = handler.Execute(sql, true))
1381 ····················{
1382 ························while (dr.Read())
1383 ····························resultList.Add( dr.GetGuid( 0 ) );
1384 ····················}
1385 ················}
1386 ················else
1387 ················{
1388 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1389 ····················var gt = typeof(Guid);
1390 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1391 ····················var st = typeof(String);
1392 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1393 ····················var dt = typeof(DateTime);
1394 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1395 ····················string transition = $@"<NdoSchemaTransition>
1396 <CreateTable name=""NDOSchemaIds"">
1397 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1398 ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" />
1399 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1400 ····</CreateTable>
1401 </NdoSchemaTransition>";
1402 ····················XElement transitionElement = XElement.Parse(transition);
1403
1404 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1405 ····················handler.Execute(sql);
1406 ················}
1407 ············}
1408
1409 ············return resultList.ToArray();
1410 ········}
1411
1412 ········/// <summary>
1413 ········/// Executes a xml script to generate the database tables.
1414 ········/// The function will generate and execute sql statements to perform
1415 ········/// the changes described by the xml.
1416 ········/// </summary>
1417 ········/// <param name="scriptFile">The xml script file.</param>
1418 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1419 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1420 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1421 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1422 ········{
1423 ············string schemaName = null;
1424 ············// Gespeicherte Version ermitteln.
1425 ············XElement transitionElements = XElement.Load( scriptFile );
1426 ············if (transitionElements.Attribute( "schemaName" ) != null)
1427 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1428
1429 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1430 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1431 ············var newIds = new List<Guid>();
1432 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1433 ············MemoryStream ms = new MemoryStream();
1434 ············StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
1435 ············bool hasChanges = false;
1436
1437 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition"))
1438 ············{
1439 ················var id = transitionElement.Attribute("id")?.Value;
1440 ················if (id == null)
1441 ····················continue;
1442 ················var gid = new Guid(id);
1443 ················if (installedIds.Contains( gid ))
1444 ····················continue;
1445 ················hasChanges = true;
1446 ················sw.Write( schemaTransitionGenerator.Generate( transitionElement ) );
1447 ················newIds.Add( gid );
1448 ············}
1449
1450 ············if (!hasChanges)
1451 ················return new string[] { };
1452
1453 ············// dtLiteral contains the leading and trailing quotes
1454 ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now );
1455
1456 ············foreach (var id in newIds)
1457 ············{
1458 ················sw.WriteLine( $"INSERT INTO NDOSchemaIds ('SchemaName','Id','InsertTime') VALUES ('{schemaName}','{id}',{dtLiteral});" );
1459 ············}
1460
1461 ············sw.Flush();
1462 ············ms.Position = 0L;
1463
1464 ············StreamReader sr = new StreamReader(ms, Encoding.UTF8);
1465 ············string s = sr.ReadToEnd();
1466 ············sr.Close();
1467
1468 ············return InternalPerformSchemaTransitions( ndoConn, s );
1469 ········}
1470
1471 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1472 ········{
1473 ············string[] arr = sql.Split( ';' );
1474 ············string last = arr[arr.Length - 1];
1475 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1476 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1477 ············int i = 0;
1478 ············string ok = "OK";
1479 ············using (var handler = GetSqlPassThroughHandler())
1480 ············{
1481 ················foreach (string statement in arr)
1482 ················{
1483 ····················if (statement != null && statement.Trim() != string.Empty)
1484 ····················{
1485 ························try
1486 ························{
1487 ····························handler.Execute( statement );
1488 ····························result[i] = ok;
1489 ························}
1490 ························catch (Exception ex)
1491 ························{
1492 ····························result[i] = ex.Message;
1493 ························}
1494 ····················}
1495 ····················i++;
1496 ················}
1497
1498 ················handler.CommitTransaction();
1499 ············}
1500 ············return result;
1501 ········}
1502 ········
1503 ········/// <summary>
1504 ········/// Transfers Data from the object to the DataRow
1505 ········/// </summary>
1506 ········/// <param name="pc"></param>
1507 ········/// <param name="row"></param>
1508 ········/// <param name="fieldNames"></param>
1509 ········/// <param name="startIndex"></param>
1510 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1511 ········{
1512 ············Class cl = GetClass( pc );
1513 ············try
1514 ············{
1515 ················pc.NDOWrite(row, fieldNames, startIndex);
1516 ················if (cl.HasEncryptedFields)
1517 ················{
1518 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1519 ····················{
1520 ························string name = field.Column.Name;
1521 ························string s = (string) row[name];
1522 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1523 ························row[name] = es;
1524 ····················}
1525 ················}
1526 ············}
1527 ············catch (Exception ex)
1528 ············{
1529 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1530 ····················+ ex.Message);
1531 ············}
1532
1533 ············if (cl.TypeNameColumn != null)
1534 ············{
1535 ················Type t = pc.GetType();
1536 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1537 ············}
1538
1539 ············var etypes = cl.EmbeddedTypes;
1540 ············foreach(string s in etypes)
1541 ············{
1542 ················try
1543 ················{
1544 ····················NDO.Mapping.Field f = cl.FindField(s);
1545 ····················if (f == null)
1546 ························continue;
1547 ····················string[] arr = s.Split('.');
1548 ····················// Suche Feld mit Namen arr[0] als object
1549 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1550 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1551 ····················Object parentOb = parentFi.GetValue(pc);
1552 ····················if (parentOb == null)
1553 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1554 ····················// Suche darin das Feld mit Namen Arr[1]
1555 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1556 ····················object o = childFi.GetValue(parentOb);
1557 ····················if (o == null
1558 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1559 ························|| o is Guid && (Guid) o == Guid.Empty)
1560 ························o = DBNull.Value;
1561 ····················row[f.Column.Name] = o;
1562 ················}
1563 ················catch (Exception ex)
1564 ················{
1565 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1566
1567 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1568 ················}
1569 ············}
1570 ········}
1571
1572 ········/// <summary>
1573 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1574 ········/// </summary>
1575 ········/// <param name="o">The parent object.</param>
1576 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1577 ········public virtual void LoadField(object o, int fieldOrdinal)
1578 ········{
1579 ············IPersistenceCapable pc = CheckPc(o);
1580 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1581 ············{
1582 ················LoadState ls = pc.NDOLoadState;
1583 ················if (ls.FieldLoadState != null)
1584 ················{
1585 ····················if (ls.FieldLoadState[fieldOrdinal])
1586 ························return;
1587 ················}
1588 ················else
1589 ················{
1590 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1591 ················}
1592 ················LoadData(o);
1593 ········}
1594 ········}
1595
1596 #pragma warning disable 419
1597 ········/// <summary>
1598 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1599 ········/// </summary>
1600 ········/// <param name="o">The hollow object.</param>
1601 ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks>
1602 #pragma warning restore 419
1603 ········public virtual void LoadData( object o )
1604 ········{
1605 ············IPersistenceCapable pc = CheckPc(o);
1606 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1607 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1608 ················return;
1609 ············Class cl = GetClass(pc);
1610 ············IQuery q;
1611 ············q = CreateOidQuery(pc, cl);
1612 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1613
1614 ············var objects = q.Execute();
1615 ············var count = objects.Count;
1616
1617 ············if (count > 1)
1618 ············{
1619 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1620 ············}
1621 ············else if (count == 0)
1622 ············{
1623 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1624 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1625 ············}
1626 ········}
1627
1628 ········/// <summary>
1629 ········/// Creates a new IQuery object for the given type
1630 ········/// </summary>
1631 ········/// <param name="t"></param>
1632 ········/// <param name="oql"></param>
1633 ········/// <param name="hollow"></param>
1634 ········/// <param name="queryLanguage"></param>
1635 ········/// <returns></returns>
1636 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1637 ········{
1638 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1639 ············Type qt = template.MakeGenericType( t );
1640 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1641 ········}
1642
1643 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1644 ········{
1645 ············ArrayList parameters = new ArrayList();
1646 ············string oql = "oid = {0}";
1647 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1648 ············q.Parameters.Add( pc.NDOObjectId );
1649 ············q.AllowSubclasses = false;
1650 ············return q;
1651 ········}
1652
1653 ········/// <summary>
1654 ········/// Mark the object dirty. The current state is
1655 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1656 ········/// object is locked in the cache.
1657 ········/// </summary>
1658 ········/// <param name="pc"></param>
1659 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1660 ········{
1661 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1662 ················return;
1663 ············SaveObjectState(pc);
1664 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1665 ········}
1666
1667 ········/// <summary>
1668 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1669 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1670 ········/// This is done, to allow easy rollback later. Also, the
1671 ········/// object is locked in the cache.
1672 ········/// </summary>
1673 ········/// <param name="pc"></param>
1674 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1675 ········{
1676 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1677
1678 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1679 ············{
1680 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1681 ············}
1682
1683 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1684 ················LoadData(pc);
1685
1686 ············// state is either (Created), Persistent, PersistentDirty
1687 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1688 ············{
1689 ················MarkDirty(pc);
1690 ············}
1691 ········}
1692
1693
1694
1695 ········/// <summary>
1696 ········/// Save current object state in DS and lock the object in the cache.
1697 ········/// The saved state can be used later to retrieve the original object value if the
1698 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1699 ········/// </summary>
1700 ········/// <param name="pc">The object that should be saved</param>
1701 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1702 ········/// <remarks>
1703 ········/// In a data row there are the following things:
1704 ········/// Item································Responsible for writing
1705 ········/// State (own, inherited, embedded)····WriteObject
1706 ········/// TimeStamp····························NDOPersistenceHandler
1707 ········/// Oid····································WriteId
1708 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1709 ········/// </remarks>
1710 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1711 ········{
1712 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1713 ············
1714 ············DataTable table = GetTable(pc);
1715 ············DataRow row = table.NewRow();
1716 ············Class cl = GetClass(pc);
1717 ············WriteObject(pc, row, cl.ColumnNames, 0);
1718 ············WriteIdToRow(pc, row);
1719 ············if (!isDeleting)
1720 ················WriteLostForeignKeysToRow(cl, pc, row);
1721 ············table.Rows.Add(row);
1722 ············row.AcceptChanges();
1723 ············
1724 ············var relations = CollectRelationStates(pc);
1725 ············cache.Lock(pc, row, relations);
1726 ········}
1727
1728 ········private void SaveFakeRow(IPersistenceCapable pc)
1729 ········{
1730 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1731 ············
1732 ············DataTable table = GetTable(pc);
1733 ············DataRow row = table.NewRow();
1734 ············Class pcClass = GetClass(pc);
1735 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1736 ············Class cl = GetClass(pc);
1737 ············//WriteObject(pc, row, cl.FieldNames, 0);
1738 ············WriteIdToRow(pc, row);
1739 ············table.Rows.Add(row);
1740 ············row.AcceptChanges();
1741 ············
1742 ············cache.Lock(pc, row, null);
1743 ········}
1744
1745 ········/// <summary>
1746 ········/// This defines one column of the row, in which we use the
1747 ········/// ColumnError property to determine, if the row is a fake row.
1748 ········/// </summary>
1749 ········/// <param name="pcClass"></param>
1750 ········/// <returns></returns>
1751 ········private string GetFakeRowOidColumnName(Class pcClass)
1752 ········{
1753 ············// In case of several OidColumns the first column defined in the mapping
1754 ············// will be the one, holding the fake row info.
1755 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1756 ········}
1757
1758 ········private bool IsFakeRow(Class cl, DataRow row)
1759 ········{
1760 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1761 ········}
1762
1763 ········/// <summary>
1764 ········/// Make a list of objects persistent.
1765 ········/// </summary>
1766 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1767 ········public void MakePersistent(System.Collections.IList list)
1768 ········{
1769 ············foreach (IPersistenceCapable pc in list)
1770 ············{
1771 ················MakePersistent(pc);
1772 ············}
1773 ········}
1774
1775 ········/// <summary>
1776 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1777 ········/// not duplicated.
1778 ········/// </summary>
1779 ········/// <param name="pc">the parent object of all relations</param>
1780 ········/// <returns></returns>
1781 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1782 ········{
1783 ············// Save state of relations
1784 ············Class c = GetClass(pc);
1785 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1786 ············foreach(Relation r in c.Relations)
1787 ············{
1788 ················if (r.Multiplicity == RelationMultiplicity.Element)
1789 ················{
1790 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1791 ················}
1792 ················else
1793 ················{
1794 ····················IList l = mappings.GetRelationContainer(pc, r);
1795 ····················if(l != null)
1796 ····················{
1797 ························l = (IList) ListCloner.CloneList(l);
1798 ····················}
1799 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1800 ················}
1801 ············}
1802
1803 ············return relations;
1804 ········}
1805
1806
1807 ········/// <summary>
1808 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1809 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1810 ········/// the list had at the beginning of the transaction.
1811 ········/// </summary>
1812 ········/// <param name="pc"></param>
1813 ········/// <param name="relations"></param>
1814 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1815 ········{
1816 ············Class c = GetClass(pc);
1817
1818 ············foreach(var entry in relations)
1819 ············{
1820 ················var r = entry.Key;
1821 ················if (r.Multiplicity == RelationMultiplicity.Element)
1822 ················{
1823 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1824 ················}
1825 ················else
1826 ················{
1827 ····················if (pc.NDOGetLoadState(r.Ordinal))
1828 ····················{
1829 ························// Help GC by clearing lists
1830 ························IList l = mappings.GetRelationContainer(pc, r);
1831 ························if(l != null)
1832 ························{
1833 ····························l.Clear();
1834 ························}
1835 ························// Restore relation
1836 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1837 ····················}
1838 ················}
1839 ············}
1840 ········}
1841
1842
1843 ········/// <summary>
1844 ········/// Generates a query for related objects without mapping table.
1845 ········/// Note: this function can't be called in polymorphic scenarios,
1846 ········/// since they need a mapping table.
1847 ········/// </summary>
1848 ········/// <returns></returns>
1849 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1850 ········{
1851 ············// At this point of execution we know,
1852 ············// that the target type is not polymorphic and is not 1:1.
1853
1854 ············// We can't fetch these objects with an NDOql query
1855 ············// since this would require a relation in the opposite direction
1856
1857 ············IList relatedObjects;
1858 ············if (l != null)
1859 ················relatedObjects = l;
1860 ············else
1861 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1862
1863 ············Type t = r.ReferencedType;
1864 ············Class cl = GetClass( t );
1865 ············var provider = cl.Provider;
1866
1867 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1868 ············var relClass = GetClass( r.ReferencedType );
1869 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1870 ············sb.Append( " WHERE " );
1871 ············int i = 0;
1872 ············List<object> parameters = new List<object>();
1873 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1874 ·············· {
1875 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1876 ·················· sb.Append( " = {" );
1877 ·················· sb.Append(i);
1878 ·················· sb.Append( '}' );
1879 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1880 ·················· if (!isLastElement)
1881 ······················ sb.Append( " AND " );
1882 ·················· i++;
1883 ·············· } );
1884
1885 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1886 ············{
1887 ················sb.Append( " AND " );
1888 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1889 ················sb.Append( " = " );
1890 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1891 ············}
1892
1893 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1894
1895 ············foreach (var p in parameters)
1896 ············{
1897 ················q.Parameters.Add( p );
1898 ············}
1899
1900 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1901
1902 ············IList l2 = q.Execute();
1903
1904 ············foreach (object o in l2)
1905 ················relatedObjects.Add( o );
1906
1907 ············return relatedObjects;
1908 ········}
1909
1910
1911 ········/// <summary>
1912 ········/// Resolves an relation. The loaded objects will be hollow.
1913 ········/// </summary>
1914 ········/// <param name="o">The parent object.</param>
1915 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1916 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1917 ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks>
1918 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1919 ········{
1920 ············IPersistenceCapable pc = CheckPc(o);
1921 ············LoadRelationInternal(pc, fieldName, hollow);
1922 ········}
1923
1924 ········
1925
1926 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1927 ········{
1928 ············IList result = null;
1929
1930 ············if (pc.NDOObjectState == NDOObjectState.Created)
1931 ················return null;
1932
1933 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1934 ················LoadData(pc);
1935
1936 ············if(r.MappingTable == null)
1937 ············{
1938 ················// 1:1 are loaded with LoadData
1939 ················if (r.Multiplicity == RelationMultiplicity.List)
1940 ················{
1941 ····················// Help GC by clearing lists
1942 ····················IList l = mappings.GetRelationContainer(pc, r);
1943 ····················if(l != null)
1944 ························l.Clear();
1945 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1946 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1947 ····················result = relatedObjects;
1948 ················}
1949 ············}
1950 ············else
1951 ············{
1952 ················DataTable dt = null;
1953
1954 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1955 ················{
1956 ····················CheckTransaction( handler, r.MappingTable.Connection );
1957 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1958 ················}
1959
1960 ················IList relatedObjects;
1961 ················if(r.Multiplicity == RelationMultiplicity.Element)
1962 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1963 ················else
1964 ················{
1965 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1966 ····················if(relatedObjects != null)
1967 ························relatedObjects.Clear();··// Objects will be reread
1968 ····················else
1969 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1970 ················}
1971 ····················
1972 ················foreach(DataRow objRow in dt.Rows)
1973 ················{
1974 ····················Type relType;
1975
1976 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1977 ····················{
1978 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1979 ························if (typeCodeObj is System.DBNull)
1980 ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) );
1981 ························relType = typeManager[(int)typeCodeObj];
1982 ························if (relType == null)
1983 ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName));
1984 ····················}························
1985 ····················else
1986 ····················{
1987 ························relType = r.ReferencedType;
1988 ····················}
1989
1990 ····················//TODO: Generic Types: Exctract the type description from the type name column
1991 ····················if (relType.IsGenericTypeDefinition)
1992 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1993 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1994 ····················IPersistenceCapable relObj = FindObject(id);
1995 ····················relatedObjects.Add(relObj);
1996 ················}····
1997 ················if (r.Multiplicity == RelationMultiplicity.Element)
1998 ················{
1999 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
2000 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
2001 ················}
2002 ················else
2003 ················{
2004 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
2005 ····················result = relatedObjects;
2006 ················}
2007 ············}
2008 ············// Mark relation as loaded
2009 ············pc.NDOSetLoadState(r.Ordinal, true);
2010 ············return result;
2011 ········}
2012
2013 ········/// <summary>
2014 ········/// Loads elements of a relation
2015 ········/// </summary>
2016 ········/// <param name="pc">The object which needs to load the relation</param>
2017 ········/// <param name="relationName">The name of the relation</param>
2018 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2019 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2020 ········{
2021 ············if (pc.NDOObjectState == NDOObjectState.Created)
2022 ················return null;
2023 ············Class cl = GetClass(pc);
2024
2025 ············Relation r = cl.FindRelation(relationName);
2026
2027 ············if ( r == null )
2028 ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) );
2029
2030 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2031 ················return null;
2032
2033 ············return LoadRelation(pc, r, hollow);
2034 ········}
2035
2036 ········/// <summary>
2037 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2038 ········/// a list of objects that has been read from the DB.
2039 ········/// </summary>
2040 ········/// <param name="pc">The parent object of the relations</param>
2041 ········/// <param name="row">A data row containing the state of the object</param>
2042 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2043 ········{
2044 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2045 ············Class cl = GetClass(pc);
2046 ············foreach(Relation r in cl.Relations)
2047 ············{
2048 ················if(r.MappingTable == null)
2049 ················{
2050 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2051 ····················{
2052 ························bool isNull = false;
2053 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2054 ························{
2055 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2056 ························}
2057 ························if (isNull)
2058 ························{
2059 ····························mappings.SetRelationField(pc, r.FieldName, null);
2060 ························}
2061 ························else
2062 ························{
2063 ····························Type relType;
2064 ····························if (r.HasSubclasses)
2065 ····························{
2066 ································object o = row[r.ForeignKeyTypeColumnName];
2067 ································if (o == DBNull.Value)
2068 ····································throw new NDOException(75, String.Format(
2069 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2070 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2071 ································relType = typeManager[(int)o];
2072 ····························}
2073 ····························else
2074 ····························{
2075 ································relType = r.ReferencedType;
2076 ····························}
2077 ····························if (relType == null)
2078 ····························{
2079 ································throw new NDOException(75, String.Format(
2080 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2081 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2082 ····························}
2083 ····
2084 ····························int count = r.ForeignKeyColumns.Count();
2085 ····························object[] keydata = new object[count];
2086 ····························int i = 0;
2087 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2088 ····························{
2089 ································keydata[i++] = row[fkColumn.Name];
2090 ····························}
2091
2092 ····························Type oidType = relType;
2093 ····························if (oidType.IsGenericTypeDefinition)
2094 ································oidType = mappings.GetRelationFieldType(r);
2095
2096 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2097 ····························if(childOid.IsValid())
2098 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2099 ····························else
2100 ································mappings.SetRelationField(pc, r.FieldName, null);
2101
2102 ························}
2103 ························pc.NDOSetLoadState(r.Ordinal, true);
2104 ····················}
2105 ················}
2106 ············}
2107 ········}
2108
2109 ········
2110 ········/// <summary>
2111 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2112 ········/// </summary>
2113 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2114 ········/// <param name="t">The type of the object, the id will belong to.</param>
2115 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2116 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2117 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2118 ········{
2119 ············return new ObjectId(oid.Id, t);
2120 ········}
2121
2122 ········/*
2123 ········/// <summary>
2124 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2125 ········/// </summary>
2126 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2127 ········/// <param name="t">The type of the object.</param>
2128 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2129 ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks>
2130 ········public ObjectId NewObjectId(object keyData, Type t)
2131 ········{
2132 ············if (keyData == null || keyData == DBNull.Value)
2133 ················return ObjectId.InvalidId;
2134
2135 ············Class cl =··GetClass(t);············
2136 ············
2137 ············if (cl.Oid.FieldType == typeof(int))
2138 ················return new ObjectId(new Int32Key(t, (int)keyData));
2139 ············else if (cl.Oid.FieldType == typeof(string))
2140 ················return new ObjectId(new StringKey(t, (String) keyData));
2141 ············else if (cl.Oid.FieldType == typeof(Guid))
2142 ················if (keyData is string)
2143 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2144 ················else
2145 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2146 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2147 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2148 ············else
2149 ················return ObjectId.InvalidId;
2150 ········}
2151 ········*/
2152
2153
2154 ········/*
2155 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2156 ····················{
2157 ························// The column, which hold the oid gets overwritten, if
2158 ························// Oid.FieldName contains a value.
2159 */
2160 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2161 ········{
2162 ············Class cl = GetClass(pc);
2163
2164 ············if (cl.Oid.IsDependent)
2165 ················return;
2166
2167 ············Key key = pc.NDOObjectId.Id;
2168
2169 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2170 ············{
2171 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2172 ················if (oidColumn.FieldName != null)
2173 ················{
2174 ····················row[oidColumn.Name] = key[i];
2175 ················}
2176 ············}
2177 ········}
2178
2179 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2180 ········{
2181 ············NDO.Mapping.Class cl = GetClass(pc);
2182 ············ObjectId oid = pc.NDOObjectId;
2183
2184 ············if (cl.TimeStampColumn != null)
2185 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2186
2187 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2188 ················return;
2189
2190 ············Key key = oid.Id;
2191
2192 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2193 ············{
2194 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2195 ················row[oidColumn.Name] = key[i];
2196 ············}
2197 ········}
2198
2199 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2200 ········{
2201 ············ObjectId oid = pc.NDOObjectId;
2202 ············NDO.Mapping.Class cl = GetClass(pc);
2203
2204 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2205 ················return;
2206
2207 ············Key key = oid.Id;
2208
2209 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2210 ············{
2211 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2212 ················object o = row[oidColumn.Name];
2213 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2214 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2215 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2216 ····················key[i] = new Guid((string)o);
2217 ················else
2218 ····················key[i] = o;
2219 ············}
2220
2221 ········}
2222
2223 ········private void ReadId (Cache.Entry e)
2224 ········{
2225 ············ReadIdFromRow(e.pc, e.row);
2226 ········}
2227
2228 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2229 ········{
2230 ············WriteObject(pc, row, fieldNames, 0);
2231 ········}
2232
2233 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2234 ········{
2235 ············if (cl.TimeStampColumn == null)
2236 ················return;
2237 ············object col = row[cl.TimeStampColumn];
2238 ············if (col is String)
2239 ················pc.NDOTimeStamp = new Guid(col.ToString());
2240 ············else
2241 ················pc.NDOTimeStamp = (Guid) col;
2242 ········}
2243
2244
2245
2246 ········private void ReadTimeStamp(Cache.Entry e)
2247 ········{
2248 ············IPersistenceCapable pc = e.pc;
2249 ············NDO.Mapping.Class cl = GetClass(pc);
2250 ············Debug.Assert(!IsFakeRow(cl, e.row));
2251 ············if (cl.TimeStampColumn == null)
2252 ················return;
2253 ············if (e.row[cl.TimeStampColumn] is String)
2254 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2255 ············else
2256 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2257 ········}
2258
2259 ········/// <summary>
2260 ········/// Determines, if any objects are new, changed or deleted.
2261 ········/// </summary>
2262 ········public virtual bool HasChanges
2263 ········{
2264 ············get
2265 ············{
2266 ················return cache.LockedObjects.Count > 0;
2267 ············}
2268 ········}
2269
2270 ········/// <summary>
2271 ········/// Do the update for all rows in the ds.
2272 ········/// </summary>
2273 ········/// <param name="types">Types with changes.</param>
2274 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2275 ········/// <remarks>
2276 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2277 ········/// </remarks>
2278 ········private void UpdateTypes(IList types, bool delete)
2279 ········{
2280 ············foreach(Type t in types)
2281 ············{
2282 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2283 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2284 ················{
2285 ····················CheckTransaction( handler, t );
2286 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2287 ····················handler.ConcurrencyError += ceh;
2288 ····················try
2289 ····················{
2290 ························DataTable dt = GetTable(t);
2291 ························if (delete)
2292 ····························handler.UpdateDeletedObjects( dt );
2293 ························else
2294 ····························handler.Update( dt );
2295 ····················}
2296 ····················finally
2297 ····················{
2298 ························handler.ConcurrencyError -= ceh;
2299 ····················}
2300 ················}
2301 ············}
2302 ········}
2303
2304 ········internal void UpdateCreatedMappingTableEntries()
2305 ········{
2306 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2307 ············{
2308 ················if (!e.DeleteEntry)
2309 ····················WriteMappingTableEntry(e);
2310 ············}
2311 ············// Now update all mapping tables
2312 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2313 ············{
2314 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2315 ················handler.Update(ds);
2316 ············}
2317 ········}
2318
2319 ········internal void UpdateDeletedMappingTableEntries()
2320 ········{
2321 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2322 ············{
2323 ················if (e.DeleteEntry)
2324 ····················WriteMappingTableEntry(e);
2325 ············}
2326 ············// Now update all mapping tables
2327 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2328 ············{
2329 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2330 ················handler.Update(ds);
2331 ············}
2332 ········}
2333
2334 ········/// <summary>
2335 ········/// Save all changed object into the DataSet and update the DB.
2336 ········/// When a newly created object is written to DB, the key might change. Therefore,
2337 ········/// the id is updated and the object is removed and re-inserted into the cache.
2338 ········/// </summary>
2339 ········public virtual void Save(bool deferCommit = false)
2340 ········{
2341 ············this.DeferredMode = deferCommit;
2342 ············var htOnSaving = new HashSet<ObjectId>();
2343 ············for(;;)
2344 ············{
2345 ················// We need to work on a copy of the locked objects list,
2346 ················// since the handlers might add more objects to the cache
2347 ················var lockedObjects = cache.LockedObjects.ToList();
2348 ················int count = lockedObjects.Count;
2349 ················foreach(Cache.Entry e in lockedObjects)
2350 ················{
2351 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2352 ····················{
2353 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2354 ························if (ipn != null)
2355 ························{
2356 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2357 ····························{
2358 ································ipn.OnSaving();
2359 ································htOnSaving.Add(e.pc.NDOObjectId);
2360 ····························}
2361 ························}
2362 ····················}
2363 ················}
2364 ················// The system is stable, if the count doesn't change
2365 ················if (cache.LockedObjects.Count == count)
2366 ····················break;
2367 ············}
2368
2369 ············if (this.OnSavingEvent != null)
2370 ············{
2371 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2372 ················foreach(Cache.Entry e in cache.LockedObjects)
2373 ····················onSavingObjects.Add(e.pc);
2374 ················OnSavingEvent(onSavingObjects);
2375 ············}
2376
2377 ············List<Type> types = new List<Type>();
2378 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2379 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2380 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2381 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2382 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2383
2384 ············// Save current state in DataSet
2385 ············foreach (Cache.Entry e in cache.LockedObjects)
2386 ············{
2387 ················Type objType = e.pc.GetType();
2388
2389 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2390 ····················objType = objType.GetGenericTypeDefinition();
2391
2392 ················Class cl = GetClass(e.pc);
2393 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2394 ················if(!types.Contains(objType))
2395 ················{
2396 ····················//Debug.WriteLine("Added··type " + objType.Name);
2397 ····················types.Add(objType);
2398 ················}
2399 ················NDOObjectState objectState = e.pc.NDOObjectState;
2400 ················if(objectState == NDOObjectState.Deleted)
2401 ················{
2402 ····················deletedObjects.Add(e.pc);
2403 ················}
2404 ················else if(objectState == NDOObjectState.Created)
2405 ················{
2406 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2407 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2408 ····················WriteForeignKeysToRow(e.pc, e.row);
2409 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2410 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2411 ····················//····················i = 0;
2412 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2413 ····················//····················{
2414 ····················//························rows[i++] = e2.row;
2415 ····················//····················}
2416 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2417 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2418
2419 ····················addedCacheEntries.Add(e);
2420 ····················addedObjects.Add( e.pc );
2421 ····················//····················objectState = NDOObjectState.Persistent;
2422 ················}
2423 ················else
2424 ················{
2425 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2426 ························changedObjects.Add( e.pc );
2427 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2428 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2429 ················}
2430 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2431 ················{
2432 ····················hollowModeObjects.Add(e.pc);
2433 ················}
2434 ············}
2435
2436 ············// Before we delete any db rows, we have to make sure, to delete mapping
2437 ············// table entries first, which might have relations to the db rows to be deleted
2438 ············UpdateDeletedMappingTableEntries();
2439
2440 ············// Update DB
2441 ············if (ds.HasChanges())
2442 ············{
2443
2444 ················// We need the reversed update order for deletions.
2445 ················types.Sort( ( t1, t2 ) =>
2446 ················{
2447 ····················int i1 = mappings.GetUpdateOrder( t1 );
2448 ····················int i2 = mappings.GetUpdateOrder( t2 );
2449 ····················if (i1 < i2)
2450 ····················{
2451 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2452 ····························i1 += 100000;
2453 ····················}
2454 ····················else
2455 ····················{
2456 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2457 ····························i2 += 100000;
2458 ····················}
2459 ····················return i2 - i1;
2460 ················} );
2461
2462 ················// Delete records first
2463
2464 ················UpdateTypes(types, true);
2465
2466 ················// Now do all other updates in correct order.
2467 ················types.Reverse();
2468
2469 ················UpdateTypes(types, false);
2470 ················
2471 ················ds.AcceptChanges();
2472 ················if(createdDirectObjects.Count > 0)
2473 ················{
2474 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2475 ····················// They must be written again to store the correct foreign keys.
2476 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2477 ····················{
2478 ························Class cl = GetClass(pc);
2479 ························DataRow r = this.cache.GetDataRow(pc);
2480 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2481 ························object o = r[fakeColumnName];
2482 ························r[fakeColumnName] = o;
2483 ····················}
2484
2485 ····················UpdateTypes(types, false);
2486 ················}
2487
2488 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2489 ················foreach(Cache.Entry e in addedCacheEntries)
2490 ················{
2491 ····················cache.DeregisterLockedObject(e.pc);
2492 ····················ReadId(e);
2493 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2494 ················}
2495
2496 ················// Now update all mapping tables. Because of possible subclasses, there is no
2497 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2498 ················// tables only after all other objects have been written to ensure correct foreign keys.
2499 ················UpdateCreatedMappingTableEntries();
2500
2501 ················// The rows may contain now new Ids, which should be
2502 ················// stored in the lostRowInfo's before the rows get detached
2503 ················foreach(Cache.Entry e in cache.LockedObjects)
2504 ················{
2505 ····················if (e.row.RowState != DataRowState.Detached)
2506 ····················{
2507 ························IPersistenceCapable pc = e.pc;
2508 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2509 ····················}
2510 ················}
2511
2512 ················ds.AcceptChanges();
2513 ············}
2514
2515 ············EndSave(!deferCommit);
2516
2517 ············foreach(IPersistenceCapable pc in deletedObjects)
2518 ············{
2519 ················MakeObjectTransient(pc, false);
2520 ············}
2521
2522 ············ds.Clear();
2523 ············mappingHandler.Clear();
2524 ············createdDirectObjects.Clear();
2525 ············createdMappingTableObjects.Clear();
2526 ············this.relationChanges.Clear();
2527
2528 ············if(hollowMode)
2529 ············{
2530 ················MakeHollow(hollowModeObjects);
2531 ············}
2532
2533 ············if (this.OnSavedEvent != null)
2534 ············{
2535 ················AuditSet auditSet = new AuditSet()
2536 ················{
2537 ····················ChangedObjects = changedObjects,
2538 ····················CreatedObjects = addedObjects,
2539 ····················DeletedObjects = deletedObjects
2540 ················};
2541 ················this.OnSavedEvent( auditSet );
2542 ············}
2543 ········}
2544
2545 ········private void EndSave(bool forceCommit)
2546 ········{
2547 ············foreach(Cache.Entry e in cache.LockedObjects)
2548 ············{
2549 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2550 ····················this.ReadTimeStamp(e);
2551 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2552 ············}
2553
2554 ············cache.UnlockAll();
2555
2556 ············CheckEndTransaction(forceCommit);
2557 ········}
2558
2559 ········/// <summary>
2560 ········/// Write all foreign keys for 1:1-relations.
2561 ········/// </summary>
2562 ········/// <param name="pc">The persistent object.</param>
2563 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2564 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2565 ········{
2566 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2567 ············{
2568 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2569 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2570
2571 ················if ( relObj != null )
2572 ················{
2573 ····················int i = 0;
2574 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2575 ····················{
2576 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2577 ····················}
2578 ····················if ( r.ForeignKeyTypeColumnName != null )
2579 ····················{
2580 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2581 ····················}
2582 ················}
2583 ················else
2584 ················{
2585 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2586 ····················{
2587 ························pcRow[fkColumn.Name] = DBNull.Value;
2588 ····················}
2589 ····················if ( r.ForeignKeyTypeColumnName != null )
2590 ····················{
2591 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2592 ····················}
2593 ················}
2594 ············}
2595 ········}
2596
2597
2598
2599 ········/// <summary>
2600 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2601 ········/// </summary>
2602 ········/// <param name="e">the mapping table entry</param>
2603 ········private void WriteMappingTableEntry(MappingTableEntry e)
2604 ········{
2605 ············IPersistenceCapable pc = e.ParentObject;
2606 ············IPersistenceCapable relObj = e.RelatedObject;
2607 ············Relation r = e.Relation;
2608 ············DataTable dt = GetTable(r.MappingTable.TableName);
2609 ············DataRow row = dt.NewRow();
2610 ············int i = 0;
2611 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2612 ············{
2613 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2614 ············}
2615 ············i = 0;
2616 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2617 ············{
2618 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2619 ············}
2620
2621 ············if (r.ForeignKeyTypeColumnName != null)
2622 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2623 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2624 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2625
2626 ············dt.Rows.Add(row);
2627 ············if(e.DeleteEntry)
2628 ············{
2629 ················row.AcceptChanges();
2630 ················row.Delete();
2631 ············}
2632
2633 ············IMappingTableHandler handler;
2634 ············if (!mappingHandler.TryGetValue( r, out handler ))
2635 ············{
2636 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2637 ············}
2638 ········}
2639
2640
2641 ········/// <summary>
2642 ········/// Undo changes of a certain object
2643 ········/// </summary>
2644 ········/// <param name="o">Object to undo</param>
2645 ········public void Restore(object o)
2646 ········{············
2647 ············IPersistenceCapable pc = CheckPc(o);
2648 ············Cache.Entry e = null;
2649 ············foreach (Cache.Entry entry in cache.LockedObjects)
2650 ············{
2651 ················if (entry.pc == pc)
2652 ················{
2653 ····················e = entry;
2654 ····················break;
2655 ················}
2656 ············}
2657 ············if (e == null)
2658 ················return;
2659 ············Class cl = GetClass(e.pc);
2660 ············switch (pc.NDOObjectState)
2661 ············{
2662 ················case NDOObjectState.PersistentDirty:
2663 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2664 ····················foreach(Relation r in cl.Relations)
2665 ····················{
2666 ························if (r.Multiplicity == RelationMultiplicity.Element)
2667 ························{
2668 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2669 ····························if (subPc != null && cache.IsLocked(subPc))
2670 ································Restore(subPc);
2671 ························}
2672 ························else
2673 ························{
2674 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2675 ································continue;
2676 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2677 ····························if (subList != null)
2678 ····························{
2679 ································foreach(IPersistenceCapable subPc2 in subList)
2680 ································{
2681 ····································if (cache.IsLocked(subPc2))
2682 ········································Restore(subPc2);
2683 ································}
2684 ····························}
2685 ························}
2686 ····················}
2687 ····················RestoreRelatedObjects(pc, e.relations);
2688 ····················e.row.RejectChanges();
2689 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2690 ····················cache.Unlock(pc);
2691 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2692 ····················break;
2693 ················case NDOObjectState.Created:
2694 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2695 ····················cache.Unlock(pc);
2696 ····················MakeObjectTransient(pc, true);
2697 ····················break;
2698 ················case NDOObjectState.Deleted:
2699 ····················if (!this.IsFakeRow(cl, e.row))
2700 ····················{
2701 ························e.row.RejectChanges();
2702 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2703 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2704 ····················}
2705 ····················else
2706 ····················{
2707 ························e.row.RejectChanges();
2708 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2709 ····················}
2710 ····················cache.Unlock(pc);
2711 ····················break;
2712
2713 ············}
2714 ········}
2715
2716 ········/// <summary>
2717 ········/// Aborts a pending transaction without restoring the object state.
2718 ········/// </summary>
2719 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2720 ········public virtual void AbortTransaction()
2721 ········{
2722 ············TransactionScope.Dispose();
2723 ········}
2724
2725 ········/// <summary>
2726 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2727 ········/// </summary>
2728 ········public virtual void Abort()
2729 ········{
2730 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2731 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2732 ············// individual row.
2733 ············createdDirectObjects.Clear();
2734 ············createdMappingTableObjects.Clear();
2735 ············ArrayList deletedObjects = new ArrayList();
2736 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2737
2738 ············// Read all objects from DataSet
2739 ············foreach (Cache.Entry e in cache.LockedObjects)
2740 ············{
2741 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2742
2743 ················Class cl = GetClass(e.pc);
2744 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2745 ················if (!isFakeRow)
2746 ················{
2747 ····················RestoreRelatedObjects(e.pc, e.relations);
2748 ················}
2749 ················else
2750 ················{
2751 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2752 ················}
2753
2754 ················switch(e.pc.NDOObjectState)
2755 ················{
2756 ····················case NDOObjectState.Created:
2757 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2758 ························deletedObjects.Add(e.pc);
2759 ························break;
2760
2761 ····················case NDOObjectState.PersistentDirty:
2762 ························e.row.RejectChanges();
2763 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2764 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2765 ························break;
2766
2767 ····················case NDOObjectState.Deleted:
2768 ························if (!isFakeRow)
2769 ························{
2770 ····························e.row.RejectChanges();
2771 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2772 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2773 ························}
2774 ························else
2775 ························{
2776 ····························e.row.RejectChanges();
2777 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2778 ························}
2779 ························break;
2780
2781 ····················default:
2782 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2783 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2784 ························//break;
2785 ················}
2786 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2787 ················{
2788 ····················hollowModeObjects.Add(e.pc);
2789 ················}
2790 ············}
2791 ············cache.UnlockAll();
2792 ············foreach(IPersistenceCapable pc in deletedObjects)
2793 ············{
2794 ················MakeObjectTransient(pc, true);
2795 ············}
2796 ············ds.Clear();
2797 ············mappingHandler.Clear();
2798 ············if(hollowMode)
2799 ············{
2800 ················MakeHollow(hollowModeObjects);
2801 ············}
2802
2803 ············this.relationChanges.Clear();
2804
2805
2806 ············AbortTransaction();
2807 ········}
2808
2809
2810 ········/// <summary>
2811 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2812 ········/// disable future access.
2813 ········/// </summary>
2814 ········/// <param name="pc"></param>
2815 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2816 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2817 ········{
2818 ············cache.Deregister(pc);
2819 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2820 ············if(removeId)
2821 ············{
2822 ················pc.NDOObjectId = null;
2823 ············}
2824 ············pc.NDOObjectState = NDOObjectState.Transient;
2825 ············pc.NDOStateManager = null;
2826 ········}
2827
2828 ········/// <summary>
2829 ········/// Makes an object transient.
2830 ········/// The object can be used afterwards, but changes will not be saved in the database.
2831 ········/// </summary>
2832 ········/// <remarks>
2833 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2834 ········/// </remarks>
2835 ········/// <param name="o">The object to detach.</param>
2836 ········public void MakeTransient(object o)
2837 ········{
2838 ············IPersistenceCapable pc = CheckPc(o);
2839 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2840 ············{
2841 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2842 ············}
2843
2844 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2845 ············{
2846 ················LoadData(pc);
2847 ············}
2848 ············MakeObjectTransient(pc, true);
2849 ········}
2850
2851
2852 ········/// <summary>
2853 ········/// Make all objects of a list transient.
2854 ········/// </summary>
2855 ········/// <param name="list">the list of transient objects</param>
2856 ········public void MakeTransient(System.Collections.IList list)
2857 ········{
2858 ············foreach (IPersistenceCapable pc in list)
2859 ················MakeTransient(pc);
2860 ········}
2861
2862 ········/// <summary>
2863 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2864 ········/// </summary>
2865 ········/// <param name="o">The object to remove</param>
2866 ········public void Delete(object o)
2867 ········{
2868 ············IPersistenceCapable pc = CheckPc(o);
2869 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2870 ············{
2871 ················throw new NDOException( 120, "Can't delete transient object" );
2872 ············}
2873 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2874 ············{
2875 ················Delete(pc, true);
2876 ············}
2877 ········}
2878
2879
2880 ········private void LoadAllRelations(object o)
2881 ········{
2882 ············IPersistenceCapable pc = CheckPc(o);
2883 ············Class cl = GetClass(pc);
2884 ············foreach(Relation r in cl.Relations)
2885 ············{
2886 ················if (!pc.NDOGetLoadState(r.Ordinal))
2887 ····················LoadRelation(pc, r, true);
2888 ············}
2889 ········}
2890
2891
2892 ········/// <summary>
2893 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2894 ········/// </summary>
2895 ········/// <remarks>
2896 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2897 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2898 ········/// the parent object.
2899 ········/// </remarks>
2900 ········/// <param name="pc">the object to remove</param>
2901 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2902 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2903 ········{
2904 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2905 ············//Debug.Indent();
2906 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2907 ············if (idn != null)
2908 ················idn.OnDelete();
2909
2910 ············LoadAllRelations(pc);
2911 ············DeleteRelatedObjects(pc, checkAssoziations);
2912
2913 ············switch(pc.NDOObjectState)
2914 ············{
2915 ················case NDOObjectState.Transient:
2916 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2917
2918 ················case NDOObjectState.Created:
2919 ····················DataRow row = cache.GetDataRow(pc);
2920 ····················row.Delete();
2921 ····················ArrayList cdosToDelete = new ArrayList();
2922 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2923 ························if ((object)cdo == (object)pc)
2924 ····························cdosToDelete.Add(cdo);
2925 ····················foreach (object o in cdosToDelete)
2926 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2927 ····················MakeObjectTransient(pc, true);
2928 ····················break;
2929 ················case NDOObjectState.Persistent:
2930 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2931 ························SaveObjectState(pc, true);
2932 ····················row = cache.GetDataRow(pc);
2933 ····················row.Delete();
2934 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2935 ····················break;
2936 ················case NDOObjectState.Hollow:
2937 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2938 ························SaveFakeRow(pc);
2939 ····················row = cache.GetDataRow(pc);
2940 ····················row.Delete();
2941 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2942 ····················break;
2943
2944 ················case NDOObjectState.PersistentDirty:
2945 ····················row = cache.GetDataRow(pc);
2946 ····················row.Delete();
2947 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2948 ····················break;
2949
2950 ················case NDOObjectState.Deleted:
2951 ····················break;
2952 ············}
2953
2954 ············//Debug.Unindent();
2955 ········}
2956
2957
2958 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2959 ········{
2960 ············MappingTableEntry mte = null;
2961 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2962 ············{
2963 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2964 ················{
2965 ····················mte = e;
2966 ····················break;
2967 ················}
2968 ············}
2969
2970 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2971 ············{
2972 ················if (mte != null)
2973 ····················createdMappingTableObjects.Remove(mte);
2974 ············}
2975 ············else
2976 ············{
2977 ················if (mte == null)
2978 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2979 ············}
2980 ········}
2981
2982 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2983 ········{
2984 ············// Two tasks: a) Null the foreign key
2985 ············//··············b) remove the element from the foreign container
2986
2987 ············if (!r.Bidirectional)
2988 ················return;
2989
2990 ············// this keeps the oid valid
2991 ············if (GetClass(child.GetType()).Oid.IsDependent)
2992 ················return;
2993
2994 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2995 ············{
2996 ················LoadAndMarkDirty(child);
2997 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2998 ············}
2999 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
3000 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
3001 ············{
3002 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
3003 ····················LoadRelation(child, r.ForeignRelation, true);
3004 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
3005 ················if (l == null)
3006 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3007 ················ObjectListManipulator.Remove(l, pc);
3008 ················// Don't need to delete the mapping table entry, because that was done
3009 ················// through the parent.
3010 ············}
3011 ········}
3012
3013 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3014 ········{
3015 ············// 1) Element····nomap····ass
3016 ············// 2) Element····nomap····comp
3017 ············// 3) Element····map········ass
3018 ············// 4) Element····map········comp
3019 ············// 5) List········nomap····ass
3020 ············// 6) List········nomap····comp
3021 ············// 7) List········map········ass
3022 ············// 8) List········map········comp
3023
3024 ············// Two tasks: Null foreign key and, if Composition, delete the child
3025
3026 ············// If Mapping Table, delete the entry - 3,7
3027 ············// If List and assoziation, null the foreign key in the foreign class - 5
3028 ············// If Element, null the foreign key in the own class 1,2,3,4
3029 ············// If composition, delete the child 2,4,6,8
3030
3031 ············// If the relObj is newly created
3032 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3033 ············Class childClass = GetClass(child);
3034
3035 ············if (r.MappingTable != null)··// 3,7
3036 ············{················
3037 ················DeleteMappingTableEntry(pc, r, child);
3038 ············}
3039 ············else if (r.Multiplicity == RelationMultiplicity.List
3040 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3041 ············{················
3042 ················LoadAndMarkDirty(child);
3043 ················DataRow row = this.cache.GetDataRow(child);
3044 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3045 ················{
3046 ····················row[fkColumnn.Name] = DBNull.Value;
3047 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3048 ················}
3049 ············}
3050 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3051 ············{
3052 ················LoadAndMarkDirty(pc);
3053 ············}
3054 ············if (r.Composition || childClass.Oid.IsDependent)
3055 ············{
3056 #if DEBUG
3057 ················if (child.NDOObjectState == NDOObjectState.Transient)
3058 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3059 #endif
3060 ················// Deletes the foreign key in case of List multiplicity
3061 ················// In case of Element multiplicity, the parent is either deleted,
3062 ················// or RemoveRelatedObject is called because the relation has been nulled.
3063 ················Delete(child);··
3064 ············}
3065 ········}
3066
3067 ········/// <summary>
3068 ········/// Remove a related object
3069 ········/// </summary>
3070 ········/// <param name="pc"></param>
3071 ········/// <param name="r"></param>
3072 ········/// <param name="child"></param>
3073 ········/// <param name="calledFromStateManager"></param>
3074 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3075 ········{
3076 ············//TODO: We need a relation management, which is independent of
3077 ············//the state management of an object. At the moment the relation
3078 ············//lists or elements are cached for restore, if an object is marked dirty.
3079 ············//Thus we have to mark dirty our parent object in any case at the moment.
3080 ············if (calledFromStateManager)
3081 ················MarkDirty(pc);
3082
3083 ············// Object can be deleted in an OnDelete-Handler
3084 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3085 ················return;
3086 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3087 ············// Preconditions
3088 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3089 ············// The Object state is checked there
3090
3091 ············// If there is a composition in the opposite direction
3092 ············// && the other direction hasn't been processed
3093 ············// throw an exception.
3094 ············// If an exception is thrown at this point, have a look at IsLocked....
3095 ············if (r.Bidirectional && r.ForeignRelation.Composition
3096 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3097 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3098
3099 ············if (!removeLock.GetLock(pc, r, child))
3100 ················return;
3101
3102 ············try
3103 ············{
3104 ················// must be in this order, since the child
3105 ················// can be deleted in DeleteOrNullRelation
3106 ················//if (changeForeignRelations)
3107 ················DeleteOrNullForeignRelation(pc, r, child);
3108 ················DeleteOrNullRelation(pc, r, child);
3109 ············}
3110 ············finally
3111 ············{
3112 ················removeLock.Unlock(pc, r, child);
3113 ············}
3114
3115 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3116 ········}
3117
3118 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3119 ········{
3120 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3121 ············//············Debug.Indent();
3122 ············if (r.Multiplicity == RelationMultiplicity.Element)
3123 ············{
3124 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3125 ················if(child != null)
3126 ················{
3127 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3128 ····················//····················{
3129 ····················//························if (!r.ForeignRelation.Composition)
3130 ····················//························{
3131 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3132 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3133 ····················//························}
3134 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3135 ····················//························else
3136 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3137 ····················//····················}
3138 ····················InternalRemoveRelatedObject(pc, r, child, false);
3139 ················}
3140 ············}
3141 ············else
3142 ············{
3143 ················IList list = mappings.GetRelationContainer(pc, r);
3144 ················if(list != null && list.Count > 0)
3145 ················{
3146 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3147 ····················//····················{
3148 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3149 ····················//····················}
3150 ····················// Since RemoveRelatedObjects probably changes the list,
3151 ····················// we iterate through a copy of the list.
3152 ····················ArrayList al = new ArrayList(list);
3153 ····················foreach(IPersistenceCapable relObj in al)
3154 ····················{
3155 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3156 ····················}
3157 ················}
3158 ············}
3159 ············//············Debug.Unindent();
3160 ········}
3161
3162 ········/// <summary>
3163 ········/// Remove all related objects from a parent.
3164 ········/// </summary>
3165 ········/// <param name="pc">the parent object</param>
3166 ········/// <param name="checkAssoziations"></param>
3167 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3168 ········{
3169 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3170 ············//············Debug.Indent();
3171 ············// delete all related objects:
3172 ············Class parentClass = GetClass(pc);
3173 ············// Remove Assoziations first
3174 ············foreach(Relation r in parentClass.Relations)
3175 ············{
3176 ················if (!r.Composition)
3177 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3178 ············}
3179 ············foreach(Relation r in parentClass.Relations)
3180 ············{
3181 ················if (r.Composition)
3182 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3183 ············}
3184
3185 ············//············Debug.Unindent();
3186 ········}
3187
3188 ········/// <summary>
3189 ········/// Delete a list of objects
3190 ········/// </summary>
3191 ········/// <param name="list">the list of objects to remove</param>
3192 ········public void Delete(IList list)
3193 ········{
3194 ············for (int i = 0; i < list.Count; i++)
3195 ············{
3196 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3197 ················Delete(pc);
3198 ············}
3199 ········}
3200
3201 ········/// <summary>
3202 ········/// Make object hollow. All relations will be unloaded and object data will be
3203 ········/// newly fetched during the next touch of a persistent field.
3204 ········/// </summary>
3205 ········/// <param name="o"></param>
3206 ········public virtual void MakeHollow(object o)
3207 ········{
3208 ············IPersistenceCapable pc = CheckPc(o);
3209 ············MakeHollow(pc, false);
3210 ········}
3211
3212 ········/// <summary>
3213 ········/// Make the object hollow if it is persistent. Unload all complex data.
3214 ········/// </summary>
3215 ········/// <param name="o"></param>
3216 ········/// <param name="recursive">if true then unload related objects as well</param>
3217 ········public virtual void MakeHollow(object o, bool recursive)
3218 ········{
3219 ············IPersistenceCapable pc = CheckPc(o);
3220 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3221 ················return;
3222 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3223 ············{
3224 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3225 ············}
3226 ············pc.NDOObjectState = NDOObjectState.Hollow;
3227 ············MakeRelationsHollow(pc, recursive);
3228 ········}
3229
3230 ········/// <summary>
3231 ········/// Make all objects of a list hollow.
3232 ········/// </summary>
3233 ········/// <param name="list">the list of objects that should be made hollow</param>
3234 ········public virtual void MakeHollow(System.Collections.IList list)
3235 ········{
3236 ············MakeHollow(list, false);
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all objects of a list hollow.
3241 ········/// </summary>
3242 ········/// <param name="list">the list of objects that should be made hollow</param>
3243 ········/// <param name="recursive">if true then unload related objects as well</param>
3244 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3245 ········{
3246 ············foreach (IPersistenceCapable pc in list)
3247 ················MakeHollow(pc, recursive);················
3248 ········}
3249
3250 ········/// <summary>
3251 ········/// Make all unlocked objects in the cache hollow.
3252 ········/// </summary>
3253 ········public virtual void MakeAllHollow()
3254 ········{
3255 ············foreach(var pc in cache.UnlockedObjects)
3256 ············{
3257 ················MakeHollow(pc, false);
3258 ············}
3259 ········}
3260
3261 ········/// <summary>
3262 ········/// Make relations hollow.
3263 ········/// </summary>
3264 ········/// <param name="pc">The parent object</param>
3265 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3266 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3267 ········{
3268 ············Class c = GetClass(pc);
3269 ············foreach(Relation r in c.Relations)
3270 ············{
3271 ················if (r.Multiplicity == RelationMultiplicity.Element)
3272 ················{
3273 ····················mappings.SetRelationField(pc, r.FieldName, null);
3274 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3275 ····················//····················if((null != child) && recursive)
3276 ····················//····················{
3277 ····················//························MakeHollow(child, true);
3278 ····················//····················}
3279 ················}
3280 ················else
3281 ················{
3282 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3283 ························continue;
3284 ····················// Help GC by clearing lists
3285 ····················IList l = mappings.GetRelationContainer(pc, r);
3286 ····················if(l != null)
3287 ····················{
3288 ························if(recursive)
3289 ························{
3290 ····························MakeHollow(l, true);
3291 ························}
3292 ························l.Clear();
3293 ····················}
3294 ····················// Hollow relation
3295 ····················mappings.SetRelationContainer(pc, r, null);
3296 ················}
3297 ············}
3298 ············ClearRelationState(pc);
3299 ········}
3300
3301 ········private void ClearRelationState(IPersistenceCapable pc)
3302 ········{
3303 ············Class cl = GetClass(pc);
3304 ············foreach(Relation r in cl.Relations)
3305 ················pc.NDOSetLoadState(r.Ordinal, false);
3306 ········}
3307
3308 ········private void SetRelationState(IPersistenceCapable pc)
3309 ········{
3310 ············Class cl = GetClass(pc);
3311 ············// Due to a bug in the enhancer the constructors are not always patched right,
3312 ············// so NDOLoadState might be uninitialized
3313 ············if (pc.NDOLoadState == null)
3314 ············{
3315 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3316 ················if (fi == null)
3317 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3318 ················fi.SetValue(pc, new LoadState());
3319 ············}
3320
3321 ············// After serialization the relation load state is null
3322 ············if (pc.NDOLoadState.RelationLoadState == null)
3323 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3324 ············foreach(Relation r in cl.Relations)
3325 ················pc.NDOSetLoadState(r.Ordinal, true);
3326 ········}
3327
3328 ········/// <summary>
3329 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3330 ········/// </summary>
3331 ········/// <param name="t">The type of the persistent object</param>
3332 ········/// <returns></returns>
3333 ········public IPersistenceCapable CreateObject(Type t)
3334 ········{
3335 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3336 ········}
3337
3338 ········/// <summary>
3339 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3340 ········/// </summary>
3341 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3342 ········/// <returns></returns>
3343 ········public T CreateObject<T>()
3344 ········{
3345 ············return (T)CreateObject( typeof( T ) );
3346 ········}
3347
3348 ········/// <summary>
3349 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3350 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3351 ········/// is performed.
3352 ········/// </summary>
3353 ········/// <param name="t">The type of the object to retrieve.</param>
3354 ········/// <param name="keyData">The key value to build the object id.</param>
3355 ········/// <returns>A hollow object</returns>
3356 ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks>
3357 ········public IPersistenceCapable FindObject(Type t, object keyData)
3358 ········{
3359 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3360 ············return FindObject(oid);
3361 ········}
3362
3363 ········/// <summary>
3364 ········/// Finds an object using a short id.
3365 ········/// </summary>
3366 ········/// <param name="encodedShortId"></param>
3367 ········/// <returns></returns>
3368 ········public IPersistenceCapable FindObject(string encodedShortId)
3369 ········{
3370 ············string shortId = encodedShortId.Decode();
3371 ············string[] arr = shortId.Split( '-' );
3372 ············if (arr.Length != 3)
3373 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3374 ············Type t = shortId.GetObjectType(this);··// try readable format
3375 ············if (t == null)
3376 ············{
3377 ················int typeCode = 0;
3378 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3379 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3380 ················t = this.typeManager[typeCode];
3381 ················if (t == null)
3382 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3383 ············}
3384
3385 ············Class cls = GetClass( t );
3386 ············if (cls == null)
3387 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3388
3389 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3390 ············string[] oidValues = arr[2].Split( ' ' );
3391
3392 ············int i = 0;
3393 ············foreach (var oidValue in oidValues)
3394 ············{
3395 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3396 ················if (oidType == typeof( int ))
3397 ················{
3398 ····················int key;
3399 ····················if (!int.TryParse( oidValue, out key ))
3400 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3401 ····················if (key > (int.MaxValue >> 1))
3402 ························key = -(int.MaxValue - key);
3403 ····················keydata[i] = key;
3404 ················}
3405 ················else if (oidType == typeof( Guid ))
3406 ················{
3407 ····················Guid key;
3408 ····················if (!Guid.TryParse( oidValue, out key ))
3409 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3410 ····················keydata[i] = key;
3411 ················}
3412 ················else if (oidType == typeof( string ))
3413 ················{
3414 ····················keydata[i] = oidValue;
3415 ················}
3416 ················else
3417 ················{
3418 ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) );
3419 ················}
3420
3421 ················i++;
3422 ············}
3423
3424 ············if (keydata.Length == 1)
3425 ················return FindObject( t, keydata[0] );
3426
3427 ············return FindObject( t, keydata );············
3428 ········}
3429
3430 ········/// <summary>
3431 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3432 ········/// instance of the object is returned. In either case, the DB is not accessed!
3433 ········/// </summary>
3434 ········/// <param name="id">Object id</param>
3435 ········/// <returns>The object to retrieve in hollow state</returns>········
3436 ········public IPersistenceCapable FindObject(ObjectId id)
3437 ········{
3438 ············if(id == null)
3439 ············{
3440 ················throw new ArgumentNullException("id");
3441 ············}
3442
3443 ············if(!id.IsValid())
3444 ············{
3445 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3446 ············}
3447
3448 ············IPersistenceCapable pc = cache.GetObject(id);
3449 ············if(pc == null)
3450 ············{
3451 ················pc = CreateObject(id.Id.Type);
3452 ················pc.NDOObjectId = id;
3453 ················pc.NDOStateManager = sm;
3454 ················pc.NDOObjectState = NDOObjectState.Hollow;
3455 ················cache.UpdateCache(pc);
3456 ············}
3457 ············return pc;
3458 ········}
3459
3460
3461 ········/// <summary>
3462 ········/// Reload an object from the database.
3463 ········/// </summary>
3464 ········/// <param name="o">The object to be reloaded.</param>
3465 ········public virtual void Refresh(object o)
3466 ········{
3467 ············IPersistenceCapable pc = CheckPc(o);
3468 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3469 ············{
3470 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3471 ············}
3472
3473 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3474 ················return; // Cannot update objects in current transaction
3475
3476 ············MakeHollow(pc);
3477 ············LoadData(pc);
3478 ········}
3479
3480 ········/// <summary>
3481 ········/// Refresh a list of objects.
3482 ········/// </summary>
3483 ········/// <param name="list">The list of objects to be refreshed.</param>
3484 ········public virtual void Refresh(IList list)
3485 ········{
3486 ············foreach (IPersistenceCapable pc in list)
3487 ················Refresh(pc);························
3488 ········}
3489
3490 ········/// <summary>
3491 ········/// Refreshes all unlocked objects in the cache.
3492 ········/// </summary>
3493 ········public virtual void RefreshAll()
3494 ········{
3495 ············Refresh( cache.UnlockedObjects.ToList() );
3496 ········}
3497
3498 ········/// <summary>
3499 ········/// Closes the PersistenceManager and releases all resources.
3500 ········/// </summary>
3501 ········public override void Close()
3502 ········{
3503 ············if (this.isClosing)
3504 ················return;
3505 ············this.isClosing = true;
3506 ············TransactionScope.Dispose();
3507 ············UnloadCache();
3508 ············base.Close();
3509 ········}
3510
3511 ········internal void LogIfVerbose( string msg )
3512 ········{
3513 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3514 ················Logger.LogDebug( msg );
3515 ········}
3516
3517
3518 ········#endregion
3519
3520
3521 #region Class extent
3522 ········/// <summary>
3523 ········/// Gets all objects of a given class.
3524 ········/// </summary>
3525 ········/// <param name="t">the type of the class</param>
3526 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3527 ········public virtual IList GetClassExtent(Type t)
3528 ········{
3529 ············return GetClassExtent(t, true);
3530 ········}
3531
3532 ········/// <summary>
3533 ········/// Gets all objects of a given class.
3534 ········/// </summary>
3535 ········/// <param name="t">The type of the class.</param>
3536 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3537 ········/// <returns>A list of all persistent objects of the given class.</returns>
3538 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3539 ········public virtual IList GetClassExtent(Type t, bool hollow)
3540 ········{
3541 ············IQuery q = NewQuery( t, null, hollow );
3542 ············return q.Execute();
3543 ········}
3544
3545 #endregion
3546
3547 #region Query engine
3548
3549
3550 ········/// <summary>
3551 ········/// Returns a virtual table for Linq queries.
3552 ········/// </summary>
3553 ········/// <typeparam name="T"></typeparam>
3554 ········/// <returns></returns>
3555 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3556 ········{
3557 ············return new VirtualTable<T>( this );
3558 ········}
3559
3560 ········
3561 ········/// <summary>
3562 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3563 ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have
3564 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3565 ········/// not lose the foreign key it will be stored as part of the object.
3566 ········/// </summary>
3567 ········/// <param name="cl"></param>
3568 ········/// <param name="pc"></param>
3569 ········/// <param name="row"></param>
3570 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3571 ········{
3572 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3573 ············{
3574 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3575 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3576 ················foreach(string colName in cl.FKColumnNames)
3577 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3578 ················pc.NDOLoadState.LostRowInfo = kvl;················
3579 ············}
3580 ········}
3581
3582 ········/// <summary>
3583 ········/// Writes information into the data row, which cannot be stored in the object.
3584 ········/// </summary>
3585 ········/// <param name="cl"></param>
3586 ········/// <param name="pc"></param>
3587 ········/// <param name="row"></param>
3588 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3589 ········{
3590 ············if (cl.FKColumnNames != null)
3591 ············{
3592 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3593 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3594 ················if (kvl == null)
3595 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3596 ················foreach (KeyValuePair pair in kvl)
3597 ····················row[(string) pair.Key] = pair.Value;
3598 ············}
3599 ········}
3600
3601 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3602 ········{
3603 ············ReadObject(pc, row, cl.ColumnNames, 0);
3604 ············ReadTimeStamp(cl, pc, row);
3605 ············ReadLostForeignKeysFromRow(cl, pc, row);
3606 ············LoadRelated1To1Objects(pc, row);
3607 ············pc.NDOObjectState = NDOObjectState.Persistent;
3608 ········}
3609
3610
3611 ········/// <summary>
3612 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3613 ········/// </summary>
3614 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3615 ········{
3616 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3617 ············if (rows.Count == 0)
3618 ················return queryResult;
3619
3620 ············IList callbackObjects = new ArrayList();
3621 ············Class cl = GetClass(t);
3622 ············if (t.IsGenericTypeDefinition)
3623 ············{
3624 ················if (cl.TypeNameColumn == null)
3625 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3626 ················IEnumerator en = rows.GetEnumerator();
3627 ················en.MoveNext();··// Move to the first element
3628 ················DataRow testrow = (DataRow)en.Current;
3629 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3630 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3631 ············}
3632
3633 ············foreach(DataRow row in rows)
3634 ············{
3635 ················Type concreteType = t;
3636 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3637 ················{
3638 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3639 ····················{
3640 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3641 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3642 ····················}
3643 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3644 ····················concreteType = Type.GetType(typeStr);
3645 ····················if (concreteType == null)
3646 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3647 ················}
3648 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3649 ················IPersistenceCapable pc = cache.GetObject(id);················
3650 ················if(pc == null)
3651 ················{
3652 ····················pc = CreateObject( concreteType );
3653 ····················pc.NDOObjectId = id;
3654 ····················pc.NDOStateManager = sm;
3655 ····················// If the object shouldn't be hollow, this will be overwritten later.
3656 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3657 ················}
3658 ················// If we have found a non hollow object, the time stamp remains the old one.
3659 ················// In every other case we use the new time stamp.
3660 ················// Note, that there could be a hollow object in the cache.
3661 ················// We need the time stamp in hollow objects in order to correctly
3662 ················// delete objects using fake rows.
3663 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3664 ················{
3665 ····················ReadTimeStamp(cl, pc, row);
3666 ················}
3667 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3668 ················{
3669 ····················Row2Object(cl, pc, row);
3670 ····················if ((pc as IPersistenceNotifiable) != null)
3671 ························callbackObjects.Add(pc);
3672 ················}
3673
3674 ················cache.UpdateCache(pc);
3675 ················queryResult.Add(pc);
3676 ············}
3677 ············// Make shure this is the last statement before returning
3678 ············// to the caller, so the user can recursively use persistent
3679 ············// objects
3680 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3681 ················ipn.OnLoaded();
3682
3683 ············return queryResult;
3684 ········}
3685
3686 #endregion
3687
3688 #region Cache Management
3689 ········/// <summary>
3690 ········/// Remove all unused entries from the cache.
3691 ········/// </summary>
3692 ········public void CleanupCache()
3693 ········{
3694 ············GC.Collect(GC.MaxGeneration);
3695 ············GC.WaitForPendingFinalizers();
3696 ············cache.Cleanup();
3697 ········}
3698
3699 ········/// <summary>
3700 ········/// Remove all unlocked objects from the cache. Use with care!
3701 ········/// </summary>
3702 ········public void UnloadCache()
3703 ········{
3704 ············cache.Unload();
3705 ········}
3706 #endregion
3707
3708 ········/// <summary>
3709 ········/// Default encryption key for NDO
3710 ········/// </summary>
3711 ········/// <remarks>
3712 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3713 ········/// </remarks>
3714 ········public virtual byte[] EncryptionKey
3715 ········{
3716 ············get
3717 ············{
3718 ················if (this.encryptionKey == null)
3719 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3720 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3721 ················return this.encryptionKey;
3722 ············}
3723 ············set { this.encryptionKey = value; }
3724 ········}
3725
3726 ········/// <summary>
3727 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3728 ········/// </summary>
3729 ········public virtual bool HollowMode
3730 ········{
3731 ············get { return hollowMode; }
3732 ············set { hollowMode = value; }
3733 ········}
3734
3735 ········internal TypeManager TypeManager
3736 ········{
3737 ············get { return typeManager; }
3738 ········}
3739
3740 ········/// <summary>
3741 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3742 ········/// </summary>
3743 ········/// <remarks>
3744 ········/// Set this value before you start any transactions.
3745 ········/// </remarks>
3746 ········public TransactionMode TransactionMode
3747 ········{
3748 ············get { return TransactionScope.TransactionMode; }
3749 ············set { TransactionScope.TransactionMode = value; }
3750 ········}
3751
3752 ········/// <summary>
3753 ········/// Sets or gets the Isolation Level.
3754 ········/// </summary>
3755 ········/// <remarks>
3756 ········/// Set this value before you start any transactions.
3757 ········/// </remarks>
3758 ········public IsolationLevel IsolationLevel
3759 ········{
3760 ············get { return TransactionScope.IsolationLevel; }
3761 ············set { TransactionScope.IsolationLevel = value; }
3762 ········}
3763
3764 ········internal class MappingTableEntry
3765 ········{
3766 ············private IPersistenceCapable parentObject;
3767 ············private IPersistenceCapable relatedObject;
3768 ············private Relation relation;
3769 ············private bool deleteEntry;
3770 ············
3771 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3772 ············{
3773 ············}
3774 ············
3775 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3776 ············{
3777 ················parentObject = pc;
3778 ················relatedObject = relObj;
3779 ················relation = r;
3780 ················this.deleteEntry = deleteEntry;
3781 ············}
3782
3783 ············public bool DeleteEntry
3784 ············{
3785 ················get
3786 ················{
3787 ····················return deleteEntry;
3788 ················}
3789 ············}
3790
3791 ············public IPersistenceCapable ParentObject
3792 ············{
3793 ················get
3794 ················{
3795 ····················return parentObject;
3796 ················}
3797 ············}
3798
3799 ············public IPersistenceCapable RelatedObject
3800 ············{
3801 ················get
3802 ················{
3803 ····················return relatedObject;
3804 ················}
3805 ············}
3806
3807 ············public Relation Relation
3808 ············{
3809 ················get
3810 ················{
3811 ····················return relation;
3812 ················}
3813 ············}
3814 ········}
3815
3816 ········/// <summary>
3817 ········/// Get a DataRow representing the given object.
3818 ········/// </summary>
3819 ········/// <param name="o"></param>
3820 ········/// <returns></returns>
3821 ········public DataRow GetClonedDataRow( object o )
3822 ········{
3823 ············IPersistenceCapable pc = CheckPc( o );
3824
3825 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3826 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3827
3828 ············DataRow row = cache.GetDataRow( pc );
3829 ············DataTable newTable = row.Table.Clone();
3830 ············newTable.ImportRow( row );
3831 ············row = newTable.Rows[0];
3832
3833 ············Class cls = mappings.FindClass(o.GetType());
3834 ············WriteObject( pc, row, cls.ColumnNames );
3835 ············WriteForeignKeysToRow( pc, row );
3836
3837 ············return row;
3838 ········}
3839
3840 ········/// <summary>
3841 ········/// Gets an object, which shows all changes applied to the given object.
3842 ········/// This function can be used to build an audit system.
3843 ········/// </summary>
3844 ········/// <param name="o"></param>
3845 ········/// <returns>An ExpandoObject</returns>
3846 ········public ChangeLog GetChangeSet( object o )
3847 ········{
3848 ············var changeLog = new ChangeLog(this);
3849 ············changeLog.Initialize( o );
3850 ············return changeLog;
3851 ········}
3852
3853 ········/// <summary>
3854 ········/// Outputs a revision number representing the assembly version.
3855 ········/// </summary>
3856 ········/// <remarks>This can be used for debugging purposes</remarks>
3857 ········public int Revision
3858 ········{
3859 ············get
3860 ············{
3861 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3862 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3863 ················return int.Parse( vstring );
3864 ············}
3865 ········}
3866 ····}
3867 }
3868