Datei: NDODLL/PersistenceManager.cs

Last Commit (f4c9ea2)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
59 ····/// </summary>
60 ····public delegate void OnSavingHandler(ICollection l);
61 ····/// <summary>
62 ····/// Delegate type for the OnSavedEvent.
63 ····/// </summary>
64 ····/// <param name="auditSet"></param>
65 ····public delegate void OnSavedHandler(AuditSet auditSet);
66
67 ····/// <summary>
68 ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid.
69 ····/// </summary>
70 ····/// <param name="pc"></param>
71 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
72 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
73 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
74
75 ····/// <summary>
76 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
77 ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation.
78 ····/// </summary>
79 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
80 ····{········
81 ········private bool hollowMode = false;
82 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
83
84 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
85 ········private ObjectLock removeLock = new ObjectLock();
86 ········private ObjectLock addLock = new ObjectLock();
87 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
88 ········// List of created objects that use mapping table and need to be stored in mapping table
89 ········// after they have been stored to the database to update foreign keys.
90 ········private ArrayList createdMappingTableObjects = new ArrayList();··
91 ········private TypeManager typeManager;
92 ········internal bool DeferredMode { get; private set; }
93 ········private INDOTransactionScope transactionScope;
94 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
95
96 ········private OpenConnectionListener openConnectionListener;
97
98 ········/// <summary>
99 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
100 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
101 ········/// </summary>
102 ········public event CollisionHandler CollisionEvent;
103
104 ········/// <summary>
105 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
106 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
107 ········/// </summary>
108 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event, if you want to be notified about the end
112 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
113 ········/// during the transaction and are to be saved or deleted.
114 ········/// </summary>
115 ········public event OnSavingHandler OnSavingEvent;
116 ········/// <summary>
117 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
118 ········/// </summary>
119 ········public event OnSavedHandler OnSavedEvent;
120 ········
121 ········private const string hollowMarker = "Hollow";
122 ········private byte[] encryptionKey;
123 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
124 ········private bool isClosing = false;
125
126 ········/// <summary>
127 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
128 ········/// </summary>
129 ········protected internal List<RelationChangeRecord> RelationChanges
130 ········{
131 ············get { return this.relationChanges; }
132 ········}
133
134 ········/// <summary>
135 ········/// Initializes a new PersistenceManager instance.
136 ········/// </summary>
137 ········/// <param name="mappingFileName"></param>
138 ········protected override void Init(string mappingFileName)
139 ········{
140 ············try
141 ············{
142 ················base.Init(mappingFileName);
143 ············}
144 ············catch (Exception ex)
145 ············{
146 ················if (ex is NDOException)
147 ····················throw;
148 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
149 ············}
150
151 ········}
152
153 ········/// <summary>
154 ········/// Initializes the persistence manager
155 ········/// </summary>
156 ········/// <remarks>
157 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
158 ········/// </remarks>
159 ········/// <param name="mapping"></param>
160 ········internal override void Init( Mappings mapping )
161 ········{
162 ············base.Init( mapping );
163
164 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
165
166 ············string dir = Path.GetDirectoryName( mapping.FileName );
167
168 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
169 ············typeManager = new TypeManager( typesFile, this.mappings );
170
171 ············sm = new StateManager( this );
172
173 ············InitClasses();
174 ········}
175
176
177 ········/// <summary>
178 ········/// Standard Constructor.
179 ········/// </summary>
180 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
181 ········/// <remarks>
182 ········/// Searches for a mapping file in the application directory.
183 ········/// The constructor tries to find a file with the same name as
184 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
185 ········/// file called AssemblyName.ndo.mapping in the application directory.
186 ········/// </remarks>
187 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
188 ········{
189 ········}
190
191 ········/// <summary>
192 ········/// Loads the mapping file from the specified location. This allows to use
193 ········/// different mapping files with different classes mapped in it.
194 ········/// </summary>
195 ········/// <param name="mappingFile">Path to the mapping file.</param>
196 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
197 ········/// <remarks>Only the Professional and Enterprise
198 ········/// Editions can handle more than one mapping file.</remarks>
199 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
200 ········{
201 ········}
202
203 ········/// <summary>
204 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
205 ········/// </summary>
206 ········/// <param name="mapping">The cached mapping object</param>
207 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
208 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
209 ········{
210 ········}
211
212 ········#region Object Container Stuff
213 ········/// <summary>
214 ········/// Gets a container of all loaded objects and tries to load all child objects,
215 ········/// which are reachable through composite relations.
216 ········/// </summary>
217 ········/// <returns>An ObjectContainer object.</returns>
218 ········/// <remarks>
219 ········/// It is not recommended, to transfer objects with a state other than Hollow,
220 ········/// Persistent, or Transient.
221 ········/// The transfer format is binary.
222 ········/// </remarks>
223 ········public ObjectContainer GetObjectContainer()
224 ········{
225 ············IList l = this.cache.AllObjects;
226 ············foreach(IPersistenceCapable pc in l)
227 ············{
228 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
229 ················{
230 ····················if (Logger != null)
231 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
232 ················}
233 ············}
234
235 ············ObjectContainer oc = new ObjectContainer();
236 ············oc.AddList(l);
237 ············return oc;
238 ········}
239
240 ········/// <summary>
241 ········/// Returns a container of all objects provided in the objects list and searches for
242 ········/// child objects according to the serFlags.
243 ········/// </summary>
244 ········/// <param name="objects">The list of the root objects to add to the container.</param>
245 ········/// <returns>An ObjectContainer object.</returns>
246 ········/// <remarks>
247 ········/// It is not recommended, to transfer objects with a state other than Hollow,
248 ········/// Persistent, or Transient.
249 ········/// </remarks>
250 ········public ObjectContainer GetObjectContainer(IList objects)
251 ········{
252 ············foreach(object o in objects)
253 ············{
254 ················CheckPc(o);
255 ················IPersistenceCapable pc = o as IPersistenceCapable;
256 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
257 ····················LoadData(pc);
258 ············}
259 ············ObjectContainer oc = new ObjectContainer();
260 ············oc.AddList(objects);
261 ············return oc;
262 ········}
263
264
265 ········/// <summary>
266 ········/// Returns a container containing the provided object
267 ········/// and tries to load all child objects
268 ········/// reachable through composite relations.
269 ········/// </summary>
270 ········/// <param name="obj">The object to be added to the container.</param>
271 ········/// <returns>An ObjectContainer object.</returns>
272 ········/// <remarks>
273 ········/// It is not recommended, to transfer objects with a state other than Hollow,
274 ········/// Persistent, or Transient.
275 ········/// The transfer format is binary.
276 ········/// </remarks>
277 ········public ObjectContainer GetObjectContainer(Object obj)
278 ········{
279 ············CheckPc(obj);
280 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
281 ················LoadData(obj);
282 ············ObjectContainer oc = new ObjectContainer();
283 ············oc.AddObject(obj);
284 ············return oc;
285 ········}
286
287 ········/// <summary>
288 ········/// Merges an object container to the active objects in the pm. All changes and the state
289 ········/// of the objects will be taken over by the pm.
290 ········/// </summary>
291 ········/// <remarks>
292 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
293 ········/// The flag MarkAsTransient can be used to perform a kind
294 ········/// of object based replication using the ObjectContainer class.
295 ········/// Objects, which are persistent at one machine, can be transfered
296 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
297 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
298 ········/// transient object tree.
299 ········/// There is one difference to freshly created objects: If an object id exists, it will be
300 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
301 ········/// oids will be reused by the receiving PersistenceManager.
302 ········/// </remarks>
303 ········/// <param name="ocb">The object container to be merged.</param>
304 ········public void MergeObjectContainer(ObjectContainerBase ocb)
305 ········{
306 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
307 ············if (csc != null)
308 ············{
309 ················MergeChangeSet(csc);
310 ················return;
311 ············}
312 ············ObjectContainer oc = ocb as ObjectContainer;
313 ············if (oc != null)
314 ············{
315 ················InternalMergeObjectContainer(oc);
316 ················return;
317 ············}
318 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
319 ········}
320
321
322 ········void InternalMergeObjectContainer(ObjectContainer oc)
323 ········{
324 ············// TODO: Check, if other states are useful. Find use scenarios.
325 ············foreach(IPersistenceCapable pc in oc.RootObjects)
326 ············{
327 ················if (pc.NDOObjectState == NDOObjectState.Transient)
328 ····················MakePersistent(pc);
329 ············}
330 ············foreach(IPersistenceCapable pc in oc.RootObjects)
331 ············{
332 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
333 ············}
334 ········}
335
336 ········void MergeChangeSet(ChangeSetContainer cs)
337 ········{
338 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
339 ············{
340 ················InternalMakePersistent(pc, false);
341 ············}
342 ············foreach(ObjectId oid in cs.DeletedObjects)
343 ············{
344 ················IPersistenceCapable pc2 = FindObject(oid);
345 ················Delete(pc2);
346 ············}
347 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
348 ············{
349 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
350 ················Class pcClass = GetClass(pc);
351 ················// Make sure, the object is loaded.
352 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
353 ····················LoadData(pc2);
354 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
355 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
356 ················DataRow row = entry.row;
357 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
358 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
359 ············}
360 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
361 ············{
362 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
363 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
364 ················Class pcClass = GetClass(parent);
365 ················Relation r = pcClass.FindRelation(rcr.RelationName);
366 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
367 ····················LoadRelation(parent, r, true);
368 ················if (rcr.IsAdded)
369 ················{
370 ····················InternalAddRelatedObject(parent, r, child, true);
371 ····················if (r.Multiplicity == RelationMultiplicity.Element)
372 ····················{
373 ························mappings.SetRelationField(parent, r.FieldName, child);
374 ····················}
375 ····················else
376 ····················{
377 ························IList l = mappings.GetRelationContainer(parent, r);
378 ························l.Add(child);
379 ····················}
380 ················}
381 ················else
382 ················{
383 ····················RemoveRelatedObject(parent, r.FieldName, child);
384 ····················if (r.Multiplicity == RelationMultiplicity.Element)
385 ····················{
386 ························mappings.SetRelationField(parent, r.FieldName, null);
387 ····················}
388 ····················else
389 ····················{
390 ························IList l = mappings.GetRelationContainer(parent, r);
391 ························try
392 ························{
393 ····························ObjectListManipulator.Remove(l, child);
394 ························}
395 ························catch
396 ························{
397 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
398 ························}
399 ····················}
400 ················}
401 ············}
402
403 ········}
404 ········#endregion
405
406 ········#region Implementation of IPersistenceManager
407
408 ········// Complete documentation can be found in IPersistenceManager
409
410
411 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
412 ········{
413 ············if (!cl.Oid.IsDependent)
414 ················return;
415 ············WriteForeignKeysToRow(pc, row);
416 ········}
417
418 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
419 ········{
420 ············// Object is now under control of the state manager
421 ············pc.NDOStateManager = sm;
422
423 ············Type pcType = pc.GetType();
424 ············Class pcClass = GetClass(pc);
425
426 ············// Create new object
427 ············DataTable dt = GetTable(pcType);
428 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
429
430 ············// In case of a Guid oid the value will be computed now.
431 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
432 ············{
433 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
434 ················{
435 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
436 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
437 ····················else
438 ························row[oidColumn.Name] = Guid.NewGuid();
439 ················}
440 ············}
441
442 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
443
444 ············// If the object is merged from an ObjectContainer, the id should be reused,
445 ············// if the id is client generated (not Autoincremented).
446 ············// In every other case, the oid is set to null, to force generating a new oid.
447 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
448 ············if ((object)pc.NDOObjectId != null)
449 ············{
450 ················bool hasAutoincrement = false;
451 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
452 ················{
453 ····················if (oidColumn.AutoIncremented)
454 ····················{
455 ························hasAutoincrement = true;
456 ························break;
457 ····················}
458 ················}
459 ················if (hasAutoincrement) // can't store existing id
460 ················{
461 ····················pc.NDOObjectId = null;
462 ····················fireIdGeneration = true;
463 ················}
464 ············}
465
466 ············// In case of a dependent class the oid has to be read from the fields according to the relations
467 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
468
469 ············if ((object)pc.NDOObjectId == null)
470 ············{
471 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
472 ············}
473
474 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
475 ············{
476 ················if (fireIdGeneration)
477 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
478 ················// At this place the oid might have been
479 ················// - deserialized (MergeObjectContainer)
480 ················// - created using NewObjectId
481 ················// - defined by the IdGenerationEvent
482
483 ················// At this point we have a valid oid.
484 ················// If the object has a field mapped to the oid we have
485 ················// to write back the oid to the field
486 ················int i = 0;
487 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
488 ················{
489 ····················if (oidColumn.FieldName != null)
490 ····················{
491 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
492 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
493 ····················}
494 ····················i++;
495 ················});
496
497
498
499 ················// Now write back the data into the row
500 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
501 ············}
502
503 ············
504 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
505 ············dt.Rows.Add(row);
506
507 ············cache.Register(pc);
508
509 ············// new object that has never been written to the DS
510 ············pc.NDOObjectState = NDOObjectState.Created;
511 ············// Mark all Relations as loaded
512 ············SetRelationState(pc);
513
514 ············if (checkRelations)
515 ············{
516 ················// Handle related objects:
517 ················foreach(Relation r in pcClass.Relations)
518 ················{
519 ····················if (r.Multiplicity == RelationMultiplicity.Element)
520 ····················{
521 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
522 ························if(child != null)
523 ························{
524 ····························AddRelatedObject(pc, r, child);
525 ························}
526 ····················}
527 ····················else
528 ····················{
529 ························IList list = mappings.GetRelationContainer(pc, r);
530 ························if(list != null)
531 ························{
532 ····························foreach(IPersistenceCapable relObj in list)
533 ····························{
534 ································if (relObj != null)
535 ····································AddRelatedObject(pc, r, relObj);
536 ····························}
537 ························}
538 ····················}
539 ················}
540 ············}
541
542 ············var relations··= CollectRelationStates(pc);
543 ············cache.Lock(pc, row, relations);
544 ········}
545
546
547 ········/// <summary>
548 ········/// Make an object persistent.
549 ········/// </summary>
550 ········/// <param name="o">the transient object that should be made persistent</param>
551 ········public void MakePersistent(object o)
552 ········{
553 ············IPersistenceCapable pc = CheckPc(o);
554
555 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
556 ············//Debug.Indent();
557
558 ············if (pc.NDOObjectState != NDOObjectState.Transient)
559 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
560
561 ············InternalMakePersistent(pc, true);
562
563 ········}
564
565
566
567 ········//········/// <summary>
568 ········//········/// Checks, if an object has a valid id, which was created by the database
569 ········//········/// </summary>
570 ········//········/// <param name="pc"></param>
571 ········//········/// <returns></returns>
572 ········//········private bool HasValidId(IPersistenceCapable pc)
573 ········//········{
574 ········//············if (this.IdGenerationEvent != null)
575 ········//················return true;
576 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
577 ········//········}
578
579
580 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
581 ········{
582 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
583 ············if(r.HasSubclasses)
584 ············{
585 ················// we don't support 1:n with foreign fields in subclasses because we would have to
586 ················// search for objects in all subclasses! Instead use a mapping table.
587 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
588 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
589 ············}
590
591 ············if (!makeRelObjPersistent)
592 ················MarkDirty(relObj);
593 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
594 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
595
596 ············if (relObjRow == null)
597 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
598
599 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
600
601 ············if (relObj.NDOLoadState.LostRowInfo == null)
602 ············{
603 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
604 ············}
605 ············else
606 ············{
607 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
608 ············}
609 ········}
610
611 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
612 ········{
613 ············switch(relObj.NDOObjectState)
614 ············{
615 ················case NDOObjectState.Persistent:
616 ····················MarkDirty(relObj);
617 ····················break;
618 ················case NDOObjectState.Hollow:
619 ····················LoadData(relObj);
620 ····················MarkDirty(relObj);
621 ····················break;
622 ············}
623
624 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
625 ············{
626 ················IPersistenceCapable newpc;
627 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
628 ················{
629 ····················if (newpc != pc)
630 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
631 ················}
632 ················else
633 ················{
634 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
635 ················}
636 ············}
637 ············else
638 ············{
639 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
640 ····················LoadRelation(relObj, r.ForeignRelation, true);
641 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
642 ················if(l == null)
643 ················{
644 ····················try
645 ····················{
646 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
647 ····················}
648 ····················catch
649 ····················{
650 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
651 ····················}
652 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
653 ················}
654 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
655 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
656 ················if (!ObjectListManipulator.Contains(l, pc))
657 ····················l.Add(pc);
658 ············}
659 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
660 ········}
661
662
663 ········/// <summary>
664 ········/// Add a related object to the specified object.
665 ········/// </summary>
666 ········/// <param name="pc">the parent object</param>
667 ········/// <param name="fieldName">the field name of the relation</param>
668 ········/// <param name="relObj">the related object that should be added</param>
669 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
670 ········{
671 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
672 ············Relation r = mappings.FindRelation(pc, fieldName);
673 ············AddRelatedObject(pc, r, relObj);
674 ········}
675
676 ········/// <summary>
677 ········/// Core functionality to add an object to a relation container or relation field.
678 ········/// </summary>
679 ········/// <param name="pc"></param>
680 ········/// <param name="r"></param>
681 ········/// <param name="relObj"></param>
682 ········/// <param name="isMerging"></param>
683 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
684 ········{
685 ············
686 ············// avoid recursion
687 ············if (!addLock.GetLock(relObj))
688 ················return;
689
690 ············try
691 ············{
692 ················//TODO: We need a relation management, which is independent of
693 ················//the state management of an object. Currently the relation
694 ················//lists or elements are cached for restore, if an object is marked dirty.
695 ················//Thus we have to mark dirty our parent object in any case at the moment.
696 ················MarkDirty(pc);
697
698 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
699 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
700 ················//The latter happens in CreateAddedObjectRow
701
702 ················Class relClass = GetClass(relObj);
703
704 ················if (r.Multiplicity == RelationMultiplicity.Element
705 ····················&& r.HasSubclasses
706 ····················&& r.MappingTable == null················
707 ····················&& !this.HasOwnerCreatedIds
708 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
709 ····················&& !relClass.HasGuidOid)
710 ················{
711 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
712 ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table.");
713 ····················if (r.Composition)
714 ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's.");
715 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
716 ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." );
717 ················}
718
719 ················bool isDependent = relClass.Oid.IsDependent;
720
721 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
722 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
723
724 ················// Need to patch pc into the relation relObj->pc, because
725 ················// the oid is built on base of this information
726 ················if (isDependent)
727 ················{
728 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
729 ················}
730
731 ················if (r.Composition || isDependent)
732 ················{
733 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
734 ························MakePersistent(relObj);
735 ················}
736
737 ················if(r.MappingTable == null)
738 ················{
739 ····················if (r.Bidirectional)
740 ····················{
741 ························// This object hasn't been saved yet, so the key is wrong.
742 ························// Therefore, the child must be written twice to update the foreign key.
743 #if trace
744 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
745 ························if (r.Multiplicity == RelationMultiplicity.Element)
746 ····························sb.Append("1");
747 ························else
748 ····························sb.Append("n");
749 ························sb.Append(":");
750 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
751 ····························sb.Append("1");
752 ························else
753 ····························sb.Append("n");
754 ························sb.Append ("OwnCreatedOther");
755 ························sb.Append(relObj.NDOObjectState.ToString());
756 ························sb.Append(' ');
757
758 ························sb.Append(types[0].ToString());
759 ························sb.Append(' ');
760 ························sb.Append(types[1].ToString());
761 ························Debug.WriteLine(sb.ToString());
762 #endif
763 ························//························if (r.Multiplicity == RelationMultiplicity.Element
764 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
765 ························//························{
766 ························// Element means:
767 ························// pc is keyholder
768 ························// -> relObj is saved first
769 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
770 ························// Both are Created - use type sort order
771 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
772 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
773 ························{
774 ····························if (mappings.GetUpdateOrder(pc.GetType())
775 ································< mappings.GetUpdateOrder(relObj.GetType()))
776 ································createdDirectObjects.Add(pc);
777 ····························else
778 ································createdDirectObjects.Add( relObj );
779 ························}
780 ····················}
781 ····················if (r.Multiplicity == RelationMultiplicity.List)
782 ····················{
783 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
784 ····················}
785 ················}
786 ················else
787 ················{
788 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
789 ················}
790 ················if(r.Bidirectional)
791 ················{
792 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
793 ····················{
794 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
795 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
796 ····················}
797 ····················else if ( !addLock.IsLocked( pc ) )
798 ····················{
799 ························PatchForeignRelation( pc, r, relObj );
800 ····················}
801 ················}
802
803 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
804 ············}
805 ············finally
806 ············{
807 ················addLock.Unlock(relObj);
808 ················//Debug.Unindent();
809 ············}
810 ········}
811
812 ········/// <summary>
813 ········/// Returns an integer value which determines the rank of the given type in the update order list.
814 ········/// </summary>
815 ········/// <param name="t">The type to determine the update order.</param>
816 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
817 ········/// <remarks>
818 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
819 ········/// </remarks>
820 ········public int GetUpdateRank(Type t)
821 ········{
822 ············return mappings.GetUpdateOrder(t);
823 ········}
824
825 ········/// <summary>
826 ········/// Add a related object to the specified object.
827 ········/// </summary>
828 ········/// <param name="pc">the parent object</param>
829 ········/// <param name="r">the relation mapping info</param>
830 ········/// <param name="relObj">the related object that should be added</param>
831 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
832 ········{
833 ············//············string idstr;
834 ············//············if (relObj.NDOObjectId == null)
835 ············//················idstr = relObj.GetType().ToString();
836 ············//············else
837 ············//················idstr = relObj.NDOObjectId.Dump();
838 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
839 ············//Debug.Indent();
840
841 ············Class relClass = GetClass(relObj);
842 ············bool isDependent = relClass.Oid.IsDependent;
843
844 ············// Do some checks to guarantee that the assignment is correct
845 ············if(r.Composition)
846 ············{
847 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
848 ················{
849 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
850 ················}
851 ············}
852 ············else
853 ············{
854 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
855 ················{
856 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
857 ················}
858 ············}
859
860 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
861 ············{
862 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
863 ············}
864
865 ············InternalAddRelatedObject(pc, r, relObj, false);
866
867 ········}
868
869 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
870 ········{
871 ············// Need to patch pc into the relation relObj->pc, because
872 ············// the oid is built on base of this information
873 ············// The second relation has to be set before adding relObj
874 ············// to the relation list.
875 ············PatchForeignRelation(pc, r, relObj);
876 ············IPersistenceCapable parent;
877 ············foreach (Relation oidRelation in relClass.Oid.Relations)
878 ············{
879 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
880 ················if (parent == null)
881 ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects.");
882 ················if (parent.NDOObjectState == NDOObjectState.Transient)
883 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
884
885 ············}
886 ········}
887
888
889 ········/// <summary>
890 ········/// Remove a related object from the specified object.
891 ········/// </summary>
892 ········/// <param name="pc">the parent object</param>
893 ········/// <param name="fieldName">Field name of the relation</param>
894 ········/// <param name="relObj">the related object that should be removed</param>
895 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
896 ········{
897 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
898 ············Relation r = mappings.FindRelation(pc, fieldName);
899 ············InternalRemoveRelatedObject(pc, r, relObj, true);
900 ········}
901
902 ········/// <summary>
903 ········/// Registers a listener which will be notified, if a new connection is opened.
904 ········/// </summary>
905 ········/// <param name="listener">Delegate of a listener function</param>
906 ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks>
907 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
908 ········{
909 ············this.openConnectionListener = listener;
910 ········}
911
912 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
913 ········{
914 ············if (openConnectionListener != null)
915 ················return openConnectionListener(conn);
916 ············return conn.Name;
917 ········}
918
919
920 ········/*
921 ········doCommit should be:
922 ········
923 ····················Query····Save····Save(true)
924 ········Optimistic····1········1········0
925 ········Pessimistic····0········1········0
926 ············
927 ········Deferred Mode············
928 ····················Query····Save····Save(true)
929 ········Optimistic····0········1········0
930 ········Pessimistic····0········1········0
931 ········ */
932
933 ········internal void CheckEndTransaction(bool doCommit)
934 ········{
935 ············if (doCommit)
936 ············{
937 ················TransactionScope.Complete();
938 ············}
939 ········}
940
941 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
942 ········{
943 ············CheckTransaction(handler, this.GetClass(t).Connection);
944 ········}
945
946 ········/// <summary>
947 ········/// Each and every database operation has to be preceded by a call to this function.
948 ········/// </summary>
949 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
950 ········{
951 ············TransactionScope.CheckTransaction();
952 ············
953 ············if (handler.Connection == null)
954 ············{
955 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
956 ················{
957 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
958 ····················string connStr = this.OnNewConnection( ndoConn );
959 ····················var connection = p.NewConnection( connStr );
960 ····················if (connection == null)
961 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
962 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
963 ····················return connection;
964 ················} );
965 ············}
966
967 ············if (TransactionMode != TransactionMode.None)
968 ············{
969 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
970 ············}
971
972 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
973 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
974 ············{
975 ················handler.Connection.Open();
976 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
977 ············}
978 ········}
979
980 ········/// <summary>
981 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
982 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
983 ········/// This is possible if there is a listener for the CollisionEvent.
984 ········/// Else we throw an exception.
985 ········/// </summary>
986 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
987 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
988 ········{
989 ············DataRow row = ex.Row;
990 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
991 ················throw(ex);
992 ············if (row.RowState == DataRowState.Detached)
993 ················return;
994 ············foreach (Cache.Entry e in cache.LockedObjects)
995 ············{
996 ················if (e.row == row)
997 ················{
998 ····················CollisionEvent(e.pc);
999 ····················return;
1000 ················}
1001 ············}
1002 ············throw ex;
1003 ········}
1004
1005
1006 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1007 ········{
1008 ············Class cl = GetClass(pc);
1009 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1010 ············Dictionary<string,MemberInfo> persistentFields = null;
1011 ············if (etypes.Length > 0)
1012 ············{
1013 ················FieldMap fm = new FieldMap(cl);
1014 ················persistentFields = fm.PersistentFields;
1015 ············}
1016 ············foreach(string s in etypes)
1017 ············{
1018 ················try
1019 ················{
1020 ····················NDO.Mapping.Field f = cl.FindField(s);
1021 ····················if (f == null)
1022 ························continue;
1023 ····················object o = row[f.Column.Name];
1024 ····················string[] arr = s.Split('.');
1025 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1026 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1027 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1028 ····················// Hole das Embedded Object
1029 ····················object parentOb = parentFi.GetValue(pc);
1030
1031 ····················if (parentOb == null)
1032 ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0]));
1033
1034 ····················// Suche darin das Feld mit Namen Arr[1]
1035
1036 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1037 ····················Type childType = childFi.FieldType;
1038
1039 ····················// Don't initialize value types, if DBNull is stored in the field.
1040 ····················// Exception: DateTime and Guid.
1041 ····················if (o == DBNull.Value && childType.IsValueType
1042 ························&& childType != typeof(Guid)
1043 ························&& childType != typeof(DateTime))
1044 ························continue;
1045
1046 ····················if (childType == typeof(DateTime))
1047 ····················{
1048 ························if (o == DBNull.Value)
1049 ····························o = DateTime.MinValue;
1050 ····················}
1051 ····················if (childType.IsClass)
1052 ····················{
1053 ························if (o == DBNull.Value)
1054 ····························o = null;
1055 ····················}
1056
1057 ····················if (childType == typeof (Guid))
1058 ····················{
1059 ························if (o == DBNull.Value)
1060 ····························o = Guid.Empty;
1061 ························if (o is string)
1062 ························{
1063 ····························childFi.SetValue(parentOb, new Guid((string)o));
1064 ························}
1065 ························else if (o is Guid)
1066 ························{
1067 ····························childFi.SetValue(parentOb, o);
1068 ························}
1069 ························else if (o is byte[])
1070 ························{
1071 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1072 ························}
1073 ························else
1074 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1075 ····················}
1076 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1077 ····················{
1078 ························object childOb = childFi.GetValue(parentOb);
1079 ························FieldInfo valueFi = childType.GetField("value__");
1080 ························valueFi.SetValue(childOb, o);
1081 ························childFi.SetValue(parentOb, childOb);
1082 ····················}
1083 ····················else
1084 ····················{
1085 ························childFi.SetValue(parentOb, o);
1086 ····················}
1087 ················}
1088 ················catch (Exception ex)
1089 ················{
1090 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1091
1092 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1093 ················}
1094
1095 ············}
1096 ············
1097 ············try
1098 ············{
1099 ················if (cl.HasEncryptedFields)
1100 ················{
1101 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1102 ····················{
1103 ························string name = field.Column.Name;
1104 ························string s = (string) row[name];
1105 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1106 ························row[name] = es;
1107 ····················}
1108 ················}
1109 ················pc.NDORead(row, fieldNames, startIndex);
1110 ············}
1111 ············catch (Exception ex)
1112 ············{
1113 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1114 ····················+ ex.Message);
1115 ············}
1116 ········}
1117
1118 ········/// <summary>
1119 ········/// Executes a sql script to generate the database tables.
1120 ········/// The function will execute any sql statements in the script
1121 ········/// which are valid according to the
1122 ········/// rules of the underlying database. Result sets are ignored.
1123 ········/// </summary>
1124 ········/// <param name="scriptFile">The script file to execute.</param>
1125 ········/// <param name="conn">A connection object, containing the connection
1126 ········/// string to the database, which should be altered.</param>
1127 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1128 ········/// <remarks>
1129 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1130 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1131 ········/// Their message property will appear in the result array.
1132 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1133 ········/// </remarks>
1134 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1135 ········{
1136 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1137 ········}
1138
1139 ········/// <summary>
1140 ········/// Executes a sql script to generate the database tables.
1141 ········/// The function will execute any sql statements in the script
1142 ········/// which are valid according to the
1143 ········/// rules of the underlying database. Result sets are ignored.
1144 ········/// </summary>
1145 ········/// <param name="scriptFile">The script file to execute.</param>
1146 ········/// <param name="conn">A connection object, containing the connection
1147 ········/// string to the database, which should be altered.</param>
1148 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1149 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1150 ········/// <remarks>
1151 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1152 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1153 ········/// Their message property will appear in the result array.
1154 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1155 ········/// </remarks>
1156 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1157 ········{
1158 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1159 ············string s = sr.ReadToEnd();
1160 ············sr.Close();
1161 ············string[] arr = s.Split(';');
1162 ············string last = arr[arr.Length - 1];
1163 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1164 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1165 ············using (var handler = GetSqlPassThroughHandler())
1166 ············{
1167 ················int i = 0;
1168 ················string ok = "OK";
1169 ················foreach (string statement in arr)
1170 ················{
1171 ····················if (statement != null && statement.Trim() != string.Empty)
1172 ····················{
1173 ························try
1174 ························{
1175 ····························handler.Execute(statement);
1176 ····························result[i] = ok;
1177 ························}
1178 ························catch (Exception ex)
1179 ························{
1180 ····························result[i] = ex.Message;
1181 ························}
1182 ····················}
1183 ····················i++;
1184 ················}
1185 ················CheckEndTransaction(true);
1186 ············}
1187 ············return result;
1188 ········}
1189
1190 ········/// <summary>
1191 ········/// Executes a sql script to generate the database tables.
1192 ········/// The function will execute any sql statements in the script
1193 ········/// which are valid according to the
1194 ········/// rules of the underlying database. Result sets are ignored.
1195 ········/// </summary>
1196 ········/// <param name="scriptFile">The script file to execute.</param>
1197 ········/// <returns></returns>
1198 ········/// <remarks>
1199 ········/// This function takes the first Connection object in the Connections list
1200 ········/// of the Mapping file und executes the script using that connection.
1201 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1202 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1203 ········/// Their message property will appear in the result array.
1204 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1205 ········/// </remarks>
1206 ········public string[] BuildDatabase(string scriptFile)
1207 ········{
1208 ············if (!File.Exists(scriptFile))
1209 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1210 ············if (!this.mappings.Connections.Any())
1211 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1212 ············Connection conn = new Connection( this.mappings );
1213 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1214 ············conn.Name = OnNewConnection( originalConnection );
1215 ············conn.Type = originalConnection.Type;
1216 ············//Connection conn = (Connection) this.mappings.Connections[0];
1217 ············return BuildDatabase(scriptFile, conn);
1218 ········}
1219
1220 ········/// <summary>
1221 ········/// Executes a sql script to generate the database tables.
1222 ········/// The function will execute any sql statements in the script
1223 ········/// which are valid according to the
1224 ········/// rules of the underlying database. Result sets are ignored.
1225 ········/// </summary>
1226 ········/// <returns>
1227 ········/// A string array, containing the error messages produced by the statements
1228 ········/// contained in the script.
1229 ········/// </returns>
1230 ········/// <remarks>
1231 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1232 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1233 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1234 ········/// This function takes the first Connection object in the Connections list
1235 ········/// of the Mapping file und executes the script using that connection.
1236 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1237 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1238 ········/// Their message property will appear in the result array.
1239 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1240 ········/// </remarks>
1241 ········public string[] BuildDatabase()
1242 ········{
1243 ············Assembly ass = Assembly.GetEntryAssembly();
1244 ············if (ass == null)
1245 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1246 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1247 ············return BuildDatabase(file);
1248 ········}
1249
1250 ········/// <summary>
1251 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1252 ········/// </summary>
1253 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1254 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1255 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1256 ········{
1257 ············if (!this.mappings.Connections.Any())
1258 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1259 ············if (conn == null)
1260 ············{
1261 ················conn = new Connection( this.mappings );
1262 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1263 ················conn.Name = OnNewConnection( originalConnection );
1264 ················conn.Type = originalConnection.Type;
1265 ············}
1266
1267 ············return new SqlPassThroughHandler( this, conn );
1268 ········}
1269
1270 ········/// <summary>
1271 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1272 ········/// </summary>
1273 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1274 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1275 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1276 ········{
1277 ············if (!this.mappings.Connections.Any())
1278 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1279 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1280 ············if (conn == null)
1281 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1282 ············return GetSqlPassThroughHandler( conn );
1283 ········}
1284
1285 ········/// <summary>
1286 ········/// Executes a xml script to generate the database tables.
1287 ········/// The function will generate and execute sql statements to perform
1288 ········/// the changes described by the xml.
1289 ········/// </summary>
1290 ········/// <returns></returns>
1291 ········/// <remarks>
1292 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1293 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1294 ········/// This function takes the first Connection object in the Connections list
1295 ········/// of the Mapping file und executes the script using that connection.
1296 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1297 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1298 ········/// Their message property will appear in the result string array.
1299 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1300 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1301 ········/// </remarks>
1302 ········public string[] PerformSchemaTransitions()
1303 ········{
1304 ············Assembly ass = Assembly.GetEntryAssembly();
1305 ············if (ass == null)
1306 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1307 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1308 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1309 ············if (fileNames.Count == 0)
1310 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1311 ············if (fileNames.Count > 1)
1312 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1313 ············return PerformSchemaTransitions( fileNames[0] );
1314 ········}
1315
1316
1317 ········/// <summary>
1318 ········/// Executes a xml script to generate the database tables.
1319 ········/// The function will generate and execute sql statements to perform
1320 ········/// the changes described by the xml.
1321 ········/// </summary>
1322 ········/// <param name="scriptFile">The script file to execute.</param>
1323 ········/// <returns></returns>
1324 ········/// <remarks>
1325 ········/// This function takes the first Connection object in the Connections list
1326 ········/// of the Mapping file und executes the script using that connection.
1327 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1328 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1329 ········/// Their message property will appear in the result string array.
1330 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1331 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1332 ········/// </remarks>
1333 ········public string[] PerformSchemaTransitions(string scriptFile)
1334 ········{
1335 ············if (!File.Exists(scriptFile))
1336 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1337
1338 ············if (!this.mappings.Connections.Any())
1339 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1340 ············Connection conn = new Connection( mappings );
1341 ············Connection originalConnection = mappings.Connections.First();
1342 ············conn.Name = OnNewConnection( originalConnection );
1343 ············conn.Type = originalConnection.Type;
1344 ············return PerformSchemaTransitions(scriptFile, conn);
1345 ········}
1346
1347
1348 ········int CompareFileName( string fn1, string fn2)
1349 ········{
1350 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1351 ············Match match = regex.Match( fn1 );
1352 ············string v1 = match.Groups[1].Value;
1353 ············match = regex.Match( fn2 );
1354 ············string v2 = match.Groups[1].Value;
1355 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1356 ········}
1357
1358 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1359 ········{
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············var resultList = new List<Guid>();
1362
1363 ············using (var handler = GetSqlPassThroughHandler())
1364 ············{
1365 ················string[] TableNames = provider.GetTableNames( connection );
1366 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1367 ················{
1368 string sql = "SELECT Id from NDOSchemaIds WHERE SchemaName ";
 
 
 
1369 ····················if (String.IsNullOrEmpty(schemaName))
1370 ························sql += "IS NULL;";
1371 ····················else
1372 ························sql += $"LIKE '{schemaName}'";
1373
1374 ····················using(IDataReader dr = handler.Execute(sql, true))
1375 ····················{
1376 ························while (dr.Read())
1377 ····························resultList.Add( dr.GetGuid( 0 ) );
1378 ····················}
1379 ················}
1380 ················else
1381 ················{
1382 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1383 ····················var gt = typeof(Guid);
1384 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1385 ····················var st = typeof(String);
1386 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1387 ····················var dt = typeof(DateTime);
1388 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1389 ····················string transition = $@"<NdoSchemaTransition>
1390 ····<CreateTable name=""NDOSchemaIds"">
1391 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1392 <CreateColumn name=""Id"" type=""{ gtype} "" size=""36"" />
1393 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1394 ····</CreateTable>
1395 </NdoSchemaTransition>";
1396 ····················XElement transitionElement = XElement.Parse(transition);
1397
1398 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1399 ····················handler.Execute(sql);
1400 ················}
 
1401 ············}
1402
1403 ············return resultList.ToArray();
1404 ········}
1405
1406 ········/// <summary>
1407 ········/// Executes a xml script to generate the database tables.
1408 ········/// The function will generate and execute sql statements to perform
1409 ········/// the changes described by the xml.
1410 ········/// </summary>
1411 ········/// <param name="scriptFile">The xml script file.</param>
1412 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1413 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1414 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1415 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1416 ········{
1417 ············string schemaName = null;
1418 ············// Gespeicherte Version ermitteln.
1419 ············XElement transitionElements = XElement.Load( scriptFile );
1420 ············if (transitionElements.Attribute( "schemaName" ) != null)
1421 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1422
1423 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1424 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1425 ············var newIds = new List<Guid>();
1426 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1427 ············MemoryStream ms = new MemoryStream();
1428 ············StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
1429 ············bool hasChanges = false;
1430
1431 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition"))
1432 ············{
1433 ················var id = transitionElement.Attribute("id")?.Value;
1434 ················if (id == null)
1435 ····················continue;
1436 ················var gid = new Guid(id);
1437 ················if (installedIds.Contains( gid ))
1438 ····················continue;
1439 ················hasChanges = true;
1440 sw. Write( schemaTransitionGenerator. Generate( transitionElement ) ) ;
1441 ················newIds.Add( gid );
1442 ············}
1443
1444 ············if (!hasChanges)
1445 ················return new string[] { };
1446
1447 ············// dtLiteral contains the leading and trailing quotes
1448 ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now );
1449
1450 foreach ( var id in newIds)
 
 
 
 
1451 ············{
1452 sw. WriteLine( $"INSERT INTO NDOSchemaIds ( 'SchemaName', 'Id', 'InsertTime') VALUES ( '{ schemaName} ', '{ id} ', { dtLiteral} ) ;" ) ;
1453 ············}
1454
1455 ············sw.Flush();
1456 ············ms.Position = 0L;
1457
1458 ············StreamReader sr = new StreamReader(ms, Encoding.UTF8);
1459 ············string s = sr.ReadToEnd();
1460 ············sr.Close();
1461
1462 ············return InternalPerformSchemaTransitions( ndoConn, s );
1463 ········}
1464
1465 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1466 ········{
1467 ············string[] arr = sql.Split( ';' );
 
1468 ············string last = arr[arr.Length - 1];
1469 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1470 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1471 ············int i = 0;
1472 ············string ok = "OK";
1473 ············using (var handler = GetSqlPassThroughHandler())
1474 ············{
 
 
1475 ················foreach (string statement in arr)
1476 ················{
1477 if ( statement != null && statement. Trim( ) != string. Empty)
1478 ····················{
1479 ························try
1480 ························{
1481 handler. Execute( statement ) ;
1482 ····························result[i] = ok;
1483 ························}
1484 ························catch (Exception ex)
1485 ························{
1486 ····························result[i] = ex.Message;
 
1487 ························}
1488 ····················}
1489 ····················i++;
1490 ················}
1491
1492 ················handler.CommitTransaction();
 
 
1493 ············}
 
1494 ············return result;
1495 ········}
1496 ········
1497 ········/// <summary>
1498 ········/// Transfers Data from the object to the DataRow
1499 ········/// </summary>
1500 ········/// <param name="pc"></param>
1501 ········/// <param name="row"></param>
1502 ········/// <param name="fieldNames"></param>
1503 ········/// <param name="startIndex"></param>
1504 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1505 ········{
1506 ············Class cl = GetClass( pc );
1507 ············try
1508 ············{
1509 ················pc.NDOWrite(row, fieldNames, startIndex);
1510 ················if (cl.HasEncryptedFields)
1511 ················{
1512 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1513 ····················{
1514 ························string name = field.Column.Name;
1515 ························string s = (string) row[name];
1516 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1517 ························row[name] = es;
1518 ····················}
1519 ················}
1520 ············}
1521 ············catch (Exception ex)
1522 ············{
1523 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1524 ····················+ ex.Message);
1525 ············}
1526
1527 ············if (cl.TypeNameColumn != null)
1528 ············{
1529 ················Type t = pc.GetType();
1530 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1531 ············}
1532
1533 ············var etypes = cl.EmbeddedTypes;
1534 ············foreach(string s in etypes)
1535 ············{
1536 ················try
1537 ················{
1538 ····················NDO.Mapping.Field f = cl.FindField(s);
1539 ····················if (f == null)
1540 ························continue;
1541 ····················string[] arr = s.Split('.');
1542 ····················// Suche Feld mit Namen arr[0] als object
1543 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1544 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1545 ····················Object parentOb = parentFi.GetValue(pc);
1546 ····················if (parentOb == null)
1547 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1548 ····················// Suche darin das Feld mit Namen Arr[1]
1549 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1550 ····················object o = childFi.GetValue(parentOb);
1551 ····················if (o == null
1552 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1553 ························|| o is Guid && (Guid) o == Guid.Empty)
1554 ························o = DBNull.Value;
1555 ····················row[f.Column.Name] = o;
1556 ················}
1557 ················catch (Exception ex)
1558 ················{
1559 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1560
1561 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1562 ················}
1563 ············}
1564 ········}
1565
1566 ········/// <summary>
1567 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1568 ········/// </summary>
1569 ········/// <param name="o">The parent object.</param>
1570 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1571 ········public virtual void LoadField(object o, int fieldOrdinal)
1572 ········{
1573 ············IPersistenceCapable pc = CheckPc(o);
1574 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1575 ············{
1576 ················LoadState ls = pc.NDOLoadState;
1577 ················if (ls.FieldLoadState != null)
1578 ················{
1579 ····················if (ls.FieldLoadState[fieldOrdinal])
1580 ························return;
1581 ················}
1582 ················else
1583 ················{
1584 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1585 ················}
1586 ················LoadData(o);
1587 ········}
1588 ········}
1589
1590 #pragma warning disable 419
1591 ········/// <summary>
1592 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1593 ········/// </summary>
1594 ········/// <param name="o">The hollow object.</param>
1595 ········/// <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>
1596 #pragma warning restore 419
1597 ········public virtual void LoadData( object o )
1598 ········{
1599 ············IPersistenceCapable pc = CheckPc(o);
1600 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1601 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1602 ················return;
1603 ············Class cl = GetClass(pc);
1604 ············IQuery q;
1605 ············q = CreateOidQuery(pc, cl);
1606 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1607
1608 ············var objects = q.Execute();
1609 ············var count = objects.Count;
1610
1611 ············if (count > 1)
1612 ············{
1613 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1614 ············}
1615 ············else if (count == 0)
1616 ············{
1617 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1618 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1619 ············}
1620 ········}
1621
1622 ········/// <summary>
1623 ········/// Creates a new IQuery object for the given type
1624 ········/// </summary>
1625 ········/// <param name="t"></param>
1626 ········/// <param name="oql"></param>
1627 ········/// <param name="hollow"></param>
1628 ········/// <param name="queryLanguage"></param>
1629 ········/// <returns></returns>
1630 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1631 ········{
1632 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1633 ············Type qt = template.MakeGenericType( t );
1634 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1635 ········}
1636
1637 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1638 ········{
1639 ············ArrayList parameters = new ArrayList();
1640 ············string oql = "oid = {0}";
1641 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1642 ············q.Parameters.Add( pc.NDOObjectId );
1643 ············q.AllowSubclasses = false;
1644 ············return q;
1645 ········}
1646
1647 ········/// <summary>
1648 ········/// Mark the object dirty. The current state is
1649 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1650 ········/// object is locked in the cache.
1651 ········/// </summary>
1652 ········/// <param name="pc"></param>
1653 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1654 ········{
1655 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1656 ················return;
1657 ············SaveObjectState(pc);
1658 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1659 ········}
1660
1661 ········/// <summary>
1662 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1663 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1664 ········/// This is done, to allow easy rollback later. Also, the
1665 ········/// object is locked in the cache.
1666 ········/// </summary>
1667 ········/// <param name="pc"></param>
1668 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1669 ········{
1670 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1671
1672 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1673 ············{
1674 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1675 ············}
1676
1677 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1678 ················LoadData(pc);
1679
1680 ············// state is either (Created), Persistent, PersistentDirty
1681 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1682 ············{
1683 ················MarkDirty(pc);
1684 ············}
1685 ········}
1686
1687
1688
1689 ········/// <summary>
1690 ········/// Save current object state in DS and lock the object in the cache.
1691 ········/// The saved state can be used later to retrieve the original object value if the
1692 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1693 ········/// </summary>
1694 ········/// <param name="pc">The object that should be saved</param>
1695 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1696 ········/// <remarks>
1697 ········/// In a data row there are the following things:
1698 ········/// Item································Responsible for writing
1699 ········/// State (own, inherited, embedded)····WriteObject
1700 ········/// TimeStamp····························NDOPersistenceHandler
1701 ········/// Oid····································WriteId
1702 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1703 ········/// </remarks>
1704 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1705 ········{
1706 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1707 ············
1708 ············DataTable table = GetTable(pc);
1709 ············DataRow row = table.NewRow();
1710 ············Class cl = GetClass(pc);
1711 ············WriteObject(pc, row, cl.ColumnNames, 0);
1712 ············WriteIdToRow(pc, row);
1713 ············if (!isDeleting)
1714 ················WriteLostForeignKeysToRow(cl, pc, row);
1715 ············table.Rows.Add(row);
1716 ············row.AcceptChanges();
1717 ············
1718 ············var relations = CollectRelationStates(pc);
1719 ············cache.Lock(pc, row, relations);
1720 ········}
1721
1722 ········private void SaveFakeRow(IPersistenceCapable pc)
1723 ········{
1724 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1725 ············
1726 ············DataTable table = GetTable(pc);
1727 ············DataRow row = table.NewRow();
1728 ············Class pcClass = GetClass(pc);
1729 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1730 ············Class cl = GetClass(pc);
1731 ············//WriteObject(pc, row, cl.FieldNames, 0);
1732 ············WriteIdToRow(pc, row);
1733 ············table.Rows.Add(row);
1734 ············row.AcceptChanges();
1735 ············
1736 ············cache.Lock(pc, row, null);
1737 ········}
1738
1739 ········/// <summary>
1740 ········/// This defines one column of the row, in which we use the
1741 ········/// ColumnError property to determine, if the row is a fake row.
1742 ········/// </summary>
1743 ········/// <param name="pcClass"></param>
1744 ········/// <returns></returns>
1745 ········private string GetFakeRowOidColumnName(Class pcClass)
1746 ········{
1747 ············// In case of several OidColumns the first column defined in the mapping
1748 ············// will be the one, holding the fake row info.
1749 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1750 ········}
1751
1752 ········private bool IsFakeRow(Class cl, DataRow row)
1753 ········{
1754 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1755 ········}
1756
1757 ········/// <summary>
1758 ········/// Make a list of objects persistent.
1759 ········/// </summary>
1760 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1761 ········public void MakePersistent(System.Collections.IList list)
1762 ········{
1763 ············foreach (IPersistenceCapable pc in list)
1764 ············{
1765 ················MakePersistent(pc);
1766 ············}
1767 ········}
1768
1769 ········/// <summary>
1770 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1771 ········/// not duplicated.
1772 ········/// </summary>
1773 ········/// <param name="pc">the parent object of all relations</param>
1774 ········/// <returns></returns>
1775 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1776 ········{
1777 ············// Save state of relations
1778 ············Class c = GetClass(pc);
1779 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1780 ············foreach(Relation r in c.Relations)
1781 ············{
1782 ················if (r.Multiplicity == RelationMultiplicity.Element)
1783 ················{
1784 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1785 ················}
1786 ················else
1787 ················{
1788 ····················IList l = mappings.GetRelationContainer(pc, r);
1789 ····················if(l != null)
1790 ····················{
1791 ························l = (IList) ListCloner.CloneList(l);
1792 ····················}
1793 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1794 ················}
1795 ············}
1796
1797 ············return relations;
1798 ········}
1799
1800
1801 ········/// <summary>
1802 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1803 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1804 ········/// the list had at the beginning of the transaction.
1805 ········/// </summary>
1806 ········/// <param name="pc"></param>
1807 ········/// <param name="relations"></param>
1808 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1809 ········{
1810 ············Class c = GetClass(pc);
1811
1812 ············foreach(var entry in relations)
1813 ············{
1814 ················var r = entry.Key;
1815 ················if (r.Multiplicity == RelationMultiplicity.Element)
1816 ················{
1817 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1818 ················}
1819 ················else
1820 ················{
1821 ····················if (pc.NDOGetLoadState(r.Ordinal))
1822 ····················{
1823 ························// Help GC by clearing lists
1824 ························IList l = mappings.GetRelationContainer(pc, r);
1825 ························if(l != null)
1826 ························{
1827 ····························l.Clear();
1828 ························}
1829 ························// Restore relation
1830 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1831 ····················}
1832 ················}
1833 ············}
1834 ········}
1835
1836
1837 ········/// <summary>
1838 ········/// Generates a query for related objects without mapping table.
1839 ········/// Note: this function can't be called in polymorphic scenarios,
1840 ········/// since they need a mapping table.
1841 ········/// </summary>
1842 ········/// <returns></returns>
1843 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1844 ········{
1845 ············// At this point of execution we know,
1846 ············// that the target type is not polymorphic and is not 1:1.
1847
1848 ············// We can't fetch these objects with an NDOql query
1849 ············// since this would require a relation in the opposite direction
1850
1851 ············IList relatedObjects;
1852 ············if (l != null)
1853 ················relatedObjects = l;
1854 ············else
1855 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1856
1857 ············Type t = r.ReferencedType;
1858 ············Class cl = GetClass( t );
1859 ············var provider = cl.Provider;
1860
1861 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1862 ············var relClass = GetClass( r.ReferencedType );
1863 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1864 ············sb.Append( " WHERE " );
1865 ············int i = 0;
1866 ············List<object> parameters = new List<object>();
1867 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1868 ·············· {
1869 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1870 ·················· sb.Append( " = {" );
1871 ·················· sb.Append(i);
1872 ·················· sb.Append( '}' );
1873 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1874 ·················· if (!isLastElement)
1875 ······················ sb.Append( " AND " );
1876 ·················· i++;
1877 ·············· } );
1878
1879 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1880 ············{
1881 ················sb.Append( " AND " );
1882 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1883 ················sb.Append( " = " );
1884 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1885 ············}
1886
1887 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1888
1889 ············foreach (var p in parameters)
1890 ············{
1891 ················q.Parameters.Add( p );
1892 ············}
1893
1894 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1895
1896 ············IList l2 = q.Execute();
1897
1898 ············foreach (object o in l2)
1899 ················relatedObjects.Add( o );
1900
1901 ············return relatedObjects;
1902 ········}
1903
1904
1905 ········/// <summary>
1906 ········/// Resolves an relation. The loaded objects will be hollow.
1907 ········/// </summary>
1908 ········/// <param name="o">The parent object.</param>
1909 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1910 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1911 ········/// <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>
1912 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1913 ········{
1914 ············IPersistenceCapable pc = CheckPc(o);
1915 ············LoadRelationInternal(pc, fieldName, hollow);
1916 ········}
1917
1918 ········
1919
1920 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1921 ········{
1922 ············IList result = null;
1923
1924 ············if (pc.NDOObjectState == NDOObjectState.Created)
1925 ················return null;
1926
1927 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1928 ················LoadData(pc);
1929
1930 ············if(r.MappingTable == null)
1931 ············{
1932 ················// 1:1 are loaded with LoadData
1933 ················if (r.Multiplicity == RelationMultiplicity.List)
1934 ················{
1935 ····················// Help GC by clearing lists
1936 ····················IList l = mappings.GetRelationContainer(pc, r);
1937 ····················if(l != null)
1938 ························l.Clear();
1939 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1940 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1941 ····················result = relatedObjects;
1942 ················}
1943 ············}
1944 ············else
1945 ············{
1946 ················DataTable dt = null;
1947
1948 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1949 ················{
1950 ····················CheckTransaction( handler, r.MappingTable.Connection );
1951 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1952 ················}
1953
1954 ················IList relatedObjects;
1955 ················if(r.Multiplicity == RelationMultiplicity.Element)
1956 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1957 ················else
1958 ················{
1959 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1960 ····················if(relatedObjects != null)
1961 ························relatedObjects.Clear();··// Objects will be reread
1962 ····················else
1963 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1964 ················}
1965 ····················
1966 ················foreach(DataRow objRow in dt.Rows)
1967 ················{
1968 ····················Type relType;
1969
1970 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1971 ····················{
1972 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1973 ························if (typeCodeObj is System.DBNull)
1974 ····························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() ) );
1975 ························relType = typeManager[(int)typeCodeObj];
1976 ························if (relType == null)
1977 ····························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));
1978 ····················}························
1979 ····················else
1980 ····················{
1981 ························relType = r.ReferencedType;
1982 ····················}
1983
1984 ····················//TODO: Generic Types: Exctract the type description from the type name column
1985 ····················if (relType.IsGenericTypeDefinition)
1986 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1987 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1988 ····················IPersistenceCapable relObj = FindObject(id);
1989 ····················relatedObjects.Add(relObj);
1990 ················}····
1991 ················if (r.Multiplicity == RelationMultiplicity.Element)
1992 ················{
1993 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
1994 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
1995 ················}
1996 ················else
1997 ················{
1998 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1999 ····················result = relatedObjects;
2000 ················}
2001 ············}
2002 ············// Mark relation as loaded
2003 ············pc.NDOSetLoadState(r.Ordinal, true);
2004 ············return result;
2005 ········}
2006
2007 ········/// <summary>
2008 ········/// Loads elements of a relation
2009 ········/// </summary>
2010 ········/// <param name="pc">The object which needs to load the relation</param>
2011 ········/// <param name="relationName">The name of the relation</param>
2012 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2013 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2014 ········{
2015 ············if (pc.NDOObjectState == NDOObjectState.Created)
2016 ················return null;
2017 ············Class cl = GetClass(pc);
2018
2019 ············Relation r = cl.FindRelation(relationName);
2020
2021 ············if ( r == null )
2022 ················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 ) );
2023
2024 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2025 ················return null;
2026
2027 ············return LoadRelation(pc, r, hollow);
2028 ········}
2029
2030 ········/// <summary>
2031 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2032 ········/// a list of objects that has been read from the DB.
2033 ········/// </summary>
2034 ········/// <param name="pc">The parent object of the relations</param>
2035 ········/// <param name="row">A data row containing the state of the object</param>
2036 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2037 ········{
2038 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2039 ············Class cl = GetClass(pc);
2040 ············foreach(Relation r in cl.Relations)
2041 ············{
2042 ················if(r.MappingTable == null)
2043 ················{
2044 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2045 ····················{
2046 ························bool isNull = false;
2047 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2048 ························{
2049 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2050 ························}
2051 ························if (isNull)
2052 ························{
2053 ····························mappings.SetRelationField(pc, r.FieldName, null);
2054 ························}
2055 ························else
2056 ························{
2057 ····························Type relType;
2058 ····························if (r.HasSubclasses)
2059 ····························{
2060 ································object o = row[r.ForeignKeyTypeColumnName];
2061 ································if (o == DBNull.Value)
2062 ····································throw new NDOException(75, String.Format(
2063 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2064 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2065 ································relType = typeManager[(int)o];
2066 ····························}
2067 ····························else
2068 ····························{
2069 ································relType = r.ReferencedType;
2070 ····························}
2071 ····························if (relType == null)
2072 ····························{
2073 ································throw new NDOException(75, String.Format(
2074 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2075 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2076 ····························}
2077 ····
2078 ····························int count = r.ForeignKeyColumns.Count();
2079 ····························object[] keydata = new object[count];
2080 ····························int i = 0;
2081 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2082 ····························{
2083 ································keydata[i++] = row[fkColumn.Name];
2084 ····························}
2085
2086 ····························Type oidType = relType;
2087 ····························if (oidType.IsGenericTypeDefinition)
2088 ································oidType = mappings.GetRelationFieldType(r);
2089
2090 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2091 ····························if(childOid.IsValid())
2092 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2093 ····························else
2094 ································mappings.SetRelationField(pc, r.FieldName, null);
2095
2096 ························}
2097 ························pc.NDOSetLoadState(r.Ordinal, true);
2098 ····················}
2099 ················}
2100 ············}
2101 ········}
2102
2103 ········
2104 ········/// <summary>
2105 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2106 ········/// </summary>
2107 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2108 ········/// <param name="t">The type of the object, the id will belong to.</param>
2109 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2110 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2111 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2112 ········{
2113 ············return new ObjectId(oid.Id, t);
2114 ········}
2115
2116 ········/*
2117 ········/// <summary>
2118 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2119 ········/// </summary>
2120 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2121 ········/// <param name="t">The type of the object.</param>
2122 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2123 ········/// <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>
2124 ········public ObjectId NewObjectId(object keyData, Type t)
2125 ········{
2126 ············if (keyData == null || keyData == DBNull.Value)
2127 ················return ObjectId.InvalidId;
2128
2129 ············Class cl =··GetClass(t);············
2130 ············
2131 ············if (cl.Oid.FieldType == typeof(int))
2132 ················return new ObjectId(new Int32Key(t, (int)keyData));
2133 ············else if (cl.Oid.FieldType == typeof(string))
2134 ················return new ObjectId(new StringKey(t, (String) keyData));
2135 ············else if (cl.Oid.FieldType == typeof(Guid))
2136 ················if (keyData is string)
2137 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2138 ················else
2139 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2140 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2141 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2142 ············else
2143 ················return ObjectId.InvalidId;
2144 ········}
2145 ········*/
2146
2147
2148 ········/*
2149 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2150 ····················{
2151 ························// The column, which hold the oid gets overwritten, if
2152 ························// Oid.FieldName contains a value.
2153 */
2154 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2155 ········{
2156 ············Class cl = GetClass(pc);
2157
2158 ············if (cl.Oid.IsDependent)
2159 ················return;
2160
2161 ············Key key = pc.NDOObjectId.Id;
2162
2163 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2164 ············{
2165 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2166 ················if (oidColumn.FieldName != null)
2167 ················{
2168 ····················row[oidColumn.Name] = key[i];
2169 ················}
2170 ············}
2171 ········}
2172
2173 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2174 ········{
2175 ············NDO.Mapping.Class cl = GetClass(pc);
2176 ············ObjectId oid = pc.NDOObjectId;
2177
2178 ············if (cl.TimeStampColumn != null)
2179 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2180
2181 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2182 ················return;
2183
2184 ············Key key = oid.Id;
2185
2186 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2187 ············{
2188 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2189 ················row[oidColumn.Name] = key[i];
2190 ············}
2191 ········}
2192
2193 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2194 ········{
2195 ············ObjectId oid = pc.NDOObjectId;
2196 ············NDO.Mapping.Class cl = GetClass(pc);
2197
2198 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2199 ················return;
2200
2201 ············Key key = oid.Id;
2202
2203 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2204 ············{
2205 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2206 ················object o = row[oidColumn.Name];
2207 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2208 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2209 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2210 ····················key[i] = new Guid((string)o);
2211 ················else
2212 ····················key[i] = o;
2213 ············}
2214
2215 ········}
2216
2217 ········private void ReadId (Cache.Entry e)
2218 ········{
2219 ············ReadIdFromRow(e.pc, e.row);
2220 ········}
2221
2222 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2223 ········{
2224 ············WriteObject(pc, row, fieldNames, 0);
2225 ········}
2226
2227 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2228 ········{
2229 ············if (cl.TimeStampColumn == null)
2230 ················return;
2231 ············object col = row[cl.TimeStampColumn];
2232 ············if (col is String)
2233 ················pc.NDOTimeStamp = new Guid(col.ToString());
2234 ············else
2235 ················pc.NDOTimeStamp = (Guid) col;
2236 ········}
2237
2238
2239
2240 ········private void ReadTimeStamp(Cache.Entry e)
2241 ········{
2242 ············IPersistenceCapable pc = e.pc;
2243 ············NDO.Mapping.Class cl = GetClass(pc);
2244 ············Debug.Assert(!IsFakeRow(cl, e.row));
2245 ············if (cl.TimeStampColumn == null)
2246 ················return;
2247 ············if (e.row[cl.TimeStampColumn] is String)
2248 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2249 ············else
2250 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2251 ········}
2252
2253 ········/// <summary>
2254 ········/// Determines, if any objects are new, changed or deleted.
2255 ········/// </summary>
2256 ········public virtual bool HasChanges
2257 ········{
2258 ············get
2259 ············{
2260 ················return cache.LockedObjects.Count > 0;
2261 ············}
2262 ········}
2263
2264 ········/// <summary>
2265 ········/// Do the update for all rows in the ds.
2266 ········/// </summary>
2267 ········/// <param name="types">Types with changes.</param>
2268 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2269 ········/// <remarks>
2270 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2271 ········/// </remarks>
2272 ········private void UpdateTypes(IList types, bool delete)
2273 ········{
2274 ············foreach(Type t in types)
2275 ············{
2276 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2277 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2278 ················{
2279 ····················CheckTransaction( handler, t );
2280 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2281 ····················handler.ConcurrencyError += ceh;
2282 ····················try
2283 ····················{
2284 ························DataTable dt = GetTable(t);
2285 ························if (delete)
2286 ····························handler.UpdateDeletedObjects( dt );
2287 ························else
2288 ····························handler.Update( dt );
2289 ····················}
2290 ····················finally
2291 ····················{
2292 ························handler.ConcurrencyError -= ceh;
2293 ····················}
2294 ················}
2295 ············}
2296 ········}
2297
2298 ········internal void UpdateCreatedMappingTableEntries()
2299 ········{
2300 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2301 ············{
2302 ················if (!e.DeleteEntry)
2303 ····················WriteMappingTableEntry(e);
2304 ············}
2305 ············// Now update all mapping tables
2306 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2307 ············{
2308 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2309 ················handler.Update(ds);
2310 ············}
2311 ········}
2312
2313 ········internal void UpdateDeletedMappingTableEntries()
2314 ········{
2315 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2316 ············{
2317 ················if (e.DeleteEntry)
2318 ····················WriteMappingTableEntry(e);
2319 ············}
2320 ············// Now update all mapping tables
2321 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2322 ············{
2323 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2324 ················handler.Update(ds);
2325 ············}
2326 ········}
2327
2328 ········/// <summary>
2329 ········/// Save all changed object into the DataSet and update the DB.
2330 ········/// When a newly created object is written to DB, the key might change. Therefore,
2331 ········/// the id is updated and the object is removed and re-inserted into the cache.
2332 ········/// </summary>
2333 ········public virtual void Save(bool deferCommit = false)
2334 ········{
2335 ············this.DeferredMode = deferCommit;
2336 ············var htOnSaving = new HashSet<ObjectId>();
2337 ············for(;;)
2338 ············{
2339 ················// We need to work on a copy of the locked objects list,
2340 ················// since the handlers might add more objects to the cache
2341 ················var lockedObjects = cache.LockedObjects.ToList();
2342 ················int count = lockedObjects.Count;
2343 ················foreach(Cache.Entry e in lockedObjects)
2344 ················{
2345 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2346 ····················{
2347 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2348 ························if (ipn != null)
2349 ························{
2350 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2351 ····························{
2352 ································ipn.OnSaving();
2353 ································htOnSaving.Add(e.pc.NDOObjectId);
2354 ····························}
2355 ························}
2356 ····················}
2357 ················}
2358 ················// The system is stable, if the count doesn't change
2359 ················if (cache.LockedObjects.Count == count)
2360 ····················break;
2361 ············}
2362
2363 ············if (this.OnSavingEvent != null)
2364 ············{
2365 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2366 ················foreach(Cache.Entry e in cache.LockedObjects)
2367 ····················onSavingObjects.Add(e.pc);
2368 ················OnSavingEvent(onSavingObjects);
2369 ············}
2370
2371 ············List<Type> types = new List<Type>();
2372 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2373 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2374 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2375 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2376 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2377
2378 ············// Save current state in DataSet
2379 ············foreach (Cache.Entry e in cache.LockedObjects)
2380 ············{
2381 ················Type objType = e.pc.GetType();
2382
2383 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2384 ····················objType = objType.GetGenericTypeDefinition();
2385
2386 ················Class cl = GetClass(e.pc);
2387 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2388 ················if(!types.Contains(objType))
2389 ················{
2390 ····················//Debug.WriteLine("Added··type " + objType.Name);
2391 ····················types.Add(objType);
2392 ················}
2393 ················NDOObjectState objectState = e.pc.NDOObjectState;
2394 ················if(objectState == NDOObjectState.Deleted)
2395 ················{
2396 ····················deletedObjects.Add(e.pc);
2397 ················}
2398 ················else if(objectState == NDOObjectState.Created)
2399 ················{
2400 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2401 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2402 ····················WriteForeignKeysToRow(e.pc, e.row);
2403 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2404 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2405 ····················//····················i = 0;
2406 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2407 ····················//····················{
2408 ····················//························rows[i++] = e2.row;
2409 ····················//····················}
2410 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2411 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2412
2413 ····················addedCacheEntries.Add(e);
2414 ····················addedObjects.Add( e.pc );
2415 ····················//····················objectState = NDOObjectState.Persistent;
2416 ················}
2417 ················else
2418 ················{
2419 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2420 ························changedObjects.Add( e.pc );
2421 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2422 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2423 ················}
2424 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2425 ················{
2426 ····················hollowModeObjects.Add(e.pc);
2427 ················}
2428 ············}
2429
2430 ············// Before we delete any db rows, we have to make sure, to delete mapping
2431 ············// table entries first, which might have relations to the db rows to be deleted
2432 ············UpdateDeletedMappingTableEntries();
2433
2434 ············// Update DB
2435 ············if (ds.HasChanges())
2436 ············{
2437
2438 ················// We need the reversed update order for deletions.
2439 ················types.Sort( ( t1, t2 ) =>
2440 ················{
2441 ····················int i1 = mappings.GetUpdateOrder( t1 );
2442 ····················int i2 = mappings.GetUpdateOrder( t2 );
2443 ····················if (i1 < i2)
2444 ····················{
2445 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2446 ····························i1 += 100000;
2447 ····················}
2448 ····················else
2449 ····················{
2450 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2451 ····························i2 += 100000;
2452 ····················}
2453 ····················return i2 - i1;
2454 ················} );
2455
2456 ················// Delete records first
2457
2458 ················UpdateTypes(types, true);
2459
2460 ················// Now do all other updates in correct order.
2461 ················types.Reverse();
2462
2463 ················UpdateTypes(types, false);
2464 ················
2465 ················ds.AcceptChanges();
2466 ················if(createdDirectObjects.Count > 0)
2467 ················{
2468 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2469 ····················// They must be written again to store the correct foreign keys.
2470 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2471 ····················{
2472 ························Class cl = GetClass(pc);
2473 ························DataRow r = this.cache.GetDataRow(pc);
2474 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2475 ························object o = r[fakeColumnName];
2476 ························r[fakeColumnName] = o;
2477 ····················}
2478
2479 ····················UpdateTypes(types, false);
2480 ················}
2481
2482 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2483 ················foreach(Cache.Entry e in addedCacheEntries)
2484 ················{
2485 ····················cache.DeregisterLockedObject(e.pc);
2486 ····················ReadId(e);
2487 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2488 ················}
2489
2490 ················// Now update all mapping tables. Because of possible subclasses, there is no
2491 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2492 ················// tables only after all other objects have been written to ensure correct foreign keys.
2493 ················UpdateCreatedMappingTableEntries();
2494
2495 ················// The rows may contain now new Ids, which should be
2496 ················// stored in the lostRowInfo's before the rows get detached
2497 ················foreach(Cache.Entry e in cache.LockedObjects)
2498 ················{
2499 ····················if (e.row.RowState != DataRowState.Detached)
2500 ····················{
2501 ························IPersistenceCapable pc = e.pc;
2502 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2503 ····················}
2504 ················}
2505
2506 ················ds.AcceptChanges();
2507 ············}
2508
2509 ············EndSave(!deferCommit);
2510
2511 ············foreach(IPersistenceCapable pc in deletedObjects)
2512 ············{
2513 ················MakeObjectTransient(pc, false);
2514 ············}
2515
2516 ············ds.Clear();
2517 ············mappingHandler.Clear();
2518 ············createdDirectObjects.Clear();
2519 ············createdMappingTableObjects.Clear();
2520 ············this.relationChanges.Clear();
2521
2522 ············if(hollowMode)
2523 ············{
2524 ················MakeHollow(hollowModeObjects);
2525 ············}
2526
2527 ············if (this.OnSavedEvent != null)
2528 ············{
2529 ················AuditSet auditSet = new AuditSet()
2530 ················{
2531 ····················ChangedObjects = changedObjects,
2532 ····················CreatedObjects = addedObjects,
2533 ····················DeletedObjects = deletedObjects
2534 ················};
2535 ················this.OnSavedEvent( auditSet );
2536 ············}
2537 ········}
2538
2539 ········private void EndSave(bool forceCommit)
2540 ········{
2541 ············foreach(Cache.Entry e in cache.LockedObjects)
2542 ············{
2543 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2544 ····················this.ReadTimeStamp(e);
2545 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2546 ············}
2547
2548 ············cache.UnlockAll();
2549
2550 ············CheckEndTransaction(forceCommit);
2551 ········}
2552
2553 ········/// <summary>
2554 ········/// Write all foreign keys for 1:1-relations.
2555 ········/// </summary>
2556 ········/// <param name="pc">The persistent object.</param>
2557 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2558 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2559 ········{
2560 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2561 ············{
2562 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2563 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2564
2565 ················if ( relObj != null )
2566 ················{
2567 ····················int i = 0;
2568 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2569 ····················{
2570 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2571 ····················}
2572 ····················if ( r.ForeignKeyTypeColumnName != null )
2573 ····················{
2574 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2575 ····················}
2576 ················}
2577 ················else
2578 ················{
2579 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2580 ····················{
2581 ························pcRow[fkColumn.Name] = DBNull.Value;
2582 ····················}
2583 ····················if ( r.ForeignKeyTypeColumnName != null )
2584 ····················{
2585 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2586 ····················}
2587 ················}
2588 ············}
2589 ········}
2590
2591
2592
2593 ········/// <summary>
2594 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2595 ········/// </summary>
2596 ········/// <param name="e">the mapping table entry</param>
2597 ········private void WriteMappingTableEntry(MappingTableEntry e)
2598 ········{
2599 ············IPersistenceCapable pc = e.ParentObject;
2600 ············IPersistenceCapable relObj = e.RelatedObject;
2601 ············Relation r = e.Relation;
2602 ············DataTable dt = GetTable(r.MappingTable.TableName);
2603 ············DataRow row = dt.NewRow();
2604 ············int i = 0;
2605 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2606 ············{
2607 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2608 ············}
2609 ············i = 0;
2610 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2611 ············{
2612 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2613 ············}
2614
2615 ············if (r.ForeignKeyTypeColumnName != null)
2616 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2617 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2618 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2619
2620 ············dt.Rows.Add(row);
2621 ············if(e.DeleteEntry)
2622 ············{
2623 ················row.AcceptChanges();
2624 ················row.Delete();
2625 ············}
2626
2627 ············IMappingTableHandler handler;
2628 ············if (!mappingHandler.TryGetValue( r, out handler ))
2629 ············{
2630 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2631 ············}
2632 ········}
2633
2634
2635 ········/// <summary>
2636 ········/// Undo changes of a certain object
2637 ········/// </summary>
2638 ········/// <param name="o">Object to undo</param>
2639 ········public void Restore(object o)
2640 ········{············
2641 ············IPersistenceCapable pc = CheckPc(o);
2642 ············Cache.Entry e = null;
2643 ············foreach (Cache.Entry entry in cache.LockedObjects)
2644 ············{
2645 ················if (entry.pc == pc)
2646 ················{
2647 ····················e = entry;
2648 ····················break;
2649 ················}
2650 ············}
2651 ············if (e == null)
2652 ················return;
2653 ············Class cl = GetClass(e.pc);
2654 ············switch (pc.NDOObjectState)
2655 ············{
2656 ················case NDOObjectState.PersistentDirty:
2657 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2658 ····················foreach(Relation r in cl.Relations)
2659 ····················{
2660 ························if (r.Multiplicity == RelationMultiplicity.Element)
2661 ························{
2662 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2663 ····························if (subPc != null && cache.IsLocked(subPc))
2664 ································Restore(subPc);
2665 ························}
2666 ························else
2667 ························{
2668 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2669 ································continue;
2670 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2671 ····························if (subList != null)
2672 ····························{
2673 ································foreach(IPersistenceCapable subPc2 in subList)
2674 ································{
2675 ····································if (cache.IsLocked(subPc2))
2676 ········································Restore(subPc2);
2677 ································}
2678 ····························}
2679 ························}
2680 ····················}
2681 ····················RestoreRelatedObjects(pc, e.relations);
2682 ····················e.row.RejectChanges();
2683 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2684 ····················cache.Unlock(pc);
2685 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2686 ····················break;
2687 ················case NDOObjectState.Created:
2688 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2689 ····················cache.Unlock(pc);
2690 ····················MakeObjectTransient(pc, true);
2691 ····················break;
2692 ················case NDOObjectState.Deleted:
2693 ····················if (!this.IsFakeRow(cl, e.row))
2694 ····················{
2695 ························e.row.RejectChanges();
2696 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2697 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2698 ····················}
2699 ····················else
2700 ····················{
2701 ························e.row.RejectChanges();
2702 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2703 ····················}
2704 ····················cache.Unlock(pc);
2705 ····················break;
2706
2707 ············}
2708 ········}
2709
2710 ········/// <summary>
2711 ········/// Aborts a pending transaction without restoring the object state.
2712 ········/// </summary>
2713 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2714 ········public virtual void AbortTransaction()
2715 ········{
2716 ············TransactionScope.Dispose();
2717 ········}
2718
2719 ········/// <summary>
2720 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2721 ········/// </summary>
2722 ········public virtual void Abort()
2723 ········{
2724 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2725 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2726 ············// individual row.
2727 ············createdDirectObjects.Clear();
2728 ············createdMappingTableObjects.Clear();
2729 ············ArrayList deletedObjects = new ArrayList();
2730 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2731
2732 ············// Read all objects from DataSet
2733 ············foreach (Cache.Entry e in cache.LockedObjects)
2734 ············{
2735 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2736
2737 ················Class cl = GetClass(e.pc);
2738 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2739 ················if (!isFakeRow)
2740 ················{
2741 ····················RestoreRelatedObjects(e.pc, e.relations);
2742 ················}
2743 ················else
2744 ················{
2745 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2746 ················}
2747
2748 ················switch(e.pc.NDOObjectState)
2749 ················{
2750 ····················case NDOObjectState.Created:
2751 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2752 ························deletedObjects.Add(e.pc);
2753 ························break;
2754
2755 ····················case NDOObjectState.PersistentDirty:
2756 ························e.row.RejectChanges();
2757 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2758 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2759 ························break;
2760
2761 ····················case NDOObjectState.Deleted:
2762 ························if (!isFakeRow)
2763 ························{
2764 ····························e.row.RejectChanges();
2765 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2766 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2767 ························}
2768 ························else
2769 ························{
2770 ····························e.row.RejectChanges();
2771 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2772 ························}
2773 ························break;
2774
2775 ····················default:
2776 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2777 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2778 ························//break;
2779 ················}
2780 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2781 ················{
2782 ····················hollowModeObjects.Add(e.pc);
2783 ················}
2784 ············}
2785 ············cache.UnlockAll();
2786 ············foreach(IPersistenceCapable pc in deletedObjects)
2787 ············{
2788 ················MakeObjectTransient(pc, true);
2789 ············}
2790 ············ds.Clear();
2791 ············mappingHandler.Clear();
2792 ············if(hollowMode)
2793 ············{
2794 ················MakeHollow(hollowModeObjects);
2795 ············}
2796
2797 ············this.relationChanges.Clear();
2798
2799
2800 ············AbortTransaction();
2801 ········}
2802
2803
2804 ········/// <summary>
2805 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2806 ········/// disable future access.
2807 ········/// </summary>
2808 ········/// <param name="pc"></param>
2809 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2810 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2811 ········{
2812 ············cache.Deregister(pc);
2813 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2814 ············if(removeId)
2815 ············{
2816 ················pc.NDOObjectId = null;
2817 ············}
2818 ············pc.NDOObjectState = NDOObjectState.Transient;
2819 ············pc.NDOStateManager = null;
2820 ········}
2821
2822 ········/// <summary>
2823 ········/// Makes an object transient.
2824 ········/// The object can be used afterwards, but changes will not be saved in the database.
2825 ········/// </summary>
2826 ········/// <remarks>
2827 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2828 ········/// </remarks>
2829 ········/// <param name="o">The object to detach.</param>
2830 ········public void MakeTransient(object o)
2831 ········{
2832 ············IPersistenceCapable pc = CheckPc(o);
2833 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2834 ············{
2835 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2836 ············}
2837
2838 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2839 ············{
2840 ················LoadData(pc);
2841 ············}
2842 ············MakeObjectTransient(pc, true);
2843 ········}
2844
2845
2846 ········/// <summary>
2847 ········/// Make all objects of a list transient.
2848 ········/// </summary>
2849 ········/// <param name="list">the list of transient objects</param>
2850 ········public void MakeTransient(System.Collections.IList list)
2851 ········{
2852 ············foreach (IPersistenceCapable pc in list)
2853 ················MakeTransient(pc);
2854 ········}
2855
2856 ········/// <summary>
2857 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2858 ········/// </summary>
2859 ········/// <param name="o">The object to remove</param>
2860 ········public void Delete(object o)
2861 ········{
2862 ············IPersistenceCapable pc = CheckPc(o);
2863 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2864 ············{
2865 ················throw new NDOException( 120, "Can't delete transient object" );
2866 ············}
2867 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2868 ············{
2869 ················Delete(pc, true);
2870 ············}
2871 ········}
2872
2873
2874 ········private void LoadAllRelations(object o)
2875 ········{
2876 ············IPersistenceCapable pc = CheckPc(o);
2877 ············Class cl = GetClass(pc);
2878 ············foreach(Relation r in cl.Relations)
2879 ············{
2880 ················if (!pc.NDOGetLoadState(r.Ordinal))
2881 ····················LoadRelation(pc, r, true);
2882 ············}
2883 ········}
2884
2885
2886 ········/// <summary>
2887 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2888 ········/// </summary>
2889 ········/// <remarks>
2890 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2891 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2892 ········/// the parent object.
2893 ········/// </remarks>
2894 ········/// <param name="pc">the object to remove</param>
2895 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2896 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2897 ········{
2898 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2899 ············//Debug.Indent();
2900 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2901 ············if (idn != null)
2902 ················idn.OnDelete();
2903
2904 ············LoadAllRelations(pc);
2905 ············DeleteRelatedObjects(pc, checkAssoziations);
2906
2907 ············switch(pc.NDOObjectState)
2908 ············{
2909 ················case NDOObjectState.Transient:
2910 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2911
2912 ················case NDOObjectState.Created:
2913 ····················DataRow row = cache.GetDataRow(pc);
2914 ····················row.Delete();
2915 ····················ArrayList cdosToDelete = new ArrayList();
2916 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2917 ························if ((object)cdo == (object)pc)
2918 ····························cdosToDelete.Add(cdo);
2919 ····················foreach (object o in cdosToDelete)
2920 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2921 ····················MakeObjectTransient(pc, true);
2922 ····················break;
2923 ················case NDOObjectState.Persistent:
2924 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2925 ························SaveObjectState(pc, true);
2926 ····················row = cache.GetDataRow(pc);
2927 ····················row.Delete();
2928 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2929 ····················break;
2930 ················case NDOObjectState.Hollow:
2931 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2932 ························SaveFakeRow(pc);
2933 ····················row = cache.GetDataRow(pc);
2934 ····················row.Delete();
2935 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2936 ····················break;
2937
2938 ················case NDOObjectState.PersistentDirty:
2939 ····················row = cache.GetDataRow(pc);
2940 ····················row.Delete();
2941 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2942 ····················break;
2943
2944 ················case NDOObjectState.Deleted:
2945 ····················break;
2946 ············}
2947
2948 ············//Debug.Unindent();
2949 ········}
2950
2951
2952 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2953 ········{
2954 ············MappingTableEntry mte = null;
2955 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2956 ············{
2957 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2958 ················{
2959 ····················mte = e;
2960 ····················break;
2961 ················}
2962 ············}
2963
2964 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2965 ············{
2966 ················if (mte != null)
2967 ····················createdMappingTableObjects.Remove(mte);
2968 ············}
2969 ············else
2970 ············{
2971 ················if (mte == null)
2972 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2973 ············}
2974 ········}
2975
2976 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2977 ········{
2978 ············// Two tasks: a) Null the foreign key
2979 ············//··············b) remove the element from the foreign container
2980
2981 ············if (!r.Bidirectional)
2982 ················return;
2983
2984 ············// this keeps the oid valid
2985 ············if (GetClass(child.GetType()).Oid.IsDependent)
2986 ················return;
2987
2988 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2989 ············{
2990 ················LoadAndMarkDirty(child);
2991 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2992 ············}
2993 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
2994 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
2995 ············{
2996 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
2997 ····················LoadRelation(child, r.ForeignRelation, true);
2998 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
2999 ················if (l == null)
3000 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3001 ················ObjectListManipulator.Remove(l, pc);
3002 ················// Don't need to delete the mapping table entry, because that was done
3003 ················// through the parent.
3004 ············}
3005 ········}
3006
3007 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3008 ········{
3009 ············// 1) Element····nomap····ass
3010 ············// 2) Element····nomap····comp
3011 ············// 3) Element····map········ass
3012 ············// 4) Element····map········comp
3013 ············// 5) List········nomap····ass
3014 ············// 6) List········nomap····comp
3015 ············// 7) List········map········ass
3016 ············// 8) List········map········comp
3017
3018 ············// Two tasks: Null foreign key and, if Composition, delete the child
3019
3020 ············// If Mapping Table, delete the entry - 3,7
3021 ············// If List and assoziation, null the foreign key in the foreign class - 5
3022 ············// If Element, null the foreign key in the own class 1,2,3,4
3023 ············// If composition, delete the child 2,4,6,8
3024
3025 ············// If the relObj is newly created
3026 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3027 ············Class childClass = GetClass(child);
3028
3029 ············if (r.MappingTable != null)··// 3,7
3030 ············{················
3031 ················DeleteMappingTableEntry(pc, r, child);
3032 ············}
3033 ············else if (r.Multiplicity == RelationMultiplicity.List
3034 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3035 ············{················
3036 ················LoadAndMarkDirty(child);
3037 ················DataRow row = this.cache.GetDataRow(child);
3038 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3039 ················{
3040 ····················row[fkColumnn.Name] = DBNull.Value;
3041 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3042 ················}
3043 ············}
3044 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3045 ············{
3046 ················LoadAndMarkDirty(pc);
3047 ············}
3048 ············if (r.Composition || childClass.Oid.IsDependent)
3049 ············{
3050 #if DEBUG
3051 ················if (child.NDOObjectState == NDOObjectState.Transient)
3052 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3053 #endif
3054 ················// Deletes the foreign key in case of List multiplicity
3055 ················// In case of Element multiplicity, the parent is either deleted,
3056 ················// or RemoveRelatedObject is called because the relation has been nulled.
3057 ················Delete(child);··
3058 ············}
3059 ········}
3060
3061 ········/// <summary>
3062 ········/// Remove a related object
3063 ········/// </summary>
3064 ········/// <param name="pc"></param>
3065 ········/// <param name="r"></param>
3066 ········/// <param name="child"></param>
3067 ········/// <param name="calledFromStateManager"></param>
3068 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3069 ········{
3070 ············//TODO: We need a relation management, which is independent of
3071 ············//the state management of an object. At the moment the relation
3072 ············//lists or elements are cached for restore, if an object is marked dirty.
3073 ············//Thus we have to mark dirty our parent object in any case at the moment.
3074 ············if (calledFromStateManager)
3075 ················MarkDirty(pc);
3076
3077 ············// Object can be deleted in an OnDelete-Handler
3078 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3079 ················return;
3080 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3081 ············// Preconditions
3082 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3083 ············// The Object state is checked there
3084
3085 ············// If there is a composition in the opposite direction
3086 ············// && the other direction hasn't been processed
3087 ············// throw an exception.
3088 ············// If an exception is thrown at this point, have a look at IsLocked....
3089 ············if (r.Bidirectional && r.ForeignRelation.Composition
3090 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3091 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3092
3093 ············if (!removeLock.GetLock(pc, r, child))
3094 ················return;
3095
3096 ············try
3097 ············{
3098 ················// must be in this order, since the child
3099 ················// can be deleted in DeleteOrNullRelation
3100 ················//if (changeForeignRelations)
3101 ················DeleteOrNullForeignRelation(pc, r, child);
3102 ················DeleteOrNullRelation(pc, r, child);
3103 ············}
3104 ············finally
3105 ············{
3106 ················removeLock.Unlock(pc, r, child);
3107 ············}
3108
3109 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3110 ········}
3111
3112 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3113 ········{
3114 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3115 ············//············Debug.Indent();
3116 ············if (r.Multiplicity == RelationMultiplicity.Element)
3117 ············{
3118 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3119 ················if(child != null)
3120 ················{
3121 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3122 ····················//····················{
3123 ····················//························if (!r.ForeignRelation.Composition)
3124 ····················//························{
3125 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3126 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3127 ····················//························}
3128 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3129 ····················//························else
3130 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3131 ····················//····················}
3132 ····················InternalRemoveRelatedObject(pc, r, child, false);
3133 ················}
3134 ············}
3135 ············else
3136 ············{
3137 ················IList list = mappings.GetRelationContainer(pc, r);
3138 ················if(list != null && list.Count > 0)
3139 ················{
3140 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3141 ····················//····················{
3142 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3143 ····················//····················}
3144 ····················// Since RemoveRelatedObjects probably changes the list,
3145 ····················// we iterate through a copy of the list.
3146 ····················ArrayList al = new ArrayList(list);
3147 ····················foreach(IPersistenceCapable relObj in al)
3148 ····················{
3149 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3150 ····················}
3151 ················}
3152 ············}
3153 ············//············Debug.Unindent();
3154 ········}
3155
3156 ········/// <summary>
3157 ········/// Remove all related objects from a parent.
3158 ········/// </summary>
3159 ········/// <param name="pc">the parent object</param>
3160 ········/// <param name="checkAssoziations"></param>
3161 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3162 ········{
3163 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3164 ············//············Debug.Indent();
3165 ············// delete all related objects:
3166 ············Class parentClass = GetClass(pc);
3167 ············// Remove Assoziations first
3168 ············foreach(Relation r in parentClass.Relations)
3169 ············{
3170 ················if (!r.Composition)
3171 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3172 ············}
3173 ············foreach(Relation r in parentClass.Relations)
3174 ············{
3175 ················if (r.Composition)
3176 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3177 ············}
3178
3179 ············//············Debug.Unindent();
3180 ········}
3181
3182 ········/// <summary>
3183 ········/// Delete a list of objects
3184 ········/// </summary>
3185 ········/// <param name="list">the list of objects to remove</param>
3186 ········public void Delete(IList list)
3187 ········{
3188 ············for (int i = 0; i < list.Count; i++)
3189 ············{
3190 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3191 ················Delete(pc);
3192 ············}
3193 ········}
3194
3195 ········/// <summary>
3196 ········/// Make object hollow. All relations will be unloaded and object data will be
3197 ········/// newly fetched during the next touch of a persistent field.
3198 ········/// </summary>
3199 ········/// <param name="o"></param>
3200 ········public virtual void MakeHollow(object o)
3201 ········{
3202 ············IPersistenceCapable pc = CheckPc(o);
3203 ············MakeHollow(pc, false);
3204 ········}
3205
3206 ········/// <summary>
3207 ········/// Make the object hollow if it is persistent. Unload all complex data.
3208 ········/// </summary>
3209 ········/// <param name="o"></param>
3210 ········/// <param name="recursive">if true then unload related objects as well</param>
3211 ········public virtual void MakeHollow(object o, bool recursive)
3212 ········{
3213 ············IPersistenceCapable pc = CheckPc(o);
3214 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3215 ················return;
3216 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3217 ············{
3218 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3219 ············}
3220 ············pc.NDOObjectState = NDOObjectState.Hollow;
3221 ············MakeRelationsHollow(pc, recursive);
3222 ········}
3223
3224 ········/// <summary>
3225 ········/// Make all objects of a list hollow.
3226 ········/// </summary>
3227 ········/// <param name="list">the list of objects that should be made hollow</param>
3228 ········public virtual void MakeHollow(System.Collections.IList list)
3229 ········{
3230 ············MakeHollow(list, false);
3231 ········}
3232
3233 ········/// <summary>
3234 ········/// Make all objects of a list hollow.
3235 ········/// </summary>
3236 ········/// <param name="list">the list of objects that should be made hollow</param>
3237 ········/// <param name="recursive">if true then unload related objects as well</param>
3238 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3239 ········{
3240 ············foreach (IPersistenceCapable pc in list)
3241 ················MakeHollow(pc, recursive);················
3242 ········}
3243
3244 ········/// <summary>
3245 ········/// Make all unlocked objects in the cache hollow.
3246 ········/// </summary>
3247 ········public virtual void MakeAllHollow()
3248 ········{
3249 ············foreach(var pc in cache.UnlockedObjects)
3250 ············{
3251 ················MakeHollow(pc, false);
3252 ············}
3253 ········}
3254
3255 ········/// <summary>
3256 ········/// Make relations hollow.
3257 ········/// </summary>
3258 ········/// <param name="pc">The parent object</param>
3259 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3260 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3261 ········{
3262 ············Class c = GetClass(pc);
3263 ············foreach(Relation r in c.Relations)
3264 ············{
3265 ················if (r.Multiplicity == RelationMultiplicity.Element)
3266 ················{
3267 ····················mappings.SetRelationField(pc, r.FieldName, null);
3268 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3269 ····················//····················if((null != child) && recursive)
3270 ····················//····················{
3271 ····················//························MakeHollow(child, true);
3272 ····················//····················}
3273 ················}
3274 ················else
3275 ················{
3276 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3277 ························continue;
3278 ····················// Help GC by clearing lists
3279 ····················IList l = mappings.GetRelationContainer(pc, r);
3280 ····················if(l != null)
3281 ····················{
3282 ························if(recursive)
3283 ························{
3284 ····························MakeHollow(l, true);
3285 ························}
3286 ························l.Clear();
3287 ····················}
3288 ····················// Hollow relation
3289 ····················mappings.SetRelationContainer(pc, r, null);
3290 ················}
3291 ············}
3292 ············ClearRelationState(pc);
3293 ········}
3294
3295 ········private void ClearRelationState(IPersistenceCapable pc)
3296 ········{
3297 ············Class cl = GetClass(pc);
3298 ············foreach(Relation r in cl.Relations)
3299 ················pc.NDOSetLoadState(r.Ordinal, false);
3300 ········}
3301
3302 ········private void SetRelationState(IPersistenceCapable pc)
3303 ········{
3304 ············Class cl = GetClass(pc);
3305 ············// Due to a bug in the enhancer the constructors are not always patched right,
3306 ············// so NDOLoadState might be uninitialized
3307 ············if (pc.NDOLoadState == null)
3308 ············{
3309 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3310 ················if (fi == null)
3311 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3312 ················fi.SetValue(pc, new LoadState());
3313 ············}
3314
3315 ············// After serialization the relation load state is null
3316 ············if (pc.NDOLoadState.RelationLoadState == null)
3317 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3318 ············foreach(Relation r in cl.Relations)
3319 ················pc.NDOSetLoadState(r.Ordinal, true);
3320 ········}
3321
3322 ········/// <summary>
3323 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3324 ········/// </summary>
3325 ········/// <param name="t">The type of the persistent object</param>
3326 ········/// <returns></returns>
3327 ········public IPersistenceCapable CreateObject(Type t)
3328 ········{
3329 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3330 ········}
3331
3332 ········/// <summary>
3333 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3334 ········/// </summary>
3335 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3336 ········/// <returns></returns>
3337 ········public T CreateObject<T>()
3338 ········{
3339 ············return (T)CreateObject( typeof( T ) );
3340 ········}
3341
3342 ········/// <summary>
3343 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3344 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3345 ········/// is performed.
3346 ········/// </summary>
3347 ········/// <param name="t">The type of the object to retrieve.</param>
3348 ········/// <param name="keyData">The key value to build the object id.</param>
3349 ········/// <returns>A hollow object</returns>
3350 ········/// <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>
3351 ········public IPersistenceCapable FindObject(Type t, object keyData)
3352 ········{
3353 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3354 ············return FindObject(oid);
3355 ········}
3356
3357 ········/// <summary>
3358 ········/// Finds an object using a short id.
3359 ········/// </summary>
3360 ········/// <param name="encodedShortId"></param>
3361 ········/// <returns></returns>
3362 ········public IPersistenceCapable FindObject(string encodedShortId)
3363 ········{
3364 ············string shortId = encodedShortId.Decode();
3365 ············string[] arr = shortId.Split( '-' );
3366 ············if (arr.Length != 3)
3367 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3368 ············Type t = shortId.GetObjectType(this);··// try readable format
3369 ············if (t == null)
3370 ············{
3371 ················int typeCode = 0;
3372 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3373 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3374 ················t = this.typeManager[typeCode];
3375 ················if (t == null)
3376 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3377 ············}
3378
3379 ············Class cls = GetClass( t );
3380 ············if (cls == null)
3381 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3382
3383 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3384 ············string[] oidValues = arr[2].Split( ' ' );
3385
3386 ············int i = 0;
3387 ············foreach (var oidValue in oidValues)
3388 ············{
3389 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3390 ················if (oidType == typeof( int ))
3391 ················{
3392 ····················int key;
3393 ····················if (!int.TryParse( oidValue, out key ))
3394 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3395 ····················if (key > (int.MaxValue >> 1))
3396 ························key = -(int.MaxValue - key);
3397 ····················keydata[i] = key;
3398 ················}
3399 ················else if (oidType == typeof( Guid ))
3400 ················{
3401 ····················Guid key;
3402 ····················if (!Guid.TryParse( oidValue, out key ))
3403 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3404 ····················keydata[i] = key;
3405 ················}
3406 ················else if (oidType == typeof( string ))
3407 ················{
3408 ····················keydata[i] = oidValue;
3409 ················}
3410 ················else
3411 ················{
3412 ····················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 ) );
3413 ················}
3414
3415 ················i++;
3416 ············}
3417
3418 ············if (keydata.Length == 1)
3419 ················return FindObject( t, keydata[0] );
3420
3421 ············return FindObject( t, keydata );············
3422 ········}
3423
3424 ········/// <summary>
3425 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3426 ········/// instance of the object is returned. In either case, the DB is not accessed!
3427 ········/// </summary>
3428 ········/// <param name="id">Object id</param>
3429 ········/// <returns>The object to retrieve in hollow state</returns>········
3430 ········public IPersistenceCapable FindObject(ObjectId id)
3431 ········{
3432 ············if(id == null)
3433 ············{
3434 ················throw new ArgumentNullException("id");
3435 ············}
3436
3437 ············if(!id.IsValid())
3438 ············{
3439 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3440 ············}
3441
3442 ············IPersistenceCapable pc = cache.GetObject(id);
3443 ············if(pc == null)
3444 ············{
3445 ················pc = CreateObject(id.Id.Type);
3446 ················pc.NDOObjectId = id;
3447 ················pc.NDOStateManager = sm;
3448 ················pc.NDOObjectState = NDOObjectState.Hollow;
3449 ················cache.UpdateCache(pc);
3450 ············}
3451 ············return pc;
3452 ········}
3453
3454
3455 ········/// <summary>
3456 ········/// Reload an object from the database.
3457 ········/// </summary>
3458 ········/// <param name="o">The object to be reloaded.</param>
3459 ········public virtual void Refresh(object o)
3460 ········{
3461 ············IPersistenceCapable pc = CheckPc(o);
3462 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3463 ············{
3464 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3465 ············}
3466
3467 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3468 ················return; // Cannot update objects in current transaction
3469
3470 ············MakeHollow(pc);
3471 ············LoadData(pc);
3472 ········}
3473
3474 ········/// <summary>
3475 ········/// Refresh a list of objects.
3476 ········/// </summary>
3477 ········/// <param name="list">The list of objects to be refreshed.</param>
3478 ········public virtual void Refresh(IList list)
3479 ········{
3480 ············foreach (IPersistenceCapable pc in list)
3481 ················Refresh(pc);························
3482 ········}
3483
3484 ········/// <summary>
3485 ········/// Refreshes all unlocked objects in the cache.
3486 ········/// </summary>
3487 ········public virtual void RefreshAll()
3488 ········{
3489 ············Refresh( cache.UnlockedObjects.ToList() );
3490 ········}
3491
3492 ········/// <summary>
3493 ········/// Closes the PersistenceManager and releases all resources.
3494 ········/// </summary>
3495 ········public override void Close()
3496 ········{
3497 ············if (this.isClosing)
3498 ················return;
3499 ············this.isClosing = true;
3500 ············TransactionScope.Dispose();
3501 ············UnloadCache();
3502 ············base.Close();
3503 ········}
3504
3505 ········internal void LogIfVerbose( string msg )
3506 ········{
3507 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3508 ················Logger.LogDebug( msg );
3509 ········}
3510
3511
3512 ········#endregion
3513
3514
3515 #region Class extent
3516 ········/// <summary>
3517 ········/// Gets all objects of a given class.
3518 ········/// </summary>
3519 ········/// <param name="t">the type of the class</param>
3520 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3521 ········public virtual IList GetClassExtent(Type t)
3522 ········{
3523 ············return GetClassExtent(t, true);
3524 ········}
3525
3526 ········/// <summary>
3527 ········/// Gets all objects of a given class.
3528 ········/// </summary>
3529 ········/// <param name="t">The type of the class.</param>
3530 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3531 ········/// <returns>A list of all persistent objects of the given class.</returns>
3532 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3533 ········public virtual IList GetClassExtent(Type t, bool hollow)
3534 ········{
3535 ············IQuery q = NewQuery( t, null, hollow );
3536 ············return q.Execute();
3537 ········}
3538
3539 #endregion
3540
3541 #region Query engine
3542
3543
3544 ········/// <summary>
3545 ········/// Returns a virtual table for Linq queries.
3546 ········/// </summary>
3547 ········/// <typeparam name="T"></typeparam>
3548 ········/// <returns></returns>
3549 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3550 ········{
3551 ············return new VirtualTable<T>( this );
3552 ········}
3553
3554 ········
3555 ········/// <summary>
3556 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3557 ········/// 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
3558 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3559 ········/// not lose the foreign key it will be stored as part of the object.
3560 ········/// </summary>
3561 ········/// <param name="cl"></param>
3562 ········/// <param name="pc"></param>
3563 ········/// <param name="row"></param>
3564 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3565 ········{
3566 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3567 ············{
3568 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3569 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3570 ················foreach(string colName in cl.FKColumnNames)
3571 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3572 ················pc.NDOLoadState.LostRowInfo = kvl;················
3573 ············}
3574 ········}
3575
3576 ········/// <summary>
3577 ········/// Writes information into the data row, which cannot be stored in the object.
3578 ········/// </summary>
3579 ········/// <param name="cl"></param>
3580 ········/// <param name="pc"></param>
3581 ········/// <param name="row"></param>
3582 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3583 ········{
3584 ············if (cl.FKColumnNames != null)
3585 ············{
3586 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3587 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3588 ················if (kvl == null)
3589 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3590 ················foreach (KeyValuePair pair in kvl)
3591 ····················row[(string) pair.Key] = pair.Value;
3592 ············}
3593 ········}
3594
3595 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3596 ········{
3597 ············ReadObject(pc, row, cl.ColumnNames, 0);
3598 ············ReadTimeStamp(cl, pc, row);
3599 ············ReadLostForeignKeysFromRow(cl, pc, row);
3600 ············LoadRelated1To1Objects(pc, row);
3601 ············pc.NDOObjectState = NDOObjectState.Persistent;
3602 ········}
3603
3604
3605 ········/// <summary>
3606 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3607 ········/// </summary>
3608 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3609 ········{
3610 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3611 ············if (rows.Count == 0)
3612 ················return queryResult;
3613
3614 ············IList callbackObjects = new ArrayList();
3615 ············Class cl = GetClass(t);
3616 ············if (t.IsGenericTypeDefinition)
3617 ············{
3618 ················if (cl.TypeNameColumn == null)
3619 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3620 ················IEnumerator en = rows.GetEnumerator();
3621 ················en.MoveNext();··// Move to the first element
3622 ················DataRow testrow = (DataRow)en.Current;
3623 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3624 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3625 ············}
3626
3627 ············foreach(DataRow row in rows)
3628 ············{
3629 ················Type concreteType = t;
3630 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3631 ················{
3632 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3633 ····················{
3634 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3635 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3636 ····················}
3637 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3638 ····················concreteType = Type.GetType(typeStr);
3639 ····················if (concreteType == null)
3640 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3641 ················}
3642 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3643 ················IPersistenceCapable pc = cache.GetObject(id);················
3644 ················if(pc == null)
3645 ················{
3646 ····················pc = CreateObject( concreteType );
3647 ····················pc.NDOObjectId = id;
3648 ····················pc.NDOStateManager = sm;
3649 ····················// If the object shouldn't be hollow, this will be overwritten later.
3650 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3651 ················}
3652 ················// If we have found a non hollow object, the time stamp remains the old one.
3653 ················// In every other case we use the new time stamp.
3654 ················// Note, that there could be a hollow object in the cache.
3655 ················// We need the time stamp in hollow objects in order to correctly
3656 ················// delete objects using fake rows.
3657 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3658 ················{
3659 ····················ReadTimeStamp(cl, pc, row);
3660 ················}
3661 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3662 ················{
3663 ····················Row2Object(cl, pc, row);
3664 ····················if ((pc as IPersistenceNotifiable) != null)
3665 ························callbackObjects.Add(pc);
3666 ················}
3667
3668 ················cache.UpdateCache(pc);
3669 ················queryResult.Add(pc);
3670 ············}
3671 ············// Make shure this is the last statement before returning
3672 ············// to the caller, so the user can recursively use persistent
3673 ············// objects
3674 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3675 ················ipn.OnLoaded();
3676
3677 ············return queryResult;
3678 ········}
3679
3680 #endregion
3681
3682 #region Cache Management
3683 ········/// <summary>
3684 ········/// Remove all unused entries from the cache.
3685 ········/// </summary>
3686 ········public void CleanupCache()
3687 ········{
3688 ············GC.Collect(GC.MaxGeneration);
3689 ············GC.WaitForPendingFinalizers();
3690 ············cache.Cleanup();
3691 ········}
3692
3693 ········/// <summary>
3694 ········/// Remove all unlocked objects from the cache. Use with care!
3695 ········/// </summary>
3696 ········public void UnloadCache()
3697 ········{
3698 ············cache.Unload();
3699 ········}
3700 #endregion
3701
3702 ········/// <summary>
3703 ········/// Default encryption key for NDO
3704 ········/// </summary>
3705 ········/// <remarks>
3706 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3707 ········/// </remarks>
3708 ········public virtual byte[] EncryptionKey
3709 ········{
3710 ············get
3711 ············{
3712 ················if (this.encryptionKey == null)
3713 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3714 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3715 ················return this.encryptionKey;
3716 ············}
3717 ············set { this.encryptionKey = value; }
3718 ········}
3719
3720 ········/// <summary>
3721 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3722 ········/// </summary>
3723 ········public virtual bool HollowMode
3724 ········{
3725 ············get { return hollowMode; }
3726 ············set { hollowMode = value; }
3727 ········}
3728
3729 ········internal TypeManager TypeManager
3730 ········{
3731 ············get { return typeManager; }
3732 ········}
3733
3734 ········/// <summary>
3735 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3736 ········/// </summary>
3737 ········/// <remarks>
3738 ········/// Set this value before you start any transactions.
3739 ········/// </remarks>
3740 ········public TransactionMode TransactionMode
3741 ········{
3742 ············get { return TransactionScope.TransactionMode; }
3743 ············set { TransactionScope.TransactionMode = value; }
3744 ········}
3745
3746 ········/// <summary>
3747 ········/// Sets or gets the Isolation Level.
3748 ········/// </summary>
3749 ········/// <remarks>
3750 ········/// Set this value before you start any transactions.
3751 ········/// </remarks>
3752 ········public IsolationLevel IsolationLevel
3753 ········{
3754 ············get { return TransactionScope.IsolationLevel; }
3755 ············set { TransactionScope.IsolationLevel = value; }
3756 ········}
3757
3758 ········internal class MappingTableEntry
3759 ········{
3760 ············private IPersistenceCapable parentObject;
3761 ············private IPersistenceCapable relatedObject;
3762 ············private Relation relation;
3763 ············private bool deleteEntry;
3764 ············
3765 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3766 ············{
3767 ············}
3768 ············
3769 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3770 ············{
3771 ················parentObject = pc;
3772 ················relatedObject = relObj;
3773 ················relation = r;
3774 ················this.deleteEntry = deleteEntry;
3775 ············}
3776
3777 ············public bool DeleteEntry
3778 ············{
3779 ················get
3780 ················{
3781 ····················return deleteEntry;
3782 ················}
3783 ············}
3784
3785 ············public IPersistenceCapable ParentObject
3786 ············{
3787 ················get
3788 ················{
3789 ····················return parentObject;
3790 ················}
3791 ············}
3792
3793 ············public IPersistenceCapable RelatedObject
3794 ············{
3795 ················get
3796 ················{
3797 ····················return relatedObject;
3798 ················}
3799 ············}
3800
3801 ············public Relation Relation
3802 ············{
3803 ················get
3804 ················{
3805 ····················return relation;
3806 ················}
3807 ············}
3808 ········}
3809
3810 ········/// <summary>
3811 ········/// Get a DataRow representing the given object.
3812 ········/// </summary>
3813 ········/// <param name="o"></param>
3814 ········/// <returns></returns>
3815 ········public DataRow GetClonedDataRow( object o )
3816 ········{
3817 ············IPersistenceCapable pc = CheckPc( o );
3818
3819 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3820 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3821
3822 ············DataRow row = cache.GetDataRow( pc );
3823 ············DataTable newTable = row.Table.Clone();
3824 ············newTable.ImportRow( row );
3825 ············row = newTable.Rows[0];
3826
3827 ············Class cls = mappings.FindClass(o.GetType());
3828 ············WriteObject( pc, row, cls.ColumnNames );
3829 ············WriteForeignKeysToRow( pc, row );
3830
3831 ············return row;
3832 ········}
3833
3834 ········/// <summary>
3835 ········/// Gets an object, which shows all changes applied to the given object.
3836 ········/// This function can be used to build an audit system.
3837 ········/// </summary>
3838 ········/// <param name="o"></param>
3839 ········/// <returns>An ExpandoObject</returns>
3840 ········public ChangeLog GetChangeSet( object o )
3841 ········{
3842 ············var changeLog = new ChangeLog(this);
3843 ············changeLog.Initialize( o );
3844 ············return changeLog;
3845 ········}
3846
3847 ········/// <summary>
3848 ········/// Outputs a revision number representing the assembly version.
3849 ········/// </summary>
3850 ········/// <remarks>This can be used for debugging purposes</remarks>
3851 ········public int Revision
3852 ········{
3853 ············get
3854 ············{
3855 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3856 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3857 ················return int.Parse( vstring );
3858 ············}
3859 ········}
3860 ····}
3861 }
3862
New Commit (01941c7)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
59 ····/// </summary>
60 ····public delegate void OnSavingHandler(ICollection l);
61 ····/// <summary>
62 ····/// Delegate type for the OnSavedEvent.
63 ····/// </summary>
64 ····/// <param name="auditSet"></param>
65 ····public delegate void OnSavedHandler(AuditSet auditSet);
66
67 ····/// <summary>
68 ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid.
69 ····/// </summary>
70 ····/// <param name="pc"></param>
71 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
72 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
73 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
74
75 ····/// <summary>
76 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
77 ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation.
78 ····/// </summary>
79 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
80 ····{········
81 ········private bool hollowMode = false;
82 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
83
84 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
85 ········private ObjectLock removeLock = new ObjectLock();
86 ········private ObjectLock addLock = new ObjectLock();
87 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
88 ········// List of created objects that use mapping table and need to be stored in mapping table
89 ········// after they have been stored to the database to update foreign keys.
90 ········private ArrayList createdMappingTableObjects = new ArrayList();··
91 ········private TypeManager typeManager;
92 ········internal bool DeferredMode { get; private set; }
93 ········private INDOTransactionScope transactionScope;
94 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
95
96 ········private OpenConnectionListener openConnectionListener;
97
98 ········/// <summary>
99 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
100 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
101 ········/// </summary>
102 ········public event CollisionHandler CollisionEvent;
103
104 ········/// <summary>
105 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
106 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
107 ········/// </summary>
108 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event, if you want to be notified about the end
112 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
113 ········/// during the transaction and are to be saved or deleted.
114 ········/// </summary>
115 ········public event OnSavingHandler OnSavingEvent;
116 ········/// <summary>
117 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
118 ········/// </summary>
119 ········public event OnSavedHandler OnSavedEvent;
120 ········
121 ········private const string hollowMarker = "Hollow";
122 ········private byte[] encryptionKey;
123 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
124 ········private bool isClosing = false;
125
126 ········/// <summary>
127 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
128 ········/// </summary>
129 ········protected internal List<RelationChangeRecord> RelationChanges
130 ········{
131 ············get { return this.relationChanges; }
132 ········}
133
134 ········/// <summary>
135 ········/// Initializes a new PersistenceManager instance.
136 ········/// </summary>
137 ········/// <param name="mappingFileName"></param>
138 ········protected override void Init(string mappingFileName)
139 ········{
140 ············try
141 ············{
142 ················base.Init(mappingFileName);
143 ············}
144 ············catch (Exception ex)
145 ············{
146 ················if (ex is NDOException)
147 ····················throw;
148 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
149 ············}
150
151 ········}
152
153 ········/// <summary>
154 ········/// Initializes the persistence manager
155 ········/// </summary>
156 ········/// <remarks>
157 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
158 ········/// </remarks>
159 ········/// <param name="mapping"></param>
160 ········internal override void Init( Mappings mapping )
161 ········{
162 ············base.Init( mapping );
163
164 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
165
166 ············string dir = Path.GetDirectoryName( mapping.FileName );
167
168 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
169 ············typeManager = new TypeManager( typesFile, this.mappings );
170
171 ············sm = new StateManager( this );
172
173 ············InitClasses();
174 ········}
175
176
177 ········/// <summary>
178 ········/// Standard Constructor.
179 ········/// </summary>
180 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
181 ········/// <remarks>
182 ········/// Searches for a mapping file in the application directory.
183 ········/// The constructor tries to find a file with the same name as
184 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
185 ········/// file called AssemblyName.ndo.mapping in the application directory.
186 ········/// </remarks>
187 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
188 ········{
189 ········}
190
191 ········/// <summary>
192 ········/// Loads the mapping file from the specified location. This allows to use
193 ········/// different mapping files with different classes mapped in it.
194 ········/// </summary>
195 ········/// <param name="mappingFile">Path to the mapping file.</param>
196 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
197 ········/// <remarks>Only the Professional and Enterprise
198 ········/// Editions can handle more than one mapping file.</remarks>
199 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
200 ········{
201 ········}
202
203 ········/// <summary>
204 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
205 ········/// </summary>
206 ········/// <param name="mapping">The cached mapping object</param>
207 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
208 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
209 ········{
210 ········}
211
212 ········#region Object Container Stuff
213 ········/// <summary>
214 ········/// Gets a container of all loaded objects and tries to load all child objects,
215 ········/// which are reachable through composite relations.
216 ········/// </summary>
217 ········/// <returns>An ObjectContainer object.</returns>
218 ········/// <remarks>
219 ········/// It is not recommended, to transfer objects with a state other than Hollow,
220 ········/// Persistent, or Transient.
221 ········/// The transfer format is binary.
222 ········/// </remarks>
223 ········public ObjectContainer GetObjectContainer()
224 ········{
225 ············IList l = this.cache.AllObjects;
226 ············foreach(IPersistenceCapable pc in l)
227 ············{
228 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
229 ················{
230 ····················if (Logger != null)
231 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
232 ················}
233 ············}
234
235 ············ObjectContainer oc = new ObjectContainer();
236 ············oc.AddList(l);
237 ············return oc;
238 ········}
239
240 ········/// <summary>
241 ········/// Returns a container of all objects provided in the objects list and searches for
242 ········/// child objects according to the serFlags.
243 ········/// </summary>
244 ········/// <param name="objects">The list of the root objects to add to the container.</param>
245 ········/// <returns>An ObjectContainer object.</returns>
246 ········/// <remarks>
247 ········/// It is not recommended, to transfer objects with a state other than Hollow,
248 ········/// Persistent, or Transient.
249 ········/// </remarks>
250 ········public ObjectContainer GetObjectContainer(IList objects)
251 ········{
252 ············foreach(object o in objects)
253 ············{
254 ················CheckPc(o);
255 ················IPersistenceCapable pc = o as IPersistenceCapable;
256 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
257 ····················LoadData(pc);
258 ············}
259 ············ObjectContainer oc = new ObjectContainer();
260 ············oc.AddList(objects);
261 ············return oc;
262 ········}
263
264
265 ········/// <summary>
266 ········/// Returns a container containing the provided object
267 ········/// and tries to load all child objects
268 ········/// reachable through composite relations.
269 ········/// </summary>
270 ········/// <param name="obj">The object to be added to the container.</param>
271 ········/// <returns>An ObjectContainer object.</returns>
272 ········/// <remarks>
273 ········/// It is not recommended, to transfer objects with a state other than Hollow,
274 ········/// Persistent, or Transient.
275 ········/// The transfer format is binary.
276 ········/// </remarks>
277 ········public ObjectContainer GetObjectContainer(Object obj)
278 ········{
279 ············CheckPc(obj);
280 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
281 ················LoadData(obj);
282 ············ObjectContainer oc = new ObjectContainer();
283 ············oc.AddObject(obj);
284 ············return oc;
285 ········}
286
287 ········/// <summary>
288 ········/// Merges an object container to the active objects in the pm. All changes and the state
289 ········/// of the objects will be taken over by the pm.
290 ········/// </summary>
291 ········/// <remarks>
292 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
293 ········/// The flag MarkAsTransient can be used to perform a kind
294 ········/// of object based replication using the ObjectContainer class.
295 ········/// Objects, which are persistent at one machine, can be transfered
296 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
297 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
298 ········/// transient object tree.
299 ········/// There is one difference to freshly created objects: If an object id exists, it will be
300 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
301 ········/// oids will be reused by the receiving PersistenceManager.
302 ········/// </remarks>
303 ········/// <param name="ocb">The object container to be merged.</param>
304 ········public void MergeObjectContainer(ObjectContainerBase ocb)
305 ········{
306 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
307 ············if (csc != null)
308 ············{
309 ················MergeChangeSet(csc);
310 ················return;
311 ············}
312 ············ObjectContainer oc = ocb as ObjectContainer;
313 ············if (oc != null)
314 ············{
315 ················InternalMergeObjectContainer(oc);
316 ················return;
317 ············}
318 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
319 ········}
320
321
322 ········void InternalMergeObjectContainer(ObjectContainer oc)
323 ········{
324 ············// TODO: Check, if other states are useful. Find use scenarios.
325 ············foreach(IPersistenceCapable pc in oc.RootObjects)
326 ············{
327 ················if (pc.NDOObjectState == NDOObjectState.Transient)
328 ····················MakePersistent(pc);
329 ············}
330 ············foreach(IPersistenceCapable pc in oc.RootObjects)
331 ············{
332 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
333 ············}
334 ········}
335
336 ········void MergeChangeSet(ChangeSetContainer cs)
337 ········{
338 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
339 ············{
340 ················InternalMakePersistent(pc, false);
341 ············}
342 ············foreach(ObjectId oid in cs.DeletedObjects)
343 ············{
344 ················IPersistenceCapable pc2 = FindObject(oid);
345 ················Delete(pc2);
346 ············}
347 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
348 ············{
349 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
350 ················Class pcClass = GetClass(pc);
351 ················// Make sure, the object is loaded.
352 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
353 ····················LoadData(pc2);
354 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
355 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
356 ················DataRow row = entry.row;
357 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
358 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
359 ············}
360 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
361 ············{
362 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
363 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
364 ················Class pcClass = GetClass(parent);
365 ················Relation r = pcClass.FindRelation(rcr.RelationName);
366 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
367 ····················LoadRelation(parent, r, true);
368 ················if (rcr.IsAdded)
369 ················{
370 ····················InternalAddRelatedObject(parent, r, child, true);
371 ····················if (r.Multiplicity == RelationMultiplicity.Element)
372 ····················{
373 ························mappings.SetRelationField(parent, r.FieldName, child);
374 ····················}
375 ····················else
376 ····················{
377 ························IList l = mappings.GetRelationContainer(parent, r);
378 ························l.Add(child);
379 ····················}
380 ················}
381 ················else
382 ················{
383 ····················RemoveRelatedObject(parent, r.FieldName, child);
384 ····················if (r.Multiplicity == RelationMultiplicity.Element)
385 ····················{
386 ························mappings.SetRelationField(parent, r.FieldName, null);
387 ····················}
388 ····················else
389 ····················{
390 ························IList l = mappings.GetRelationContainer(parent, r);
391 ························try
392 ························{
393 ····························ObjectListManipulator.Remove(l, child);
394 ························}
395 ························catch
396 ························{
397 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
398 ························}
399 ····················}
400 ················}
401 ············}
402
403 ········}
404 ········#endregion
405
406 ········#region Implementation of IPersistenceManager
407
408 ········// Complete documentation can be found in IPersistenceManager
409
410
411 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
412 ········{
413 ············if (!cl.Oid.IsDependent)
414 ················return;
415 ············WriteForeignKeysToRow(pc, row);
416 ········}
417
418 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
419 ········{
420 ············// Object is now under control of the state manager
421 ············pc.NDOStateManager = sm;
422
423 ············Type pcType = pc.GetType();
424 ············Class pcClass = GetClass(pc);
425
426 ············// Create new object
427 ············DataTable dt = GetTable(pcType);
428 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
429
430 ············// In case of a Guid oid the value will be computed now.
431 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
432 ············{
433 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
434 ················{
435 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
436 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
437 ····················else
438 ························row[oidColumn.Name] = Guid.NewGuid();
439 ················}
440 ············}
441
442 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
443
444 ············// If the object is merged from an ObjectContainer, the id should be reused,
445 ············// if the id is client generated (not Autoincremented).
446 ············// In every other case, the oid is set to null, to force generating a new oid.
447 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
448 ············if ((object)pc.NDOObjectId != null)
449 ············{
450 ················bool hasAutoincrement = false;
451 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
452 ················{
453 ····················if (oidColumn.AutoIncremented)
454 ····················{
455 ························hasAutoincrement = true;
456 ························break;
457 ····················}
458 ················}
459 ················if (hasAutoincrement) // can't store existing id
460 ················{
461 ····················pc.NDOObjectId = null;
462 ····················fireIdGeneration = true;
463 ················}
464 ············}
465
466 ············// In case of a dependent class the oid has to be read from the fields according to the relations
467 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
468
469 ············if ((object)pc.NDOObjectId == null)
470 ············{
471 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
472 ············}
473
474 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
475 ············{
476 ················if (fireIdGeneration)
477 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
478 ················// At this place the oid might have been
479 ················// - deserialized (MergeObjectContainer)
480 ················// - created using NewObjectId
481 ················// - defined by the IdGenerationEvent
482
483 ················// At this point we have a valid oid.
484 ················// If the object has a field mapped to the oid we have
485 ················// to write back the oid to the field
486 ················int i = 0;
487 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
488 ················{
489 ····················if (oidColumn.FieldName != null)
490 ····················{
491 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
492 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
493 ····················}
494 ····················i++;
495 ················});
496
497
498
499 ················// Now write back the data into the row
500 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
501 ············}
502
503 ············
504 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
505 ············dt.Rows.Add(row);
506
507 ············cache.Register(pc);
508
509 ············// new object that has never been written to the DS
510 ············pc.NDOObjectState = NDOObjectState.Created;
511 ············// Mark all Relations as loaded
512 ············SetRelationState(pc);
513
514 ············if (checkRelations)
515 ············{
516 ················// Handle related objects:
517 ················foreach(Relation r in pcClass.Relations)
518 ················{
519 ····················if (r.Multiplicity == RelationMultiplicity.Element)
520 ····················{
521 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
522 ························if(child != null)
523 ························{
524 ····························AddRelatedObject(pc, r, child);
525 ························}
526 ····················}
527 ····················else
528 ····················{
529 ························IList list = mappings.GetRelationContainer(pc, r);
530 ························if(list != null)
531 ························{
532 ····························foreach(IPersistenceCapable relObj in list)
533 ····························{
534 ································if (relObj != null)
535 ····································AddRelatedObject(pc, r, relObj);
536 ····························}
537 ························}
538 ····················}
539 ················}
540 ············}
541
542 ············var relations··= CollectRelationStates(pc);
543 ············cache.Lock(pc, row, relations);
544 ········}
545
546
547 ········/// <summary>
548 ········/// Make an object persistent.
549 ········/// </summary>
550 ········/// <param name="o">the transient object that should be made persistent</param>
551 ········public void MakePersistent(object o)
552 ········{
553 ············IPersistenceCapable pc = CheckPc(o);
554
555 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
556 ············//Debug.Indent();
557
558 ············if (pc.NDOObjectState != NDOObjectState.Transient)
559 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
560
561 ············InternalMakePersistent(pc, true);
562
563 ········}
564
565
566
567 ········//········/// <summary>
568 ········//········/// Checks, if an object has a valid id, which was created by the database
569 ········//········/// </summary>
570 ········//········/// <param name="pc"></param>
571 ········//········/// <returns></returns>
572 ········//········private bool HasValidId(IPersistenceCapable pc)
573 ········//········{
574 ········//············if (this.IdGenerationEvent != null)
575 ········//················return true;
576 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
577 ········//········}
578
579
580 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
581 ········{
582 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
583 ············if(r.HasSubclasses)
584 ············{
585 ················// we don't support 1:n with foreign fields in subclasses because we would have to
586 ················// search for objects in all subclasses! Instead use a mapping table.
587 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
588 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
589 ············}
590
591 ············if (!makeRelObjPersistent)
592 ················MarkDirty(relObj);
593 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
594 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
595
596 ············if (relObjRow == null)
597 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
598
599 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
600
601 ············if (relObj.NDOLoadState.LostRowInfo == null)
602 ············{
603 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
604 ············}
605 ············else
606 ············{
607 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
608 ············}
609 ········}
610
611 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
612 ········{
613 ············switch(relObj.NDOObjectState)
614 ············{
615 ················case NDOObjectState.Persistent:
616 ····················MarkDirty(relObj);
617 ····················break;
618 ················case NDOObjectState.Hollow:
619 ····················LoadData(relObj);
620 ····················MarkDirty(relObj);
621 ····················break;
622 ············}
623
624 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
625 ············{
626 ················IPersistenceCapable newpc;
627 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
628 ················{
629 ····················if (newpc != pc)
630 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
631 ················}
632 ················else
633 ················{
634 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
635 ················}
636 ············}
637 ············else
638 ············{
639 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
640 ····················LoadRelation(relObj, r.ForeignRelation, true);
641 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
642 ················if(l == null)
643 ················{
644 ····················try
645 ····················{
646 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
647 ····················}
648 ····················catch
649 ····················{
650 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
651 ····················}
652 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
653 ················}
654 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
655 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
656 ················if (!ObjectListManipulator.Contains(l, pc))
657 ····················l.Add(pc);
658 ············}
659 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
660 ········}
661
662
663 ········/// <summary>
664 ········/// Add a related object to the specified object.
665 ········/// </summary>
666 ········/// <param name="pc">the parent object</param>
667 ········/// <param name="fieldName">the field name of the relation</param>
668 ········/// <param name="relObj">the related object that should be added</param>
669 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
670 ········{
671 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
672 ············Relation r = mappings.FindRelation(pc, fieldName);
673 ············AddRelatedObject(pc, r, relObj);
674 ········}
675
676 ········/// <summary>
677 ········/// Core functionality to add an object to a relation container or relation field.
678 ········/// </summary>
679 ········/// <param name="pc"></param>
680 ········/// <param name="r"></param>
681 ········/// <param name="relObj"></param>
682 ········/// <param name="isMerging"></param>
683 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
684 ········{
685 ············
686 ············// avoid recursion
687 ············if (!addLock.GetLock(relObj))
688 ················return;
689
690 ············try
691 ············{
692 ················//TODO: We need a relation management, which is independent of
693 ················//the state management of an object. Currently the relation
694 ················//lists or elements are cached for restore, if an object is marked dirty.
695 ················//Thus we have to mark dirty our parent object in any case at the moment.
696 ················MarkDirty(pc);
697
698 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
699 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
700 ················//The latter happens in CreateAddedObjectRow
701
702 ················Class relClass = GetClass(relObj);
703
704 ················if (r.Multiplicity == RelationMultiplicity.Element
705 ····················&& r.HasSubclasses
706 ····················&& r.MappingTable == null················
707 ····················&& !this.HasOwnerCreatedIds
708 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
709 ····················&& !relClass.HasGuidOid)
710 ················{
711 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
712 ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table.");
713 ····················if (r.Composition)
714 ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's.");
715 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
716 ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." );
717 ················}
718
719 ················bool isDependent = relClass.Oid.IsDependent;
720
721 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
722 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
723
724 ················// Need to patch pc into the relation relObj->pc, because
725 ················// the oid is built on base of this information
726 ················if (isDependent)
727 ················{
728 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
729 ················}
730
731 ················if (r.Composition || isDependent)
732 ················{
733 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
734 ························MakePersistent(relObj);
735 ················}
736
737 ················if(r.MappingTable == null)
738 ················{
739 ····················if (r.Bidirectional)
740 ····················{
741 ························// This object hasn't been saved yet, so the key is wrong.
742 ························// Therefore, the child must be written twice to update the foreign key.
743 #if trace
744 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
745 ························if (r.Multiplicity == RelationMultiplicity.Element)
746 ····························sb.Append("1");
747 ························else
748 ····························sb.Append("n");
749 ························sb.Append(":");
750 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
751 ····························sb.Append("1");
752 ························else
753 ····························sb.Append("n");
754 ························sb.Append ("OwnCreatedOther");
755 ························sb.Append(relObj.NDOObjectState.ToString());
756 ························sb.Append(' ');
757
758 ························sb.Append(types[0].ToString());
759 ························sb.Append(' ');
760 ························sb.Append(types[1].ToString());
761 ························Debug.WriteLine(sb.ToString());
762 #endif
763 ························//························if (r.Multiplicity == RelationMultiplicity.Element
764 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
765 ························//························{
766 ························// Element means:
767 ························// pc is keyholder
768 ························// -> relObj is saved first
769 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
770 ························// Both are Created - use type sort order
771 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
772 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
773 ························{
774 ····························if (mappings.GetUpdateOrder(pc.GetType())
775 ································< mappings.GetUpdateOrder(relObj.GetType()))
776 ································createdDirectObjects.Add(pc);
777 ····························else
778 ································createdDirectObjects.Add( relObj );
779 ························}
780 ····················}
781 ····················if (r.Multiplicity == RelationMultiplicity.List)
782 ····················{
783 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
784 ····················}
785 ················}
786 ················else
787 ················{
788 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
789 ················}
790 ················if(r.Bidirectional)
791 ················{
792 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
793 ····················{
794 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
795 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
796 ····················}
797 ····················else if ( !addLock.IsLocked( pc ) )
798 ····················{
799 ························PatchForeignRelation( pc, r, relObj );
800 ····················}
801 ················}
802
803 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
804 ············}
805 ············finally
806 ············{
807 ················addLock.Unlock(relObj);
808 ················//Debug.Unindent();
809 ············}
810 ········}
811
812 ········/// <summary>
813 ········/// Returns an integer value which determines the rank of the given type in the update order list.
814 ········/// </summary>
815 ········/// <param name="t">The type to determine the update order.</param>
816 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
817 ········/// <remarks>
818 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
819 ········/// </remarks>
820 ········public int GetUpdateRank(Type t)
821 ········{
822 ············return mappings.GetUpdateOrder(t);
823 ········}
824
825 ········/// <summary>
826 ········/// Add a related object to the specified object.
827 ········/// </summary>
828 ········/// <param name="pc">the parent object</param>
829 ········/// <param name="r">the relation mapping info</param>
830 ········/// <param name="relObj">the related object that should be added</param>
831 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
832 ········{
833 ············//············string idstr;
834 ············//············if (relObj.NDOObjectId == null)
835 ············//················idstr = relObj.GetType().ToString();
836 ············//············else
837 ············//················idstr = relObj.NDOObjectId.Dump();
838 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
839 ············//Debug.Indent();
840
841 ············Class relClass = GetClass(relObj);
842 ············bool isDependent = relClass.Oid.IsDependent;
843
844 ············// Do some checks to guarantee that the assignment is correct
845 ············if(r.Composition)
846 ············{
847 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
848 ················{
849 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
850 ················}
851 ············}
852 ············else
853 ············{
854 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
855 ················{
856 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
857 ················}
858 ············}
859
860 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
861 ············{
862 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
863 ············}
864
865 ············InternalAddRelatedObject(pc, r, relObj, false);
866
867 ········}
868
869 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
870 ········{
871 ············// Need to patch pc into the relation relObj->pc, because
872 ············// the oid is built on base of this information
873 ············// The second relation has to be set before adding relObj
874 ············// to the relation list.
875 ············PatchForeignRelation(pc, r, relObj);
876 ············IPersistenceCapable parent;
877 ············foreach (Relation oidRelation in relClass.Oid.Relations)
878 ············{
879 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
880 ················if (parent == null)
881 ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects.");
882 ················if (parent.NDOObjectState == NDOObjectState.Transient)
883 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
884
885 ············}
886 ········}
887
888
889 ········/// <summary>
890 ········/// Remove a related object from the specified object.
891 ········/// </summary>
892 ········/// <param name="pc">the parent object</param>
893 ········/// <param name="fieldName">Field name of the relation</param>
894 ········/// <param name="relObj">the related object that should be removed</param>
895 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
896 ········{
897 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
898 ············Relation r = mappings.FindRelation(pc, fieldName);
899 ············InternalRemoveRelatedObject(pc, r, relObj, true);
900 ········}
901
902 ········/// <summary>
903 ········/// Registers a listener which will be notified, if a new connection is opened.
904 ········/// </summary>
905 ········/// <param name="listener">Delegate of a listener function</param>
906 ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks>
907 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
908 ········{
909 ············this.openConnectionListener = listener;
910 ········}
911
912 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
913 ········{
914 ············if (openConnectionListener != null)
915 ················return openConnectionListener(conn);
916 ············return conn.Name;
917 ········}
918
919
920 ········/*
921 ········doCommit should be:
922 ········
923 ····················Query····Save····Save(true)
924 ········Optimistic····1········1········0
925 ········Pessimistic····0········1········0
926 ············
927 ········Deferred Mode············
928 ····················Query····Save····Save(true)
929 ········Optimistic····0········1········0
930 ········Pessimistic····0········1········0
931 ········ */
932
933 ········internal void CheckEndTransaction(bool doCommit)
934 ········{
935 ············if (doCommit)
936 ············{
937 ················TransactionScope.Complete();
938 ············}
939 ········}
940
941 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
942 ········{
943 ············CheckTransaction(handler, this.GetClass(t).Connection);
944 ········}
945
946 ········/// <summary>
947 ········/// Each and every database operation has to be preceded by a call to this function.
948 ········/// </summary>
949 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
950 ········{
951 ············TransactionScope.CheckTransaction();
952 ············
953 ············if (handler.Connection == null)
954 ············{
955 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
956 ················{
957 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
958 ····················string connStr = this.OnNewConnection( ndoConn );
959 ····················var connection = p.NewConnection( connStr );
960 ····················if (connection == null)
961 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
962 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
963 ····················return connection;
964 ················} );
965 ············}
966
967 ············if (TransactionMode != TransactionMode.None)
968 ············{
969 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
970 ············}
971
972 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
973 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
974 ············{
975 ················handler.Connection.Open();
976 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
977 ············}
978 ········}
979
980 ········/// <summary>
981 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
982 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
983 ········/// This is possible if there is a listener for the CollisionEvent.
984 ········/// Else we throw an exception.
985 ········/// </summary>
986 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
987 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
988 ········{
989 ············DataRow row = ex.Row;
990 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
991 ················throw(ex);
992 ············if (row.RowState == DataRowState.Detached)
993 ················return;
994 ············foreach (Cache.Entry e in cache.LockedObjects)
995 ············{
996 ················if (e.row == row)
997 ················{
998 ····················CollisionEvent(e.pc);
999 ····················return;
1000 ················}
1001 ············}
1002 ············throw ex;
1003 ········}
1004
1005
1006 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1007 ········{
1008 ············Class cl = GetClass(pc);
1009 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1010 ············Dictionary<string,MemberInfo> persistentFields = null;
1011 ············if (etypes.Length > 0)
1012 ············{
1013 ················FieldMap fm = new FieldMap(cl);
1014 ················persistentFields = fm.PersistentFields;
1015 ············}
1016 ············foreach(string s in etypes)
1017 ············{
1018 ················try
1019 ················{
1020 ····················NDO.Mapping.Field f = cl.FindField(s);
1021 ····················if (f == null)
1022 ························continue;
1023 ····················object o = row[f.Column.Name];
1024 ····················string[] arr = s.Split('.');
1025 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1026 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1027 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1028 ····················// Hole das Embedded Object
1029 ····················object parentOb = parentFi.GetValue(pc);
1030
1031 ····················if (parentOb == null)
1032 ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0]));
1033
1034 ····················// Suche darin das Feld mit Namen Arr[1]
1035
1036 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1037 ····················Type childType = childFi.FieldType;
1038
1039 ····················// Don't initialize value types, if DBNull is stored in the field.
1040 ····················// Exception: DateTime and Guid.
1041 ····················if (o == DBNull.Value && childType.IsValueType
1042 ························&& childType != typeof(Guid)
1043 ························&& childType != typeof(DateTime))
1044 ························continue;
1045
1046 ····················if (childType == typeof(DateTime))
1047 ····················{
1048 ························if (o == DBNull.Value)
1049 ····························o = DateTime.MinValue;
1050 ····················}
1051 ····················if (childType.IsClass)
1052 ····················{
1053 ························if (o == DBNull.Value)
1054 ····························o = null;
1055 ····················}
1056
1057 ····················if (childType == typeof (Guid))
1058 ····················{
1059 ························if (o == DBNull.Value)
1060 ····························o = Guid.Empty;
1061 ························if (o is string)
1062 ························{
1063 ····························childFi.SetValue(parentOb, new Guid((string)o));
1064 ························}
1065 ························else if (o is Guid)
1066 ························{
1067 ····························childFi.SetValue(parentOb, o);
1068 ························}
1069 ························else if (o is byte[])
1070 ························{
1071 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1072 ························}
1073 ························else
1074 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1075 ····················}
1076 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1077 ····················{
1078 ························object childOb = childFi.GetValue(parentOb);
1079 ························FieldInfo valueFi = childType.GetField("value__");
1080 ························valueFi.SetValue(childOb, o);
1081 ························childFi.SetValue(parentOb, childOb);
1082 ····················}
1083 ····················else
1084 ····················{
1085 ························childFi.SetValue(parentOb, o);
1086 ····················}
1087 ················}
1088 ················catch (Exception ex)
1089 ················{
1090 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1091
1092 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1093 ················}
1094
1095 ············}
1096 ············
1097 ············try
1098 ············{
1099 ················if (cl.HasEncryptedFields)
1100 ················{
1101 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1102 ····················{
1103 ························string name = field.Column.Name;
1104 ························string s = (string) row[name];
1105 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1106 ························row[name] = es;
1107 ····················}
1108 ················}
1109 ················pc.NDORead(row, fieldNames, startIndex);
1110 ············}
1111 ············catch (Exception ex)
1112 ············{
1113 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1114 ····················+ ex.Message);
1115 ············}
1116 ········}
1117
1118 ········/// <summary>
1119 ········/// Executes a sql script to generate the database tables.
1120 ········/// The function will execute any sql statements in the script
1121 ········/// which are valid according to the
1122 ········/// rules of the underlying database. Result sets are ignored.
1123 ········/// </summary>
1124 ········/// <param name="scriptFile">The script file to execute.</param>
1125 ········/// <param name="conn">A connection object, containing the connection
1126 ········/// string to the database, which should be altered.</param>
1127 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1128 ········/// <remarks>
1129 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1130 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1131 ········/// Their message property will appear in the result array.
1132 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1133 ········/// </remarks>
1134 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1135 ········{
1136 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1137 ········}
1138
1139 ········/// <summary>
1140 ········/// Executes a sql script to generate the database tables.
1141 ········/// The function will execute any sql statements in the script
1142 ········/// which are valid according to the
1143 ········/// rules of the underlying database. Result sets are ignored.
1144 ········/// </summary>
1145 ········/// <param name="scriptFile">The script file to execute.</param>
1146 ········/// <param name="conn">A connection object, containing the connection
1147 ········/// string to the database, which should be altered.</param>
1148 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1149 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1150 ········/// <remarks>
1151 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1152 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1153 ········/// Their message property will appear in the result array.
1154 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1155 ········/// </remarks>
1156 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1157 ········{
1158 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1159 ············string s = sr.ReadToEnd();
1160 ············sr.Close();
1161 ············string[] arr = s.Split(';');
1162 ············string last = arr[arr.Length - 1];
1163 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1164 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1165 ············using (var handler = GetSqlPassThroughHandler())
1166 ············{
1167 ················int i = 0;
1168 ················string ok = "OK";
1169 ················foreach (string statement in arr)
1170 ················{
1171 ····················if (statement != null && statement.Trim() != string.Empty)
1172 ····················{
1173 ························try
1174 ························{
1175 ····························handler.Execute(statement);
1176 ····························result[i] = ok;
1177 ························}
1178 ························catch (Exception ex)
1179 ························{
1180 ····························result[i] = ex.Message;
1181 ························}
1182 ····················}
1183 ····················i++;
1184 ················}
1185 ················CheckEndTransaction(true);
1186 ············}
1187 ············return result;
1188 ········}
1189
1190 ········/// <summary>
1191 ········/// Executes a sql script to generate the database tables.
1192 ········/// The function will execute any sql statements in the script
1193 ········/// which are valid according to the
1194 ········/// rules of the underlying database. Result sets are ignored.
1195 ········/// </summary>
1196 ········/// <param name="scriptFile">The script file to execute.</param>
1197 ········/// <returns></returns>
1198 ········/// <remarks>
1199 ········/// This function takes the first Connection object in the Connections list
1200 ········/// of the Mapping file und executes the script using that connection.
1201 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1202 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1203 ········/// Their message property will appear in the result array.
1204 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1205 ········/// </remarks>
1206 ········public string[] BuildDatabase(string scriptFile)
1207 ········{
1208 ············if (!File.Exists(scriptFile))
1209 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1210 ············if (!this.mappings.Connections.Any())
1211 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1212 ············Connection conn = new Connection( this.mappings );
1213 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1214 ············conn.Name = OnNewConnection( originalConnection );
1215 ············conn.Type = originalConnection.Type;
1216 ············//Connection conn = (Connection) this.mappings.Connections[0];
1217 ············return BuildDatabase(scriptFile, conn);
1218 ········}
1219
1220 ········/// <summary>
1221 ········/// Executes a sql script to generate the database tables.
1222 ········/// The function will execute any sql statements in the script
1223 ········/// which are valid according to the
1224 ········/// rules of the underlying database. Result sets are ignored.
1225 ········/// </summary>
1226 ········/// <returns>
1227 ········/// A string array, containing the error messages produced by the statements
1228 ········/// contained in the script.
1229 ········/// </returns>
1230 ········/// <remarks>
1231 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1232 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1233 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1234 ········/// This function takes the first Connection object in the Connections list
1235 ········/// of the Mapping file und executes the script using that connection.
1236 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1237 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1238 ········/// Their message property will appear in the result array.
1239 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1240 ········/// </remarks>
1241 ········public string[] BuildDatabase()
1242 ········{
1243 ············Assembly ass = Assembly.GetEntryAssembly();
1244 ············if (ass == null)
1245 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1246 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1247 ············return BuildDatabase(file);
1248 ········}
1249
1250 ········/// <summary>
1251 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1252 ········/// </summary>
1253 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1254 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1255 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1256 ········{
1257 ············if (!this.mappings.Connections.Any())
1258 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1259 ············if (conn == null)
1260 ············{
1261 ················conn = new Connection( this.mappings );
1262 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1263 ················conn.Name = OnNewConnection( originalConnection );
1264 ················conn.Type = originalConnection.Type;
1265 ············}
1266
1267 ············return new SqlPassThroughHandler( this, conn );
1268 ········}
1269
1270 ········/// <summary>
1271 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1272 ········/// </summary>
1273 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1274 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1275 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1276 ········{
1277 ············if (!this.mappings.Connections.Any())
1278 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1279 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1280 ············if (conn == null)
1281 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1282 ············return GetSqlPassThroughHandler( conn );
1283 ········}
1284
1285 ········/// <summary>
1286 ········/// Executes a xml script to generate the database tables.
1287 ········/// The function will generate and execute sql statements to perform
1288 ········/// the changes described by the xml.
1289 ········/// </summary>
1290 ········/// <returns></returns>
1291 ········/// <remarks>
1292 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1293 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1294 ········/// This function takes the first Connection object in the Connections list
1295 ········/// of the Mapping file und executes the script using that connection.
1296 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1297 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1298 ········/// Their message property will appear in the result string array.
1299 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1300 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1301 ········/// </remarks>
1302 ········public string[] PerformSchemaTransitions()
1303 ········{
1304 ············Assembly ass = Assembly.GetEntryAssembly();
1305 ············if (ass == null)
1306 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1307 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1308 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1309 ············if (fileNames.Count == 0)
1310 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1311 ············if (fileNames.Count > 1)
1312 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1313 ············return PerformSchemaTransitions( fileNames[0] );
1314 ········}
1315
1316
1317 ········/// <summary>
1318 ········/// Executes a xml script to generate the database tables.
1319 ········/// The function will generate and execute sql statements to perform
1320 ········/// the changes described by the xml.
1321 ········/// </summary>
1322 ········/// <param name="scriptFile">The script file to execute.</param>
1323 ········/// <returns></returns>
1324 ········/// <remarks>
1325 ········/// This function takes the first Connection object in the Connections list
1326 ········/// of the Mapping file und executes the script using that connection.
1327 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1328 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1329 ········/// Their message property will appear in the result string array.
1330 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1331 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1332 ········/// </remarks>
1333 ········public string[] PerformSchemaTransitions(string scriptFile)
1334 ········{
1335 ············if (!File.Exists(scriptFile))
1336 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1337
1338 ············if (!this.mappings.Connections.Any())
1339 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1340 ············Connection conn = new Connection( mappings );
1341 ············Connection originalConnection = mappings.Connections.First();
1342 ············conn.Name = OnNewConnection( originalConnection );
1343 ············conn.Type = originalConnection.Type;
1344 ············return PerformSchemaTransitions(scriptFile, conn);
1345 ········}
1346
1347
1348 ········int CompareFileName( string fn1, string fn2)
1349 ········{
1350 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1351 ············Match match = regex.Match( fn1 );
1352 ············string v1 = match.Groups[1].Value;
1353 ············match = regex.Match( fn2 );
1354 ············string v2 = match.Groups[1].Value;
1355 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1356 ········}
1357
1358 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1359 ········{
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············var resultList = new List<Guid>();
1362
1363 ············using (var handler = GetSqlPassThroughHandler())
1364 ············{
1365 ················string[] TableNames = provider.GetTableNames( connection );
1366 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1367 ················{
1368 var schemaIds = provider. GetQualifiedTableName( "NDOSchemaIds") ;
1369 ····················var sn = provider.GetQuotedName("SchemaName");
1370 ····················var id = provider.GetQuotedName("Id");
1371 ····················string sql = $"SELECT {id} from {schemaIds} WHERE {sn} ";
1372 ····················if (String.IsNullOrEmpty(schemaName))
1373 ························sql += "IS NULL;";
1374 ····················else
1375 ························sql += $"LIKE '{schemaName}'";
1376
1377 ····················using(IDataReader dr = handler.Execute(sql, true))
1378 ····················{
1379 ························while (dr.Read())
1380 ····························resultList.Add( dr.GetGuid( 0 ) );
1381 ····················}
1382 ················}
1383 ················else
1384 ················{
1385 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1386 ····················var gt = typeof(Guid);
1387 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1388 ····················var st = typeof(String);
1389 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1390 ····················var dt = typeof(DateTime);
1391 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1392 ····················string transition = $@"<NdoSchemaTransition>
1393 ····<CreateTable name=""NDOSchemaIds"">
1394 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1395 <CreateColumn name=""Id"" type=""{ gtype} "" size=""36"" isPrimary=""True"" />
1396 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1397 ····</CreateTable>
1398 </NdoSchemaTransition>";
1399 ····················XElement transitionElement = XElement.Parse(transition);
1400
1401 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1402 ····················handler.Execute(sql);
1403 ················}
1404 ················handler.CommitTransaction();
1405 ············}
1406
1407 ············return resultList.ToArray();
1408 ········}
1409
1410 ········/// <summary>
1411 ········/// Executes a xml script to generate the database tables.
1412 ········/// The function will generate and execute sql statements to perform
1413 ········/// the changes described by the xml.
1414 ········/// </summary>
1415 ········/// <param name="scriptFile">The xml script file.</param>
1416 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1417 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1418 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1419 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1420 ········{
1421 ············string schemaName = null;
1422 ············// Gespeicherte Version ermitteln.
1423 ············XElement transitionElements = XElement.Load( scriptFile );
1424 ············if (transitionElements.Attribute( "schemaName" ) != null)
1425 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1426
1427 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1428 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1429 ············var newIds = new List<Guid>();
1430 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1431 ············MemoryStream ms = new MemoryStream();
1432 ············StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
1433 ············bool hasChanges = false;
1434
1435 ············foreach (XElement transitionElement in transitionElements.Elements("NdoSchemaTransition"))
1436 ············{
1437 ················var id = transitionElement.Attribute("id")?.Value;
1438 ················if (id == null)
1439 ····················continue;
1440 ················var gid = new Guid(id);
1441 ················if (installedIds.Contains( gid ))
1442 ····················continue;
1443 ················hasChanges = true;
1444 sw. WriteLine( schemaTransitionGenerator. Generate( transitionElement ) ) ;
1445 ················newIds.Add( gid );
1446 ············}
1447
1448 ············if (!hasChanges)
1449 ················return new string[] { };
1450
1451 ············// dtLiteral contains the leading and trailing quotes
1452 ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now );
1453
1454 var ndoSchemaIds = provider. GetQualifiedTableName( "NDOSchemaIds") ;
1455 ············var schName = provider.GetQuotedName("SchemaName");
1456 ············var idCol = provider.GetQuotedName("Id");
1457 ············var insertTime = provider.GetQuotedName("InsertTime");
1458 ············foreach (var tid in newIds)
1459 ············{
1460 sw. WriteLine( $"INSERT INTO { ndoSchemaIds} ( { schName} , { idCol} , { insertTime} ) VALUES ( '{ schemaName} ', '{ tid} ', { dtLiteral} ) ;" ) ;
1461 ············}
1462
1463 ············sw.Flush();
1464 ············ms.Position = 0L;
1465
1466 ············StreamReader sr = new StreamReader(ms, Encoding.UTF8);
1467 ············string s = sr.ReadToEnd();
1468 ············sr.Close();
1469
1470 ············return InternalPerformSchemaTransitions( ndoConn, s );
1471 ········}
1472
1473 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1474 ········{
1475 ············string[] arr = sql.Split( ';' );
1476
1477 ············string last = arr[arr.Length - 1];
1478 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1479 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1480 ············int i = 0;
1481 ············string ok = "OK";
1482 ············using (var handler = GetSqlPassThroughHandler())
1483 ············{
1484 ················handler.BeginTransaction();
1485 ················var doCommit = true;
1486 ················foreach (string statement in arr)
1487 ················{
1488 if ( !String. IsNullOrWhiteSpace( statement) )
1489 ····················{
1490 ························try
1491 ························{
1492 handler. Execute( statement. Trim( ) ) ;
1493 ····························result[i] = ok;
1494 ························}
1495 ························catch (Exception ex)
1496 ························{
1497 ····························result[i] = ex.Message;
1498 ····························doCommit = false;
1499 ························}
1500 ····················}
1501 ····················i++;
1502 ················}
1503 if ( doCommit)
1504 ····················handler.CommitTransaction();
1505 ················else
1506 ····················AbortTransaction();
1507 ············}
1508
1509 ············return result;
1510 ········}
1511 ········
1512 ········/// <summary>
1513 ········/// Transfers Data from the object to the DataRow
1514 ········/// </summary>
1515 ········/// <param name="pc"></param>
1516 ········/// <param name="row"></param>
1517 ········/// <param name="fieldNames"></param>
1518 ········/// <param name="startIndex"></param>
1519 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1520 ········{
1521 ············Class cl = GetClass( pc );
1522 ············try
1523 ············{
1524 ················pc.NDOWrite(row, fieldNames, startIndex);
1525 ················if (cl.HasEncryptedFields)
1526 ················{
1527 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1528 ····················{
1529 ························string name = field.Column.Name;
1530 ························string s = (string) row[name];
1531 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1532 ························row[name] = es;
1533 ····················}
1534 ················}
1535 ············}
1536 ············catch (Exception ex)
1537 ············{
1538 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1539 ····················+ ex.Message);
1540 ············}
1541
1542 ············if (cl.TypeNameColumn != null)
1543 ············{
1544 ················Type t = pc.GetType();
1545 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1546 ············}
1547
1548 ············var etypes = cl.EmbeddedTypes;
1549 ············foreach(string s in etypes)
1550 ············{
1551 ················try
1552 ················{
1553 ····················NDO.Mapping.Field f = cl.FindField(s);
1554 ····················if (f == null)
1555 ························continue;
1556 ····················string[] arr = s.Split('.');
1557 ····················// Suche Feld mit Namen arr[0] als object
1558 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1559 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1560 ····················Object parentOb = parentFi.GetValue(pc);
1561 ····················if (parentOb == null)
1562 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1563 ····················// Suche darin das Feld mit Namen Arr[1]
1564 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1565 ····················object o = childFi.GetValue(parentOb);
1566 ····················if (o == null
1567 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1568 ························|| o is Guid && (Guid) o == Guid.Empty)
1569 ························o = DBNull.Value;
1570 ····················row[f.Column.Name] = o;
1571 ················}
1572 ················catch (Exception ex)
1573 ················{
1574 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1575
1576 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1577 ················}
1578 ············}
1579 ········}
1580
1581 ········/// <summary>
1582 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1583 ········/// </summary>
1584 ········/// <param name="o">The parent object.</param>
1585 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1586 ········public virtual void LoadField(object o, int fieldOrdinal)
1587 ········{
1588 ············IPersistenceCapable pc = CheckPc(o);
1589 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1590 ············{
1591 ················LoadState ls = pc.NDOLoadState;
1592 ················if (ls.FieldLoadState != null)
1593 ················{
1594 ····················if (ls.FieldLoadState[fieldOrdinal])
1595 ························return;
1596 ················}
1597 ················else
1598 ················{
1599 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1600 ················}
1601 ················LoadData(o);
1602 ········}
1603 ········}
1604
1605 #pragma warning disable 419
1606 ········/// <summary>
1607 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1608 ········/// </summary>
1609 ········/// <param name="o">The hollow object.</param>
1610 ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks>
1611 #pragma warning restore 419
1612 ········public virtual void LoadData( object o )
1613 ········{
1614 ············IPersistenceCapable pc = CheckPc(o);
1615 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1616 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1617 ················return;
1618 ············Class cl = GetClass(pc);
1619 ············IQuery q;
1620 ············q = CreateOidQuery(pc, cl);
1621 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1622
1623 ············var objects = q.Execute();
1624 ············var count = objects.Count;
1625
1626 ············if (count > 1)
1627 ············{
1628 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1629 ············}
1630 ············else if (count == 0)
1631 ············{
1632 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1633 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1634 ············}
1635 ········}
1636
1637 ········/// <summary>
1638 ········/// Creates a new IQuery object for the given type
1639 ········/// </summary>
1640 ········/// <param name="t"></param>
1641 ········/// <param name="oql"></param>
1642 ········/// <param name="hollow"></param>
1643 ········/// <param name="queryLanguage"></param>
1644 ········/// <returns></returns>
1645 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1646 ········{
1647 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1648 ············Type qt = template.MakeGenericType( t );
1649 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1650 ········}
1651
1652 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1653 ········{
1654 ············ArrayList parameters = new ArrayList();
1655 ············string oql = "oid = {0}";
1656 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1657 ············q.Parameters.Add( pc.NDOObjectId );
1658 ············q.AllowSubclasses = false;
1659 ············return q;
1660 ········}
1661
1662 ········/// <summary>
1663 ········/// Mark the object dirty. The current state is
1664 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1665 ········/// object is locked in the cache.
1666 ········/// </summary>
1667 ········/// <param name="pc"></param>
1668 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1669 ········{
1670 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1671 ················return;
1672 ············SaveObjectState(pc);
1673 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1674 ········}
1675
1676 ········/// <summary>
1677 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1678 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1679 ········/// This is done, to allow easy rollback later. Also, the
1680 ········/// object is locked in the cache.
1681 ········/// </summary>
1682 ········/// <param name="pc"></param>
1683 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1684 ········{
1685 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1686
1687 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1688 ············{
1689 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1690 ············}
1691
1692 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1693 ················LoadData(pc);
1694
1695 ············// state is either (Created), Persistent, PersistentDirty
1696 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1697 ············{
1698 ················MarkDirty(pc);
1699 ············}
1700 ········}
1701
1702
1703
1704 ········/// <summary>
1705 ········/// Save current object state in DS and lock the object in the cache.
1706 ········/// The saved state can be used later to retrieve the original object value if the
1707 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1708 ········/// </summary>
1709 ········/// <param name="pc">The object that should be saved</param>
1710 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1711 ········/// <remarks>
1712 ········/// In a data row there are the following things:
1713 ········/// Item································Responsible for writing
1714 ········/// State (own, inherited, embedded)····WriteObject
1715 ········/// TimeStamp····························NDOPersistenceHandler
1716 ········/// Oid····································WriteId
1717 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1718 ········/// </remarks>
1719 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1720 ········{
1721 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1722 ············
1723 ············DataTable table = GetTable(pc);
1724 ············DataRow row = table.NewRow();
1725 ············Class cl = GetClass(pc);
1726 ············WriteObject(pc, row, cl.ColumnNames, 0);
1727 ············WriteIdToRow(pc, row);
1728 ············if (!isDeleting)
1729 ················WriteLostForeignKeysToRow(cl, pc, row);
1730 ············table.Rows.Add(row);
1731 ············row.AcceptChanges();
1732 ············
1733 ············var relations = CollectRelationStates(pc);
1734 ············cache.Lock(pc, row, relations);
1735 ········}
1736
1737 ········private void SaveFakeRow(IPersistenceCapable pc)
1738 ········{
1739 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1740 ············
1741 ············DataTable table = GetTable(pc);
1742 ············DataRow row = table.NewRow();
1743 ············Class pcClass = GetClass(pc);
1744 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1745 ············Class cl = GetClass(pc);
1746 ············//WriteObject(pc, row, cl.FieldNames, 0);
1747 ············WriteIdToRow(pc, row);
1748 ············table.Rows.Add(row);
1749 ············row.AcceptChanges();
1750 ············
1751 ············cache.Lock(pc, row, null);
1752 ········}
1753
1754 ········/// <summary>
1755 ········/// This defines one column of the row, in which we use the
1756 ········/// ColumnError property to determine, if the row is a fake row.
1757 ········/// </summary>
1758 ········/// <param name="pcClass"></param>
1759 ········/// <returns></returns>
1760 ········private string GetFakeRowOidColumnName(Class pcClass)
1761 ········{
1762 ············// In case of several OidColumns the first column defined in the mapping
1763 ············// will be the one, holding the fake row info.
1764 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1765 ········}
1766
1767 ········private bool IsFakeRow(Class cl, DataRow row)
1768 ········{
1769 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1770 ········}
1771
1772 ········/// <summary>
1773 ········/// Make a list of objects persistent.
1774 ········/// </summary>
1775 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1776 ········public void MakePersistent(System.Collections.IList list)
1777 ········{
1778 ············foreach (IPersistenceCapable pc in list)
1779 ············{
1780 ················MakePersistent(pc);
1781 ············}
1782 ········}
1783
1784 ········/// <summary>
1785 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1786 ········/// not duplicated.
1787 ········/// </summary>
1788 ········/// <param name="pc">the parent object of all relations</param>
1789 ········/// <returns></returns>
1790 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1791 ········{
1792 ············// Save state of relations
1793 ············Class c = GetClass(pc);
1794 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1795 ············foreach(Relation r in c.Relations)
1796 ············{
1797 ················if (r.Multiplicity == RelationMultiplicity.Element)
1798 ················{
1799 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1800 ················}
1801 ················else
1802 ················{
1803 ····················IList l = mappings.GetRelationContainer(pc, r);
1804 ····················if(l != null)
1805 ····················{
1806 ························l = (IList) ListCloner.CloneList(l);
1807 ····················}
1808 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1809 ················}
1810 ············}
1811
1812 ············return relations;
1813 ········}
1814
1815
1816 ········/// <summary>
1817 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1818 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1819 ········/// the list had at the beginning of the transaction.
1820 ········/// </summary>
1821 ········/// <param name="pc"></param>
1822 ········/// <param name="relations"></param>
1823 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1824 ········{
1825 ············Class c = GetClass(pc);
1826
1827 ············foreach(var entry in relations)
1828 ············{
1829 ················var r = entry.Key;
1830 ················if (r.Multiplicity == RelationMultiplicity.Element)
1831 ················{
1832 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1833 ················}
1834 ················else
1835 ················{
1836 ····················if (pc.NDOGetLoadState(r.Ordinal))
1837 ····················{
1838 ························// Help GC by clearing lists
1839 ························IList l = mappings.GetRelationContainer(pc, r);
1840 ························if(l != null)
1841 ························{
1842 ····························l.Clear();
1843 ························}
1844 ························// Restore relation
1845 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1846 ····················}
1847 ················}
1848 ············}
1849 ········}
1850
1851
1852 ········/// <summary>
1853 ········/// Generates a query for related objects without mapping table.
1854 ········/// Note: this function can't be called in polymorphic scenarios,
1855 ········/// since they need a mapping table.
1856 ········/// </summary>
1857 ········/// <returns></returns>
1858 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1859 ········{
1860 ············// At this point of execution we know,
1861 ············// that the target type is not polymorphic and is not 1:1.
1862
1863 ············// We can't fetch these objects with an NDOql query
1864 ············// since this would require a relation in the opposite direction
1865
1866 ············IList relatedObjects;
1867 ············if (l != null)
1868 ················relatedObjects = l;
1869 ············else
1870 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1871
1872 ············Type t = r.ReferencedType;
1873 ············Class cl = GetClass( t );
1874 ············var provider = cl.Provider;
1875
1876 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1877 ············var relClass = GetClass( r.ReferencedType );
1878 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1879 ············sb.Append( " WHERE " );
1880 ············int i = 0;
1881 ············List<object> parameters = new List<object>();
1882 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1883 ·············· {
1884 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1885 ·················· sb.Append( " = {" );
1886 ·················· sb.Append(i);
1887 ·················· sb.Append( '}' );
1888 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1889 ·················· if (!isLastElement)
1890 ······················ sb.Append( " AND " );
1891 ·················· i++;
1892 ·············· } );
1893
1894 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1895 ············{
1896 ················sb.Append( " AND " );
1897 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1898 ················sb.Append( " = " );
1899 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1900 ············}
1901
1902 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1903
1904 ············foreach (var p in parameters)
1905 ············{
1906 ················q.Parameters.Add( p );
1907 ············}
1908
1909 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1910
1911 ············IList l2 = q.Execute();
1912
1913 ············foreach (object o in l2)
1914 ················relatedObjects.Add( o );
1915
1916 ············return relatedObjects;
1917 ········}
1918
1919
1920 ········/// <summary>
1921 ········/// Resolves an relation. The loaded objects will be hollow.
1922 ········/// </summary>
1923 ········/// <param name="o">The parent object.</param>
1924 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1925 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1926 ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks>
1927 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1928 ········{
1929 ············IPersistenceCapable pc = CheckPc(o);
1930 ············LoadRelationInternal(pc, fieldName, hollow);
1931 ········}
1932
1933 ········
1934
1935 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1936 ········{
1937 ············IList result = null;
1938
1939 ············if (pc.NDOObjectState == NDOObjectState.Created)
1940 ················return null;
1941
1942 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1943 ················LoadData(pc);
1944
1945 ············if(r.MappingTable == null)
1946 ············{
1947 ················// 1:1 are loaded with LoadData
1948 ················if (r.Multiplicity == RelationMultiplicity.List)
1949 ················{
1950 ····················// Help GC by clearing lists
1951 ····················IList l = mappings.GetRelationContainer(pc, r);
1952 ····················if(l != null)
1953 ························l.Clear();
1954 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1955 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1956 ····················result = relatedObjects;
1957 ················}
1958 ············}
1959 ············else
1960 ············{
1961 ················DataTable dt = null;
1962
1963 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1964 ················{
1965 ····················CheckTransaction( handler, r.MappingTable.Connection );
1966 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1967 ················}
1968
1969 ················IList relatedObjects;
1970 ················if(r.Multiplicity == RelationMultiplicity.Element)
1971 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1972 ················else
1973 ················{
1974 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1975 ····················if(relatedObjects != null)
1976 ························relatedObjects.Clear();··// Objects will be reread
1977 ····················else
1978 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1979 ················}
1980 ····················
1981 ················foreach(DataRow objRow in dt.Rows)
1982 ················{
1983 ····················Type relType;
1984
1985 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1986 ····················{
1987 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1988 ························if (typeCodeObj is System.DBNull)
1989 ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) );
1990 ························relType = typeManager[(int)typeCodeObj];
1991 ························if (relType == null)
1992 ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName));
1993 ····················}························
1994 ····················else
1995 ····················{
1996 ························relType = r.ReferencedType;
1997 ····················}
1998
1999 ····················//TODO: Generic Types: Exctract the type description from the type name column
2000 ····················if (relType.IsGenericTypeDefinition)
2001 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
2002 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
2003 ····················IPersistenceCapable relObj = FindObject(id);
2004 ····················relatedObjects.Add(relObj);
2005 ················}····
2006 ················if (r.Multiplicity == RelationMultiplicity.Element)
2007 ················{
2008 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
2009 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
2010 ················}
2011 ················else
2012 ················{
2013 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
2014 ····················result = relatedObjects;
2015 ················}
2016 ············}
2017 ············// Mark relation as loaded
2018 ············pc.NDOSetLoadState(r.Ordinal, true);
2019 ············return result;
2020 ········}
2021
2022 ········/// <summary>
2023 ········/// Loads elements of a relation
2024 ········/// </summary>
2025 ········/// <param name="pc">The object which needs to load the relation</param>
2026 ········/// <param name="relationName">The name of the relation</param>
2027 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2028 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2029 ········{
2030 ············if (pc.NDOObjectState == NDOObjectState.Created)
2031 ················return null;
2032 ············Class cl = GetClass(pc);
2033
2034 ············Relation r = cl.FindRelation(relationName);
2035
2036 ············if ( r == null )
2037 ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) );
2038
2039 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2040 ················return null;
2041
2042 ············return LoadRelation(pc, r, hollow);
2043 ········}
2044
2045 ········/// <summary>
2046 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2047 ········/// a list of objects that has been read from the DB.
2048 ········/// </summary>
2049 ········/// <param name="pc">The parent object of the relations</param>
2050 ········/// <param name="row">A data row containing the state of the object</param>
2051 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2052 ········{
2053 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2054 ············Class cl = GetClass(pc);
2055 ············foreach(Relation r in cl.Relations)
2056 ············{
2057 ················if(r.MappingTable == null)
2058 ················{
2059 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2060 ····················{
2061 ························bool isNull = false;
2062 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2063 ························{
2064 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2065 ························}
2066 ························if (isNull)
2067 ························{
2068 ····························mappings.SetRelationField(pc, r.FieldName, null);
2069 ························}
2070 ························else
2071 ························{
2072 ····························Type relType;
2073 ····························if (r.HasSubclasses)
2074 ····························{
2075 ································object o = row[r.ForeignKeyTypeColumnName];
2076 ································if (o == DBNull.Value)
2077 ····································throw new NDOException(75, String.Format(
2078 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2079 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2080 ································relType = typeManager[(int)o];
2081 ····························}
2082 ····························else
2083 ····························{
2084 ································relType = r.ReferencedType;
2085 ····························}
2086 ····························if (relType == null)
2087 ····························{
2088 ································throw new NDOException(75, String.Format(
2089 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2090 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2091 ····························}
2092 ····
2093 ····························int count = r.ForeignKeyColumns.Count();
2094 ····························object[] keydata = new object[count];
2095 ····························int i = 0;
2096 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2097 ····························{
2098 ································keydata[i++] = row[fkColumn.Name];
2099 ····························}
2100
2101 ····························Type oidType = relType;
2102 ····························if (oidType.IsGenericTypeDefinition)
2103 ································oidType = mappings.GetRelationFieldType(r);
2104
2105 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2106 ····························if(childOid.IsValid())
2107 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2108 ····························else
2109 ································mappings.SetRelationField(pc, r.FieldName, null);
2110
2111 ························}
2112 ························pc.NDOSetLoadState(r.Ordinal, true);
2113 ····················}
2114 ················}
2115 ············}
2116 ········}
2117
2118 ········
2119 ········/// <summary>
2120 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2121 ········/// </summary>
2122 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2123 ········/// <param name="t">The type of the object, the id will belong to.</param>
2124 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2125 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2126 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2127 ········{
2128 ············return new ObjectId(oid.Id, t);
2129 ········}
2130
2131 ········/*
2132 ········/// <summary>
2133 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2134 ········/// </summary>
2135 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2136 ········/// <param name="t">The type of the object.</param>
2137 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2138 ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks>
2139 ········public ObjectId NewObjectId(object keyData, Type t)
2140 ········{
2141 ············if (keyData == null || keyData == DBNull.Value)
2142 ················return ObjectId.InvalidId;
2143
2144 ············Class cl =··GetClass(t);············
2145 ············
2146 ············if (cl.Oid.FieldType == typeof(int))
2147 ················return new ObjectId(new Int32Key(t, (int)keyData));
2148 ············else if (cl.Oid.FieldType == typeof(string))
2149 ················return new ObjectId(new StringKey(t, (String) keyData));
2150 ············else if (cl.Oid.FieldType == typeof(Guid))
2151 ················if (keyData is string)
2152 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2153 ················else
2154 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2155 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2156 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2157 ············else
2158 ················return ObjectId.InvalidId;
2159 ········}
2160 ········*/
2161
2162
2163 ········/*
2164 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2165 ····················{
2166 ························// The column, which hold the oid gets overwritten, if
2167 ························// Oid.FieldName contains a value.
2168 */
2169 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2170 ········{
2171 ············Class cl = GetClass(pc);
2172
2173 ············if (cl.Oid.IsDependent)
2174 ················return;
2175
2176 ············Key key = pc.NDOObjectId.Id;
2177
2178 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2179 ············{
2180 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2181 ················if (oidColumn.FieldName != null)
2182 ················{
2183 ····················row[oidColumn.Name] = key[i];
2184 ················}
2185 ············}
2186 ········}
2187
2188 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2189 ········{
2190 ············NDO.Mapping.Class cl = GetClass(pc);
2191 ············ObjectId oid = pc.NDOObjectId;
2192
2193 ············if (cl.TimeStampColumn != null)
2194 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2195
2196 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2197 ················return;
2198
2199 ············Key key = oid.Id;
2200
2201 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2202 ············{
2203 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2204 ················row[oidColumn.Name] = key[i];
2205 ············}
2206 ········}
2207
2208 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2209 ········{
2210 ············ObjectId oid = pc.NDOObjectId;
2211 ············NDO.Mapping.Class cl = GetClass(pc);
2212
2213 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2214 ················return;
2215
2216 ············Key key = oid.Id;
2217
2218 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2219 ············{
2220 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2221 ················object o = row[oidColumn.Name];
2222 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2223 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2224 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2225 ····················key[i] = new Guid((string)o);
2226 ················else
2227 ····················key[i] = o;
2228 ············}
2229
2230 ········}
2231
2232 ········private void ReadId (Cache.Entry e)
2233 ········{
2234 ············ReadIdFromRow(e.pc, e.row);
2235 ········}
2236
2237 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2238 ········{
2239 ············WriteObject(pc, row, fieldNames, 0);
2240 ········}
2241
2242 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2243 ········{
2244 ············if (cl.TimeStampColumn == null)
2245 ················return;
2246 ············object col = row[cl.TimeStampColumn];
2247 ············if (col is String)
2248 ················pc.NDOTimeStamp = new Guid(col.ToString());
2249 ············else
2250 ················pc.NDOTimeStamp = (Guid) col;
2251 ········}
2252
2253
2254
2255 ········private void ReadTimeStamp(Cache.Entry e)
2256 ········{
2257 ············IPersistenceCapable pc = e.pc;
2258 ············NDO.Mapping.Class cl = GetClass(pc);
2259 ············Debug.Assert(!IsFakeRow(cl, e.row));
2260 ············if (cl.TimeStampColumn == null)
2261 ················return;
2262 ············if (e.row[cl.TimeStampColumn] is String)
2263 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2264 ············else
2265 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2266 ········}
2267
2268 ········/// <summary>
2269 ········/// Determines, if any objects are new, changed or deleted.
2270 ········/// </summary>
2271 ········public virtual bool HasChanges
2272 ········{
2273 ············get
2274 ············{
2275 ················return cache.LockedObjects.Count > 0;
2276 ············}
2277 ········}
2278
2279 ········/// <summary>
2280 ········/// Do the update for all rows in the ds.
2281 ········/// </summary>
2282 ········/// <param name="types">Types with changes.</param>
2283 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2284 ········/// <remarks>
2285 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2286 ········/// </remarks>
2287 ········private void UpdateTypes(IList types, bool delete)
2288 ········{
2289 ············foreach(Type t in types)
2290 ············{
2291 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2292 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2293 ················{
2294 ····················CheckTransaction( handler, t );
2295 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2296 ····················handler.ConcurrencyError += ceh;
2297 ····················try
2298 ····················{
2299 ························DataTable dt = GetTable(t);
2300 ························if (delete)
2301 ····························handler.UpdateDeletedObjects( dt );
2302 ························else
2303 ····························handler.Update( dt );
2304 ····················}
2305 ····················finally
2306 ····················{
2307 ························handler.ConcurrencyError -= ceh;
2308 ····················}
2309 ················}
2310 ············}
2311 ········}
2312
2313 ········internal void UpdateCreatedMappingTableEntries()
2314 ········{
2315 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2316 ············{
2317 ················if (!e.DeleteEntry)
2318 ····················WriteMappingTableEntry(e);
2319 ············}
2320 ············// Now update all mapping tables
2321 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2322 ············{
2323 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2324 ················handler.Update(ds);
2325 ············}
2326 ········}
2327
2328 ········internal void UpdateDeletedMappingTableEntries()
2329 ········{
2330 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2331 ············{
2332 ················if (e.DeleteEntry)
2333 ····················WriteMappingTableEntry(e);
2334 ············}
2335 ············// Now update all mapping tables
2336 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2337 ············{
2338 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2339 ················handler.Update(ds);
2340 ············}
2341 ········}
2342
2343 ········/// <summary>
2344 ········/// Save all changed object into the DataSet and update the DB.
2345 ········/// When a newly created object is written to DB, the key might change. Therefore,
2346 ········/// the id is updated and the object is removed and re-inserted into the cache.
2347 ········/// </summary>
2348 ········public virtual void Save(bool deferCommit = false)
2349 ········{
2350 ············this.DeferredMode = deferCommit;
2351 ············var htOnSaving = new HashSet<ObjectId>();
2352 ············for(;;)
2353 ············{
2354 ················// We need to work on a copy of the locked objects list,
2355 ················// since the handlers might add more objects to the cache
2356 ················var lockedObjects = cache.LockedObjects.ToList();
2357 ················int count = lockedObjects.Count;
2358 ················foreach(Cache.Entry e in lockedObjects)
2359 ················{
2360 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2361 ····················{
2362 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2363 ························if (ipn != null)
2364 ························{
2365 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2366 ····························{
2367 ································ipn.OnSaving();
2368 ································htOnSaving.Add(e.pc.NDOObjectId);
2369 ····························}
2370 ························}
2371 ····················}
2372 ················}
2373 ················// The system is stable, if the count doesn't change
2374 ················if (cache.LockedObjects.Count == count)
2375 ····················break;
2376 ············}
2377
2378 ············if (this.OnSavingEvent != null)
2379 ············{
2380 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2381 ················foreach(Cache.Entry e in cache.LockedObjects)
2382 ····················onSavingObjects.Add(e.pc);
2383 ················OnSavingEvent(onSavingObjects);
2384 ············}
2385
2386 ············List<Type> types = new List<Type>();
2387 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2388 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2389 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2390 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2391 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2392
2393 ············// Save current state in DataSet
2394 ············foreach (Cache.Entry e in cache.LockedObjects)
2395 ············{
2396 ················Type objType = e.pc.GetType();
2397
2398 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2399 ····················objType = objType.GetGenericTypeDefinition();
2400
2401 ················Class cl = GetClass(e.pc);
2402 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2403 ················if(!types.Contains(objType))
2404 ················{
2405 ····················//Debug.WriteLine("Added··type " + objType.Name);
2406 ····················types.Add(objType);
2407 ················}
2408 ················NDOObjectState objectState = e.pc.NDOObjectState;
2409 ················if(objectState == NDOObjectState.Deleted)
2410 ················{
2411 ····················deletedObjects.Add(e.pc);
2412 ················}
2413 ················else if(objectState == NDOObjectState.Created)
2414 ················{
2415 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2416 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2417 ····················WriteForeignKeysToRow(e.pc, e.row);
2418 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2419 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2420 ····················//····················i = 0;
2421 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2422 ····················//····················{
2423 ····················//························rows[i++] = e2.row;
2424 ····················//····················}
2425 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2426 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2427
2428 ····················addedCacheEntries.Add(e);
2429 ····················addedObjects.Add( e.pc );
2430 ····················//····················objectState = NDOObjectState.Persistent;
2431 ················}
2432 ················else
2433 ················{
2434 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2435 ························changedObjects.Add( e.pc );
2436 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2437 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2438 ················}
2439 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2440 ················{
2441 ····················hollowModeObjects.Add(e.pc);
2442 ················}
2443 ············}
2444
2445 ············// Before we delete any db rows, we have to make sure, to delete mapping
2446 ············// table entries first, which might have relations to the db rows to be deleted
2447 ············UpdateDeletedMappingTableEntries();
2448
2449 ············// Update DB
2450 ············if (ds.HasChanges())
2451 ············{
2452
2453 ················// We need the reversed update order for deletions.
2454 ················types.Sort( ( t1, t2 ) =>
2455 ················{
2456 ····················int i1 = mappings.GetUpdateOrder( t1 );
2457 ····················int i2 = mappings.GetUpdateOrder( t2 );
2458 ····················if (i1 < i2)
2459 ····················{
2460 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2461 ····························i1 += 100000;
2462 ····················}
2463 ····················else
2464 ····················{
2465 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2466 ····························i2 += 100000;
2467 ····················}
2468 ····················return i2 - i1;
2469 ················} );
2470
2471 ················// Delete records first
2472
2473 ················UpdateTypes(types, true);
2474
2475 ················// Now do all other updates in correct order.
2476 ················types.Reverse();
2477
2478 ················UpdateTypes(types, false);
2479 ················
2480 ················ds.AcceptChanges();
2481 ················if(createdDirectObjects.Count > 0)
2482 ················{
2483 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2484 ····················// They must be written again to store the correct foreign keys.
2485 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2486 ····················{
2487 ························Class cl = GetClass(pc);
2488 ························DataRow r = this.cache.GetDataRow(pc);
2489 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2490 ························object o = r[fakeColumnName];
2491 ························r[fakeColumnName] = o;
2492 ····················}
2493
2494 ····················UpdateTypes(types, false);
2495 ················}
2496
2497 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2498 ················foreach(Cache.Entry e in addedCacheEntries)
2499 ················{
2500 ····················cache.DeregisterLockedObject(e.pc);
2501 ····················ReadId(e);
2502 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2503 ················}
2504
2505 ················// Now update all mapping tables. Because of possible subclasses, there is no
2506 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2507 ················// tables only after all other objects have been written to ensure correct foreign keys.
2508 ················UpdateCreatedMappingTableEntries();
2509
2510 ················// The rows may contain now new Ids, which should be
2511 ················// stored in the lostRowInfo's before the rows get detached
2512 ················foreach(Cache.Entry e in cache.LockedObjects)
2513 ················{
2514 ····················if (e.row.RowState != DataRowState.Detached)
2515 ····················{
2516 ························IPersistenceCapable pc = e.pc;
2517 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2518 ····················}
2519 ················}
2520
2521 ················ds.AcceptChanges();
2522 ············}
2523
2524 ············EndSave(!deferCommit);
2525
2526 ············foreach(IPersistenceCapable pc in deletedObjects)
2527 ············{
2528 ················MakeObjectTransient(pc, false);
2529 ············}
2530
2531 ············ds.Clear();
2532 ············mappingHandler.Clear();
2533 ············createdDirectObjects.Clear();
2534 ············createdMappingTableObjects.Clear();
2535 ············this.relationChanges.Clear();
2536
2537 ············if(hollowMode)
2538 ············{
2539 ················MakeHollow(hollowModeObjects);
2540 ············}
2541
2542 ············if (this.OnSavedEvent != null)
2543 ············{
2544 ················AuditSet auditSet = new AuditSet()
2545 ················{
2546 ····················ChangedObjects = changedObjects,
2547 ····················CreatedObjects = addedObjects,
2548 ····················DeletedObjects = deletedObjects
2549 ················};
2550 ················this.OnSavedEvent( auditSet );
2551 ············}
2552 ········}
2553
2554 ········private void EndSave(bool forceCommit)
2555 ········{
2556 ············foreach(Cache.Entry e in cache.LockedObjects)
2557 ············{
2558 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2559 ····················this.ReadTimeStamp(e);
2560 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2561 ············}
2562
2563 ············cache.UnlockAll();
2564
2565 ············CheckEndTransaction(forceCommit);
2566 ········}
2567
2568 ········/// <summary>
2569 ········/// Write all foreign keys for 1:1-relations.
2570 ········/// </summary>
2571 ········/// <param name="pc">The persistent object.</param>
2572 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2573 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2574 ········{
2575 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2576 ············{
2577 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2578 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2579
2580 ················if ( relObj != null )
2581 ················{
2582 ····················int i = 0;
2583 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2584 ····················{
2585 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2586 ····················}
2587 ····················if ( r.ForeignKeyTypeColumnName != null )
2588 ····················{
2589 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2590 ····················}
2591 ················}
2592 ················else
2593 ················{
2594 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2595 ····················{
2596 ························pcRow[fkColumn.Name] = DBNull.Value;
2597 ····················}
2598 ····················if ( r.ForeignKeyTypeColumnName != null )
2599 ····················{
2600 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2601 ····················}
2602 ················}
2603 ············}
2604 ········}
2605
2606
2607
2608 ········/// <summary>
2609 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2610 ········/// </summary>
2611 ········/// <param name="e">the mapping table entry</param>
2612 ········private void WriteMappingTableEntry(MappingTableEntry e)
2613 ········{
2614 ············IPersistenceCapable pc = e.ParentObject;
2615 ············IPersistenceCapable relObj = e.RelatedObject;
2616 ············Relation r = e.Relation;
2617 ············DataTable dt = GetTable(r.MappingTable.TableName);
2618 ············DataRow row = dt.NewRow();
2619 ············int i = 0;
2620 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2621 ············{
2622 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2623 ············}
2624 ············i = 0;
2625 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2626 ············{
2627 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2628 ············}
2629
2630 ············if (r.ForeignKeyTypeColumnName != null)
2631 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2632 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2633 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2634
2635 ············dt.Rows.Add(row);
2636 ············if(e.DeleteEntry)
2637 ············{
2638 ················row.AcceptChanges();
2639 ················row.Delete();
2640 ············}
2641
2642 ············IMappingTableHandler handler;
2643 ············if (!mappingHandler.TryGetValue( r, out handler ))
2644 ············{
2645 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2646 ············}
2647 ········}
2648
2649
2650 ········/// <summary>
2651 ········/// Undo changes of a certain object
2652 ········/// </summary>
2653 ········/// <param name="o">Object to undo</param>
2654 ········public void Restore(object o)
2655 ········{············
2656 ············IPersistenceCapable pc = CheckPc(o);
2657 ············Cache.Entry e = null;
2658 ············foreach (Cache.Entry entry in cache.LockedObjects)
2659 ············{
2660 ················if (entry.pc == pc)
2661 ················{
2662 ····················e = entry;
2663 ····················break;
2664 ················}
2665 ············}
2666 ············if (e == null)
2667 ················return;
2668 ············Class cl = GetClass(e.pc);
2669 ············switch (pc.NDOObjectState)
2670 ············{
2671 ················case NDOObjectState.PersistentDirty:
2672 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2673 ····················foreach(Relation r in cl.Relations)
2674 ····················{
2675 ························if (r.Multiplicity == RelationMultiplicity.Element)
2676 ························{
2677 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2678 ····························if (subPc != null && cache.IsLocked(subPc))
2679 ································Restore(subPc);
2680 ························}
2681 ························else
2682 ························{
2683 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2684 ································continue;
2685 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2686 ····························if (subList != null)
2687 ····························{
2688 ································foreach(IPersistenceCapable subPc2 in subList)
2689 ································{
2690 ····································if (cache.IsLocked(subPc2))
2691 ········································Restore(subPc2);
2692 ································}
2693 ····························}
2694 ························}
2695 ····················}
2696 ····················RestoreRelatedObjects(pc, e.relations);
2697 ····················e.row.RejectChanges();
2698 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2699 ····················cache.Unlock(pc);
2700 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2701 ····················break;
2702 ················case NDOObjectState.Created:
2703 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2704 ····················cache.Unlock(pc);
2705 ····················MakeObjectTransient(pc, true);
2706 ····················break;
2707 ················case NDOObjectState.Deleted:
2708 ····················if (!this.IsFakeRow(cl, e.row))
2709 ····················{
2710 ························e.row.RejectChanges();
2711 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2712 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2713 ····················}
2714 ····················else
2715 ····················{
2716 ························e.row.RejectChanges();
2717 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2718 ····················}
2719 ····················cache.Unlock(pc);
2720 ····················break;
2721
2722 ············}
2723 ········}
2724
2725 ········/// <summary>
2726 ········/// Aborts a pending transaction without restoring the object state.
2727 ········/// </summary>
2728 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2729 ········public virtual void AbortTransaction()
2730 ········{
2731 ············TransactionScope.Dispose();
2732 ········}
2733
2734 ········/// <summary>
2735 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2736 ········/// </summary>
2737 ········public virtual void Abort()
2738 ········{
2739 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2740 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2741 ············// individual row.
2742 ············createdDirectObjects.Clear();
2743 ············createdMappingTableObjects.Clear();
2744 ············ArrayList deletedObjects = new ArrayList();
2745 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2746
2747 ············// Read all objects from DataSet
2748 ············foreach (Cache.Entry e in cache.LockedObjects)
2749 ············{
2750 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2751
2752 ················Class cl = GetClass(e.pc);
2753 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2754 ················if (!isFakeRow)
2755 ················{
2756 ····················RestoreRelatedObjects(e.pc, e.relations);
2757 ················}
2758 ················else
2759 ················{
2760 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2761 ················}
2762
2763 ················switch(e.pc.NDOObjectState)
2764 ················{
2765 ····················case NDOObjectState.Created:
2766 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2767 ························deletedObjects.Add(e.pc);
2768 ························break;
2769
2770 ····················case NDOObjectState.PersistentDirty:
2771 ························e.row.RejectChanges();
2772 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2773 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2774 ························break;
2775
2776 ····················case NDOObjectState.Deleted:
2777 ························if (!isFakeRow)
2778 ························{
2779 ····························e.row.RejectChanges();
2780 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2781 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2782 ························}
2783 ························else
2784 ························{
2785 ····························e.row.RejectChanges();
2786 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2787 ························}
2788 ························break;
2789
2790 ····················default:
2791 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2792 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2793 ························//break;
2794 ················}
2795 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2796 ················{
2797 ····················hollowModeObjects.Add(e.pc);
2798 ················}
2799 ············}
2800 ············cache.UnlockAll();
2801 ············foreach(IPersistenceCapable pc in deletedObjects)
2802 ············{
2803 ················MakeObjectTransient(pc, true);
2804 ············}
2805 ············ds.Clear();
2806 ············mappingHandler.Clear();
2807 ············if(hollowMode)
2808 ············{
2809 ················MakeHollow(hollowModeObjects);
2810 ············}
2811
2812 ············this.relationChanges.Clear();
2813
2814
2815 ············AbortTransaction();
2816 ········}
2817
2818
2819 ········/// <summary>
2820 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2821 ········/// disable future access.
2822 ········/// </summary>
2823 ········/// <param name="pc"></param>
2824 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2825 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2826 ········{
2827 ············cache.Deregister(pc);
2828 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2829 ············if(removeId)
2830 ············{
2831 ················pc.NDOObjectId = null;
2832 ············}
2833 ············pc.NDOObjectState = NDOObjectState.Transient;
2834 ············pc.NDOStateManager = null;
2835 ········}
2836
2837 ········/// <summary>
2838 ········/// Makes an object transient.
2839 ········/// The object can be used afterwards, but changes will not be saved in the database.
2840 ········/// </summary>
2841 ········/// <remarks>
2842 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2843 ········/// </remarks>
2844 ········/// <param name="o">The object to detach.</param>
2845 ········public void MakeTransient(object o)
2846 ········{
2847 ············IPersistenceCapable pc = CheckPc(o);
2848 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2849 ············{
2850 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2851 ············}
2852
2853 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2854 ············{
2855 ················LoadData(pc);
2856 ············}
2857 ············MakeObjectTransient(pc, true);
2858 ········}
2859
2860
2861 ········/// <summary>
2862 ········/// Make all objects of a list transient.
2863 ········/// </summary>
2864 ········/// <param name="list">the list of transient objects</param>
2865 ········public void MakeTransient(System.Collections.IList list)
2866 ········{
2867 ············foreach (IPersistenceCapable pc in list)
2868 ················MakeTransient(pc);
2869 ········}
2870
2871 ········/// <summary>
2872 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2873 ········/// </summary>
2874 ········/// <param name="o">The object to remove</param>
2875 ········public void Delete(object o)
2876 ········{
2877 ············IPersistenceCapable pc = CheckPc(o);
2878 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2879 ············{
2880 ················throw new NDOException( 120, "Can't delete transient object" );
2881 ············}
2882 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2883 ············{
2884 ················Delete(pc, true);
2885 ············}
2886 ········}
2887
2888
2889 ········private void LoadAllRelations(object o)
2890 ········{
2891 ············IPersistenceCapable pc = CheckPc(o);
2892 ············Class cl = GetClass(pc);
2893 ············foreach(Relation r in cl.Relations)
2894 ············{
2895 ················if (!pc.NDOGetLoadState(r.Ordinal))
2896 ····················LoadRelation(pc, r, true);
2897 ············}
2898 ········}
2899
2900
2901 ········/// <summary>
2902 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2903 ········/// </summary>
2904 ········/// <remarks>
2905 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2906 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2907 ········/// the parent object.
2908 ········/// </remarks>
2909 ········/// <param name="pc">the object to remove</param>
2910 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2911 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2912 ········{
2913 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2914 ············//Debug.Indent();
2915 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2916 ············if (idn != null)
2917 ················idn.OnDelete();
2918
2919 ············LoadAllRelations(pc);
2920 ············DeleteRelatedObjects(pc, checkAssoziations);
2921
2922 ············switch(pc.NDOObjectState)
2923 ············{
2924 ················case NDOObjectState.Transient:
2925 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2926
2927 ················case NDOObjectState.Created:
2928 ····················DataRow row = cache.GetDataRow(pc);
2929 ····················row.Delete();
2930 ····················ArrayList cdosToDelete = new ArrayList();
2931 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2932 ························if ((object)cdo == (object)pc)
2933 ····························cdosToDelete.Add(cdo);
2934 ····················foreach (object o in cdosToDelete)
2935 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2936 ····················MakeObjectTransient(pc, true);
2937 ····················break;
2938 ················case NDOObjectState.Persistent:
2939 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2940 ························SaveObjectState(pc, true);
2941 ····················row = cache.GetDataRow(pc);
2942 ····················row.Delete();
2943 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2944 ····················break;
2945 ················case NDOObjectState.Hollow:
2946 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2947 ························SaveFakeRow(pc);
2948 ····················row = cache.GetDataRow(pc);
2949 ····················row.Delete();
2950 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2951 ····················break;
2952
2953 ················case NDOObjectState.PersistentDirty:
2954 ····················row = cache.GetDataRow(pc);
2955 ····················row.Delete();
2956 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2957 ····················break;
2958
2959 ················case NDOObjectState.Deleted:
2960 ····················break;
2961 ············}
2962
2963 ············//Debug.Unindent();
2964 ········}
2965
2966
2967 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2968 ········{
2969 ············MappingTableEntry mte = null;
2970 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2971 ············{
2972 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2973 ················{
2974 ····················mte = e;
2975 ····················break;
2976 ················}
2977 ············}
2978
2979 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2980 ············{
2981 ················if (mte != null)
2982 ····················createdMappingTableObjects.Remove(mte);
2983 ············}
2984 ············else
2985 ············{
2986 ················if (mte == null)
2987 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2988 ············}
2989 ········}
2990
2991 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2992 ········{
2993 ············// Two tasks: a) Null the foreign key
2994 ············//··············b) remove the element from the foreign container
2995
2996 ············if (!r.Bidirectional)
2997 ················return;
2998
2999 ············// this keeps the oid valid
3000 ············if (GetClass(child.GetType()).Oid.IsDependent)
3001 ················return;
3002
3003 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
3004 ············{
3005 ················LoadAndMarkDirty(child);
3006 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3007 ············}
3008 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
3009 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
3010 ············{
3011 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
3012 ····················LoadRelation(child, r.ForeignRelation, true);
3013 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
3014 ················if (l == null)
3015 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3016 ················ObjectListManipulator.Remove(l, pc);
3017 ················// Don't need to delete the mapping table entry, because that was done
3018 ················// through the parent.
3019 ············}
3020 ········}
3021
3022 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3023 ········{
3024 ············// 1) Element····nomap····ass
3025 ············// 2) Element····nomap····comp
3026 ············// 3) Element····map········ass
3027 ············// 4) Element····map········comp
3028 ············// 5) List········nomap····ass
3029 ············// 6) List········nomap····comp
3030 ············// 7) List········map········ass
3031 ············// 8) List········map········comp
3032
3033 ············// Two tasks: Null foreign key and, if Composition, delete the child
3034
3035 ············// If Mapping Table, delete the entry - 3,7
3036 ············// If List and assoziation, null the foreign key in the foreign class - 5
3037 ············// If Element, null the foreign key in the own class 1,2,3,4
3038 ············// If composition, delete the child 2,4,6,8
3039
3040 ············// If the relObj is newly created
3041 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3042 ············Class childClass = GetClass(child);
3043
3044 ············if (r.MappingTable != null)··// 3,7
3045 ············{················
3046 ················DeleteMappingTableEntry(pc, r, child);
3047 ············}
3048 ············else if (r.Multiplicity == RelationMultiplicity.List
3049 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3050 ············{················
3051 ················LoadAndMarkDirty(child);
3052 ················DataRow row = this.cache.GetDataRow(child);
3053 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3054 ················{
3055 ····················row[fkColumnn.Name] = DBNull.Value;
3056 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3057 ················}
3058 ············}
3059 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3060 ············{
3061 ················LoadAndMarkDirty(pc);
3062 ············}
3063 ············if (r.Composition || childClass.Oid.IsDependent)
3064 ············{
3065 #if DEBUG
3066 ················if (child.NDOObjectState == NDOObjectState.Transient)
3067 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3068 #endif
3069 ················// Deletes the foreign key in case of List multiplicity
3070 ················// In case of Element multiplicity, the parent is either deleted,
3071 ················// or RemoveRelatedObject is called because the relation has been nulled.
3072 ················Delete(child);··
3073 ············}
3074 ········}
3075
3076 ········/// <summary>
3077 ········/// Remove a related object
3078 ········/// </summary>
3079 ········/// <param name="pc"></param>
3080 ········/// <param name="r"></param>
3081 ········/// <param name="child"></param>
3082 ········/// <param name="calledFromStateManager"></param>
3083 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3084 ········{
3085 ············//TODO: We need a relation management, which is independent of
3086 ············//the state management of an object. At the moment the relation
3087 ············//lists or elements are cached for restore, if an object is marked dirty.
3088 ············//Thus we have to mark dirty our parent object in any case at the moment.
3089 ············if (calledFromStateManager)
3090 ················MarkDirty(pc);
3091
3092 ············// Object can be deleted in an OnDelete-Handler
3093 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3094 ················return;
3095 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3096 ············// Preconditions
3097 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3098 ············// The Object state is checked there
3099
3100 ············// If there is a composition in the opposite direction
3101 ············// && the other direction hasn't been processed
3102 ············// throw an exception.
3103 ············// If an exception is thrown at this point, have a look at IsLocked....
3104 ············if (r.Bidirectional && r.ForeignRelation.Composition
3105 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3106 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3107
3108 ············if (!removeLock.GetLock(pc, r, child))
3109 ················return;
3110
3111 ············try
3112 ············{
3113 ················// must be in this order, since the child
3114 ················// can be deleted in DeleteOrNullRelation
3115 ················//if (changeForeignRelations)
3116 ················DeleteOrNullForeignRelation(pc, r, child);
3117 ················DeleteOrNullRelation(pc, r, child);
3118 ············}
3119 ············finally
3120 ············{
3121 ················removeLock.Unlock(pc, r, child);
3122 ············}
3123
3124 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3125 ········}
3126
3127 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3128 ········{
3129 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3130 ············//············Debug.Indent();
3131 ············if (r.Multiplicity == RelationMultiplicity.Element)
3132 ············{
3133 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3134 ················if(child != null)
3135 ················{
3136 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3137 ····················//····················{
3138 ····················//························if (!r.ForeignRelation.Composition)
3139 ····················//························{
3140 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3141 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3142 ····················//························}
3143 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3144 ····················//························else
3145 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3146 ····················//····················}
3147 ····················InternalRemoveRelatedObject(pc, r, child, false);
3148 ················}
3149 ············}
3150 ············else
3151 ············{
3152 ················IList list = mappings.GetRelationContainer(pc, r);
3153 ················if(list != null && list.Count > 0)
3154 ················{
3155 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3156 ····················//····················{
3157 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3158 ····················//····················}
3159 ····················// Since RemoveRelatedObjects probably changes the list,
3160 ····················// we iterate through a copy of the list.
3161 ····················ArrayList al = new ArrayList(list);
3162 ····················foreach(IPersistenceCapable relObj in al)
3163 ····················{
3164 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3165 ····················}
3166 ················}
3167 ············}
3168 ············//············Debug.Unindent();
3169 ········}
3170
3171 ········/// <summary>
3172 ········/// Remove all related objects from a parent.
3173 ········/// </summary>
3174 ········/// <param name="pc">the parent object</param>
3175 ········/// <param name="checkAssoziations"></param>
3176 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3177 ········{
3178 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3179 ············//············Debug.Indent();
3180 ············// delete all related objects:
3181 ············Class parentClass = GetClass(pc);
3182 ············// Remove Assoziations first
3183 ············foreach(Relation r in parentClass.Relations)
3184 ············{
3185 ················if (!r.Composition)
3186 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3187 ············}
3188 ············foreach(Relation r in parentClass.Relations)
3189 ············{
3190 ················if (r.Composition)
3191 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3192 ············}
3193
3194 ············//············Debug.Unindent();
3195 ········}
3196
3197 ········/// <summary>
3198 ········/// Delete a list of objects
3199 ········/// </summary>
3200 ········/// <param name="list">the list of objects to remove</param>
3201 ········public void Delete(IList list)
3202 ········{
3203 ············for (int i = 0; i < list.Count; i++)
3204 ············{
3205 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3206 ················Delete(pc);
3207 ············}
3208 ········}
3209
3210 ········/// <summary>
3211 ········/// Make object hollow. All relations will be unloaded and object data will be
3212 ········/// newly fetched during the next touch of a persistent field.
3213 ········/// </summary>
3214 ········/// <param name="o"></param>
3215 ········public virtual void MakeHollow(object o)
3216 ········{
3217 ············IPersistenceCapable pc = CheckPc(o);
3218 ············MakeHollow(pc, false);
3219 ········}
3220
3221 ········/// <summary>
3222 ········/// Make the object hollow if it is persistent. Unload all complex data.
3223 ········/// </summary>
3224 ········/// <param name="o"></param>
3225 ········/// <param name="recursive">if true then unload related objects as well</param>
3226 ········public virtual void MakeHollow(object o, bool recursive)
3227 ········{
3228 ············IPersistenceCapable pc = CheckPc(o);
3229 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3230 ················return;
3231 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3232 ············{
3233 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3234 ············}
3235 ············pc.NDOObjectState = NDOObjectState.Hollow;
3236 ············MakeRelationsHollow(pc, recursive);
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all objects of a list hollow.
3241 ········/// </summary>
3242 ········/// <param name="list">the list of objects that should be made hollow</param>
3243 ········public virtual void MakeHollow(System.Collections.IList list)
3244 ········{
3245 ············MakeHollow(list, false);
3246 ········}
3247
3248 ········/// <summary>
3249 ········/// Make all objects of a list hollow.
3250 ········/// </summary>
3251 ········/// <param name="list">the list of objects that should be made hollow</param>
3252 ········/// <param name="recursive">if true then unload related objects as well</param>
3253 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3254 ········{
3255 ············foreach (IPersistenceCapable pc in list)
3256 ················MakeHollow(pc, recursive);················
3257 ········}
3258
3259 ········/// <summary>
3260 ········/// Make all unlocked objects in the cache hollow.
3261 ········/// </summary>
3262 ········public virtual void MakeAllHollow()
3263 ········{
3264 ············foreach(var pc in cache.UnlockedObjects)
3265 ············{
3266 ················MakeHollow(pc, false);
3267 ············}
3268 ········}
3269
3270 ········/// <summary>
3271 ········/// Make relations hollow.
3272 ········/// </summary>
3273 ········/// <param name="pc">The parent object</param>
3274 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3275 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3276 ········{
3277 ············Class c = GetClass(pc);
3278 ············foreach(Relation r in c.Relations)
3279 ············{
3280 ················if (r.Multiplicity == RelationMultiplicity.Element)
3281 ················{
3282 ····················mappings.SetRelationField(pc, r.FieldName, null);
3283 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3284 ····················//····················if((null != child) && recursive)
3285 ····················//····················{
3286 ····················//························MakeHollow(child, true);
3287 ····················//····················}
3288 ················}
3289 ················else
3290 ················{
3291 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3292 ························continue;
3293 ····················// Help GC by clearing lists
3294 ····················IList l = mappings.GetRelationContainer(pc, r);
3295 ····················if(l != null)
3296 ····················{
3297 ························if(recursive)
3298 ························{
3299 ····························MakeHollow(l, true);
3300 ························}
3301 ························l.Clear();
3302 ····················}
3303 ····················// Hollow relation
3304 ····················mappings.SetRelationContainer(pc, r, null);
3305 ················}
3306 ············}
3307 ············ClearRelationState(pc);
3308 ········}
3309
3310 ········private void ClearRelationState(IPersistenceCapable pc)
3311 ········{
3312 ············Class cl = GetClass(pc);
3313 ············foreach(Relation r in cl.Relations)
3314 ················pc.NDOSetLoadState(r.Ordinal, false);
3315 ········}
3316
3317 ········private void SetRelationState(IPersistenceCapable pc)
3318 ········{
3319 ············Class cl = GetClass(pc);
3320 ············// Due to a bug in the enhancer the constructors are not always patched right,
3321 ············// so NDOLoadState might be uninitialized
3322 ············if (pc.NDOLoadState == null)
3323 ············{
3324 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3325 ················if (fi == null)
3326 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3327 ················fi.SetValue(pc, new LoadState());
3328 ············}
3329
3330 ············// After serialization the relation load state is null
3331 ············if (pc.NDOLoadState.RelationLoadState == null)
3332 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3333 ············foreach(Relation r in cl.Relations)
3334 ················pc.NDOSetLoadState(r.Ordinal, true);
3335 ········}
3336
3337 ········/// <summary>
3338 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3339 ········/// </summary>
3340 ········/// <param name="t">The type of the persistent object</param>
3341 ········/// <returns></returns>
3342 ········public IPersistenceCapable CreateObject(Type t)
3343 ········{
3344 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3345 ········}
3346
3347 ········/// <summary>
3348 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3349 ········/// </summary>
3350 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3351 ········/// <returns></returns>
3352 ········public T CreateObject<T>()
3353 ········{
3354 ············return (T)CreateObject( typeof( T ) );
3355 ········}
3356
3357 ········/// <summary>
3358 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3359 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3360 ········/// is performed.
3361 ········/// </summary>
3362 ········/// <param name="t">The type of the object to retrieve.</param>
3363 ········/// <param name="keyData">The key value to build the object id.</param>
3364 ········/// <returns>A hollow object</returns>
3365 ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks>
3366 ········public IPersistenceCapable FindObject(Type t, object keyData)
3367 ········{
3368 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3369 ············return FindObject(oid);
3370 ········}
3371
3372 ········/// <summary>
3373 ········/// Finds an object using a short id.
3374 ········/// </summary>
3375 ········/// <param name="encodedShortId"></param>
3376 ········/// <returns></returns>
3377 ········public IPersistenceCapable FindObject(string encodedShortId)
3378 ········{
3379 ············string shortId = encodedShortId.Decode();
3380 ············string[] arr = shortId.Split( '-' );
3381 ············if (arr.Length != 3)
3382 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3383 ············Type t = shortId.GetObjectType(this);··// try readable format
3384 ············if (t == null)
3385 ············{
3386 ················int typeCode = 0;
3387 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3388 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3389 ················t = this.typeManager[typeCode];
3390 ················if (t == null)
3391 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3392 ············}
3393
3394 ············Class cls = GetClass( t );
3395 ············if (cls == null)
3396 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3397
3398 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3399 ············string[] oidValues = arr[2].Split( ' ' );
3400
3401 ············int i = 0;
3402 ············foreach (var oidValue in oidValues)
3403 ············{
3404 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3405 ················if (oidType == typeof( int ))
3406 ················{
3407 ····················int key;
3408 ····················if (!int.TryParse( oidValue, out key ))
3409 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3410 ····················if (key > (int.MaxValue >> 1))
3411 ························key = -(int.MaxValue - key);
3412 ····················keydata[i] = key;
3413 ················}
3414 ················else if (oidType == typeof( Guid ))
3415 ················{
3416 ····················Guid key;
3417 ····················if (!Guid.TryParse( oidValue, out key ))
3418 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3419 ····················keydata[i] = key;
3420 ················}
3421 ················else if (oidType == typeof( string ))
3422 ················{
3423 ····················keydata[i] = oidValue;
3424 ················}
3425 ················else
3426 ················{
3427 ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) );
3428 ················}
3429
3430 ················i++;
3431 ············}
3432
3433 ············if (keydata.Length == 1)
3434 ················return FindObject( t, keydata[0] );
3435
3436 ············return FindObject( t, keydata );············
3437 ········}
3438
3439 ········/// <summary>
3440 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3441 ········/// instance of the object is returned. In either case, the DB is not accessed!
3442 ········/// </summary>
3443 ········/// <param name="id">Object id</param>
3444 ········/// <returns>The object to retrieve in hollow state</returns>········
3445 ········public IPersistenceCapable FindObject(ObjectId id)
3446 ········{
3447 ············if(id == null)
3448 ············{
3449 ················throw new ArgumentNullException("id");
3450 ············}
3451
3452 ············if(!id.IsValid())
3453 ············{
3454 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3455 ············}
3456
3457 ············IPersistenceCapable pc = cache.GetObject(id);
3458 ············if(pc == null)
3459 ············{
3460 ················pc = CreateObject(id.Id.Type);
3461 ················pc.NDOObjectId = id;
3462 ················pc.NDOStateManager = sm;
3463 ················pc.NDOObjectState = NDOObjectState.Hollow;
3464 ················cache.UpdateCache(pc);
3465 ············}
3466 ············return pc;
3467 ········}
3468
3469
3470 ········/// <summary>
3471 ········/// Reload an object from the database.
3472 ········/// </summary>
3473 ········/// <param name="o">The object to be reloaded.</param>
3474 ········public virtual void Refresh(object o)
3475 ········{
3476 ············IPersistenceCapable pc = CheckPc(o);
3477 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3478 ············{
3479 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3480 ············}
3481
3482 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3483 ················return; // Cannot update objects in current transaction
3484
3485 ············MakeHollow(pc);
3486 ············LoadData(pc);
3487 ········}
3488
3489 ········/// <summary>
3490 ········/// Refresh a list of objects.
3491 ········/// </summary>
3492 ········/// <param name="list">The list of objects to be refreshed.</param>
3493 ········public virtual void Refresh(IList list)
3494 ········{
3495 ············foreach (IPersistenceCapable pc in list)
3496 ················Refresh(pc);························
3497 ········}
3498
3499 ········/// <summary>
3500 ········/// Refreshes all unlocked objects in the cache.
3501 ········/// </summary>
3502 ········public virtual void RefreshAll()
3503 ········{
3504 ············Refresh( cache.UnlockedObjects.ToList() );
3505 ········}
3506
3507 ········/// <summary>
3508 ········/// Closes the PersistenceManager and releases all resources.
3509 ········/// </summary>
3510 ········public override void Close()
3511 ········{
3512 ············if (this.isClosing)
3513 ················return;
3514 ············this.isClosing = true;
3515 ············TransactionScope.Dispose();
3516 ············UnloadCache();
3517 ············base.Close();
3518 ········}
3519
3520 ········internal void LogIfVerbose( string msg )
3521 ········{
3522 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3523 ················Logger.LogDebug( msg );
3524 ········}
3525
3526
3527 ········#endregion
3528
3529
3530 #region Class extent
3531 ········/// <summary>
3532 ········/// Gets all objects of a given class.
3533 ········/// </summary>
3534 ········/// <param name="t">the type of the class</param>
3535 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3536 ········public virtual IList GetClassExtent(Type t)
3537 ········{
3538 ············return GetClassExtent(t, true);
3539 ········}
3540
3541 ········/// <summary>
3542 ········/// Gets all objects of a given class.
3543 ········/// </summary>
3544 ········/// <param name="t">The type of the class.</param>
3545 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3546 ········/// <returns>A list of all persistent objects of the given class.</returns>
3547 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3548 ········public virtual IList GetClassExtent(Type t, bool hollow)
3549 ········{
3550 ············IQuery q = NewQuery( t, null, hollow );
3551 ············return q.Execute();
3552 ········}
3553
3554 #endregion
3555
3556 #region Query engine
3557
3558
3559 ········/// <summary>
3560 ········/// Returns a virtual table for Linq queries.
3561 ········/// </summary>
3562 ········/// <typeparam name="T"></typeparam>
3563 ········/// <returns></returns>
3564 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3565 ········{
3566 ············return new VirtualTable<T>( this );
3567 ········}
3568
3569 ········
3570 ········/// <summary>
3571 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3572 ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have
3573 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3574 ········/// not lose the foreign key it will be stored as part of the object.
3575 ········/// </summary>
3576 ········/// <param name="cl"></param>
3577 ········/// <param name="pc"></param>
3578 ········/// <param name="row"></param>
3579 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3580 ········{
3581 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3582 ············{
3583 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3584 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3585 ················foreach(string colName in cl.FKColumnNames)
3586 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3587 ················pc.NDOLoadState.LostRowInfo = kvl;················
3588 ············}
3589 ········}
3590
3591 ········/// <summary>
3592 ········/// Writes information into the data row, which cannot be stored in the object.
3593 ········/// </summary>
3594 ········/// <param name="cl"></param>
3595 ········/// <param name="pc"></param>
3596 ········/// <param name="row"></param>
3597 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3598 ········{
3599 ············if (cl.FKColumnNames != null)
3600 ············{
3601 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3602 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3603 ················if (kvl == null)
3604 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3605 ················foreach (KeyValuePair pair in kvl)
3606 ····················row[(string) pair.Key] = pair.Value;
3607 ············}
3608 ········}
3609
3610 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3611 ········{
3612 ············ReadObject(pc, row, cl.ColumnNames, 0);
3613 ············ReadTimeStamp(cl, pc, row);
3614 ············ReadLostForeignKeysFromRow(cl, pc, row);
3615 ············LoadRelated1To1Objects(pc, row);
3616 ············pc.NDOObjectState = NDOObjectState.Persistent;
3617 ········}
3618
3619
3620 ········/// <summary>
3621 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3622 ········/// </summary>
3623 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3624 ········{
3625 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3626 ············if (rows.Count == 0)
3627 ················return queryResult;
3628
3629 ············IList callbackObjects = new ArrayList();
3630 ············Class cl = GetClass(t);
3631 ············if (t.IsGenericTypeDefinition)
3632 ············{
3633 ················if (cl.TypeNameColumn == null)
3634 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3635 ················IEnumerator en = rows.GetEnumerator();
3636 ················en.MoveNext();··// Move to the first element
3637 ················DataRow testrow = (DataRow)en.Current;
3638 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3639 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3640 ············}
3641
3642 ············foreach(DataRow row in rows)
3643 ············{
3644 ················Type concreteType = t;
3645 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3646 ················{
3647 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3648 ····················{
3649 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3650 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3651 ····················}
3652 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3653 ····················concreteType = Type.GetType(typeStr);
3654 ····················if (concreteType == null)
3655 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3656 ················}
3657 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3658 ················IPersistenceCapable pc = cache.GetObject(id);················
3659 ················if(pc == null)
3660 ················{
3661 ····················pc = CreateObject( concreteType );
3662 ····················pc.NDOObjectId = id;
3663 ····················pc.NDOStateManager = sm;
3664 ····················// If the object shouldn't be hollow, this will be overwritten later.
3665 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3666 ················}
3667 ················// If we have found a non hollow object, the time stamp remains the old one.
3668 ················// In every other case we use the new time stamp.
3669 ················// Note, that there could be a hollow object in the cache.
3670 ················// We need the time stamp in hollow objects in order to correctly
3671 ················// delete objects using fake rows.
3672 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3673 ················{
3674 ····················ReadTimeStamp(cl, pc, row);
3675 ················}
3676 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3677 ················{
3678 ····················Row2Object(cl, pc, row);
3679 ····················if ((pc as IPersistenceNotifiable) != null)
3680 ························callbackObjects.Add(pc);
3681 ················}
3682
3683 ················cache.UpdateCache(pc);
3684 ················queryResult.Add(pc);
3685 ············}
3686 ············// Make shure this is the last statement before returning
3687 ············// to the caller, so the user can recursively use persistent
3688 ············// objects
3689 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3690 ················ipn.OnLoaded();
3691
3692 ············return queryResult;
3693 ········}
3694
3695 #endregion
3696
3697 #region Cache Management
3698 ········/// <summary>
3699 ········/// Remove all unused entries from the cache.
3700 ········/// </summary>
3701 ········public void CleanupCache()
3702 ········{
3703 ············GC.Collect(GC.MaxGeneration);
3704 ············GC.WaitForPendingFinalizers();
3705 ············cache.Cleanup();
3706 ········}
3707
3708 ········/// <summary>
3709 ········/// Remove all unlocked objects from the cache. Use with care!
3710 ········/// </summary>
3711 ········public void UnloadCache()
3712 ········{
3713 ············cache.Unload();
3714 ········}
3715 #endregion
3716
3717 ········/// <summary>
3718 ········/// Default encryption key for NDO
3719 ········/// </summary>
3720 ········/// <remarks>
3721 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3722 ········/// </remarks>
3723 ········public virtual byte[] EncryptionKey
3724 ········{
3725 ············get
3726 ············{
3727 ················if (this.encryptionKey == null)
3728 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3729 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3730 ················return this.encryptionKey;
3731 ············}
3732 ············set { this.encryptionKey = value; }
3733 ········}
3734
3735 ········/// <summary>
3736 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3737 ········/// </summary>
3738 ········public virtual bool HollowMode
3739 ········{
3740 ············get { return hollowMode; }
3741 ············set { hollowMode = value; }
3742 ········}
3743
3744 ········internal TypeManager TypeManager
3745 ········{
3746 ············get { return typeManager; }
3747 ········}
3748
3749 ········/// <summary>
3750 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3751 ········/// </summary>
3752 ········/// <remarks>
3753 ········/// Set this value before you start any transactions.
3754 ········/// </remarks>
3755 ········public TransactionMode TransactionMode
3756 ········{
3757 ············get { return TransactionScope.TransactionMode; }
3758 ············set { TransactionScope.TransactionMode = value; }
3759 ········}
3760
3761 ········/// <summary>
3762 ········/// Sets or gets the Isolation Level.
3763 ········/// </summary>
3764 ········/// <remarks>
3765 ········/// Set this value before you start any transactions.
3766 ········/// </remarks>
3767 ········public IsolationLevel IsolationLevel
3768 ········{
3769 ············get { return TransactionScope.IsolationLevel; }
3770 ············set { TransactionScope.IsolationLevel = value; }
3771 ········}
3772
3773 ········internal class MappingTableEntry
3774 ········{
3775 ············private IPersistenceCapable parentObject;
3776 ············private IPersistenceCapable relatedObject;
3777 ············private Relation relation;
3778 ············private bool deleteEntry;
3779 ············
3780 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3781 ············{
3782 ············}
3783 ············
3784 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3785 ············{
3786 ················parentObject = pc;
3787 ················relatedObject = relObj;
3788 ················relation = r;
3789 ················this.deleteEntry = deleteEntry;
3790 ············}
3791
3792 ············public bool DeleteEntry
3793 ············{
3794 ················get
3795 ················{
3796 ····················return deleteEntry;
3797 ················}
3798 ············}
3799
3800 ············public IPersistenceCapable ParentObject
3801 ············{
3802 ················get
3803 ················{
3804 ····················return parentObject;
3805 ················}
3806 ············}
3807
3808 ············public IPersistenceCapable RelatedObject
3809 ············{
3810 ················get
3811 ················{
3812 ····················return relatedObject;
3813 ················}
3814 ············}
3815
3816 ············public Relation Relation
3817 ············{
3818 ················get
3819 ················{
3820 ····················return relation;
3821 ················}
3822 ············}
3823 ········}
3824
3825 ········/// <summary>
3826 ········/// Get a DataRow representing the given object.
3827 ········/// </summary>
3828 ········/// <param name="o"></param>
3829 ········/// <returns></returns>
3830 ········public DataRow GetClonedDataRow( object o )
3831 ········{
3832 ············IPersistenceCapable pc = CheckPc( o );
3833
3834 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3835 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3836
3837 ············DataRow row = cache.GetDataRow( pc );
3838 ············DataTable newTable = row.Table.Clone();
3839 ············newTable.ImportRow( row );
3840 ············row = newTable.Rows[0];
3841
3842 ············Class cls = mappings.FindClass(o.GetType());
3843 ············WriteObject( pc, row, cls.ColumnNames );
3844 ············WriteForeignKeysToRow( pc, row );
3845
3846 ············return row;
3847 ········}
3848
3849 ········/// <summary>
3850 ········/// Gets an object, which shows all changes applied to the given object.
3851 ········/// This function can be used to build an audit system.
3852 ········/// </summary>
3853 ········/// <param name="o"></param>
3854 ········/// <returns>An ExpandoObject</returns>
3855 ········public ChangeLog GetChangeSet( object o )
3856 ········{
3857 ············var changeLog = new ChangeLog(this);
3858 ············changeLog.Initialize( o );
3859 ············return changeLog;
3860 ········}
3861
3862 ········/// <summary>
3863 ········/// Outputs a revision number representing the assembly version.
3864 ········/// </summary>
3865 ········/// <remarks>This can be used for debugging purposes</remarks>
3866 ········public int Revision
3867 ········{
3868 ············get
3869 ············{
3870 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3871 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3872 ················return int.Parse( vstring );
3873 ············}
3874 ········}
3875 ····}
3876 }
3877