Datei: NDODLL/PersistenceManager.cs

Last Commit (01941c7)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
59 ····/// </summary>
60 ····public delegate void OnSavingHandler(ICollection l);
61 ····/// <summary>
62 ····/// Delegate type for the OnSavedEvent.
63 ····/// </summary>
64 ····/// <param name="auditSet"></param>
65 ····public delegate void OnSavedHandler(AuditSet auditSet);
66
67 ····/// <summary>
68 ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid.
69 ····/// </summary>
70 ····/// <param name="pc"></param>
71 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
72 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
73 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
74
75 ····/// <summary>
76 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
77 ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation.
78 ····/// </summary>
79 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
80 ····{········
81 ········private bool hollowMode = false;
82 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
83
84 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
85 ········private ObjectLock removeLock = new ObjectLock();
86 ········private ObjectLock addLock = new ObjectLock();
87 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
88 ········// List of created objects that use mapping table and need to be stored in mapping table
89 ········// after they have been stored to the database to update foreign keys.
90 ········private ArrayList createdMappingTableObjects = new ArrayList();··
91 ········private TypeManager typeManager;
92 ········internal bool DeferredMode { get; private set; }
93 ········private INDOTransactionScope transactionScope;
94 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
95
96 ········private OpenConnectionListener openConnectionListener;
97
98 ········/// <summary>
99 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
100 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
101 ········/// </summary>
102 ········public event CollisionHandler CollisionEvent;
103
104 ········/// <summary>
105 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
106 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
107 ········/// </summary>
108 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event, if you want to be notified about the end
112 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
113 ········/// during the transaction and are to be saved or deleted.
114 ········/// </summary>
115 ········public event OnSavingHandler OnSavingEvent;
116 ········/// <summary>
117 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
118 ········/// </summary>
119 ········public event OnSavedHandler OnSavedEvent;
120 ········
121 ········private const string hollowMarker = "Hollow";
122 ········private byte[] encryptionKey;
123 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
124 ········private bool isClosing = false;
125
126 ········/// <summary>
127 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
128 ········/// </summary>
129 ········protected internal List<RelationChangeRecord> RelationChanges
130 ········{
131 ············get { return this.relationChanges; }
132 ········}
133
134 ········/// <summary>
135 ········/// Initializes a new PersistenceManager instance.
136 ········/// </summary>
137 ········/// <param name="mappingFileName"></param>
138 ········protected override void Init(string mappingFileName)
139 ········{
140 ············try
141 ············{
142 ················base.Init(mappingFileName);
143 ············}
144 ············catch (Exception ex)
145 ············{
146 ················if (ex is NDOException)
147 ····················throw;
148 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
149 ············}
150
151 ········}
152
153 ········/// <summary>
154 ········/// Initializes the persistence manager
155 ········/// </summary>
156 ········/// <remarks>
157 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
158 ········/// </remarks>
159 ········/// <param name="mapping"></param>
160 ········internal override void Init( Mappings mapping )
161 ········{
162 ············base.Init( mapping );
163
164 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
165
166 ············string dir = Path.GetDirectoryName( mapping.FileName );
167
168 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
169 ············typeManager = new TypeManager( typesFile, this.mappings );
170
171 ············sm = new StateManager( this );
172
173 ············InitClasses();
174 ········}
175
176
177 ········/// <summary>
178 ········/// Standard Constructor.
179 ········/// </summary>
180 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
181 ········/// <remarks>
182 ········/// Searches for a mapping file in the application directory.
183 ········/// The constructor tries to find a file with the same name as
184 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
185 ········/// file called AssemblyName.ndo.mapping in the application directory.
186 ········/// </remarks>
187 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
188 ········{
189 ········}
190
191 ········/// <summary>
192 ········/// Loads the mapping file from the specified location. This allows to use
193 ········/// different mapping files with different classes mapped in it.
194 ········/// </summary>
195 ········/// <param name="mappingFile">Path to the mapping file.</param>
196 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
197 ········/// <remarks>Only the Professional and Enterprise
198 ········/// Editions can handle more than one mapping file.</remarks>
199 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
200 ········{
201 ········}
202
203 ········/// <summary>
204 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
205 ········/// </summary>
206 ········/// <param name="mapping">The cached mapping object</param>
207 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
208 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
209 ········{
210 ········}
211
212 ········#region Object Container Stuff
213 ········/// <summary>
214 ········/// Gets a container of all loaded objects and tries to load all child objects,
215 ········/// which are reachable through composite relations.
216 ········/// </summary>
217 ········/// <returns>An ObjectContainer object.</returns>
218 ········/// <remarks>
219 ········/// It is not recommended, to transfer objects with a state other than Hollow,
220 ········/// Persistent, or Transient.
221 ········/// The transfer format is binary.
222 ········/// </remarks>
223 ········public ObjectContainer GetObjectContainer()
224 ········{
225 ············IList l = this.cache.AllObjects;
226 ············foreach(IPersistenceCapable pc in l)
227 ············{
228 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
229 ················{
230 ····················if (Logger != null)
231 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
232 ················}
233 ············}
234
235 ············ObjectContainer oc = new ObjectContainer();
236 ············oc.AddList(l);
237 ············return oc;
238 ········}
239
240 ········/// <summary>
241 ········/// Returns a container of all objects provided in the objects list and searches for
242 ········/// child objects according to the serFlags.
243 ········/// </summary>
244 ········/// <param name="objects">The list of the root objects to add to the container.</param>
245 ········/// <returns>An ObjectContainer object.</returns>
246 ········/// <remarks>
247 ········/// It is not recommended, to transfer objects with a state other than Hollow,
248 ········/// Persistent, or Transient.
249 ········/// </remarks>
250 ········public ObjectContainer GetObjectContainer(IList objects)
251 ········{
252 ············foreach(object o in objects)
253 ············{
254 ················CheckPc(o);
255 ················IPersistenceCapable pc = o as IPersistenceCapable;
256 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
257 ····················LoadData(pc);
258 ············}
259 ············ObjectContainer oc = new ObjectContainer();
260 ············oc.AddList(objects);
261 ············return oc;
262 ········}
263
264
265 ········/// <summary>
266 ········/// Returns a container containing the provided object
267 ········/// and tries to load all child objects
268 ········/// reachable through composite relations.
269 ········/// </summary>
270 ········/// <param name="obj">The object to be added to the container.</param>
271 ········/// <returns>An ObjectContainer object.</returns>
272 ········/// <remarks>
273 ········/// It is not recommended, to transfer objects with a state other than Hollow,
274 ········/// Persistent, or Transient.
275 ········/// The transfer format is binary.
276 ········/// </remarks>
277 ········public ObjectContainer GetObjectContainer(Object obj)
278 ········{
279 ············CheckPc(obj);
280 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
281 ················LoadData(obj);
282 ············ObjectContainer oc = new ObjectContainer();
283 ············oc.AddObject(obj);
284 ············return oc;
285 ········}
286
287 ········/// <summary>
288 ········/// Merges an object container to the active objects in the pm. All changes and the state
289 ········/// of the objects will be taken over by the pm.
290 ········/// </summary>
291 ········/// <remarks>
292 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
293 ········/// The flag MarkAsTransient can be used to perform a kind
294 ········/// of object based replication using the ObjectContainer class.
295 ········/// Objects, which are persistent at one machine, can be transfered
296 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
297 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
298 ········/// transient object tree.
299 ········/// There is one difference to freshly created objects: If an object id exists, it will be
300 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
301 ········/// oids will be reused by the receiving PersistenceManager.
302 ········/// </remarks>
303 ········/// <param name="ocb">The object container to be merged.</param>
304 ········public void MergeObjectContainer(ObjectContainerBase ocb)
305 ········{
306 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
307 ············if (csc != null)
308 ············{
309 ················MergeChangeSet(csc);
310 ················return;
311 ············}
312 ············ObjectContainer oc = ocb as ObjectContainer;
313 ············if (oc != null)
314 ············{
315 ················InternalMergeObjectContainer(oc);
316 ················return;
317 ············}
318 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
319 ········}
320
321
322 ········void InternalMergeObjectContainer(ObjectContainer oc)
323 ········{
324 ············// TODO: Check, if other states are useful. Find use scenarios.
325 ············foreach(IPersistenceCapable pc in oc.RootObjects)
326 ············{
327 ················if (pc.NDOObjectState == NDOObjectState.Transient)
328 ····················MakePersistent(pc);
329 ············}
330 ············foreach(IPersistenceCapable pc in oc.RootObjects)
331 ············{
332 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
333 ············}
334 ········}
335
336 ········void MergeChangeSet(ChangeSetContainer cs)
337 ········{
338 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
339 ············{
340 ················InternalMakePersistent(pc, false);
341 ············}
342 ············foreach(ObjectId oid in cs.DeletedObjects)
343 ············{
344 ················IPersistenceCapable pc2 = FindObject(oid);
345 ················Delete(pc2);
346 ············}
347 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
348 ············{
349 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
350 ················Class pcClass = GetClass(pc);
351 ················// Make sure, the object is loaded.
352 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
353 ····················LoadData(pc2);
354 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
355 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
356 ················DataRow row = entry.row;
357 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
358 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
359 ············}
360 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
361 ············{
362 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
363 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
364 ················Class pcClass = GetClass(parent);
365 ················Relation r = pcClass.FindRelation(rcr.RelationName);
366 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
367 ····················LoadRelation(parent, r, true);
368 ················if (rcr.IsAdded)
369 ················{
370 ····················InternalAddRelatedObject(parent, r, child, true);
371 ····················if (r.Multiplicity == RelationMultiplicity.Element)
372 ····················{
373 ························mappings.SetRelationField(parent, r.FieldName, child);
374 ····················}
375 ····················else
376 ····················{
377 ························IList l = mappings.GetRelationContainer(parent, r);
378 ························l.Add(child);
379 ····················}
380 ················}
381 ················else
382 ················{
383 ····················RemoveRelatedObject(parent, r.FieldName, child);
384 ····················if (r.Multiplicity == RelationMultiplicity.Element)
385 ····················{
386 ························mappings.SetRelationField(parent, r.FieldName, null);
387 ····················}
388 ····················else
389 ····················{
390 ························IList l = mappings.GetRelationContainer(parent, r);
391 ························try
392 ························{
393 ····························ObjectListManipulator.Remove(l, child);
394 ························}
395 ························catch
396 ························{
397 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
398 ························}
399 ····················}
400 ················}
401 ············}
402
403 ········}
404 ········#endregion
405
406 ········#region Implementation of IPersistenceManager
407
408 ········// Complete documentation can be found in IPersistenceManager
409
410
411 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
412 ········{
413 ············if (!cl.Oid.IsDependent)
414 ················return;
415 ············WriteForeignKeysToRow(pc, row);
416 ········}
417
418 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
419 ········{
420 ············// Object is now under control of the state manager
421 ············pc.NDOStateManager = sm;
422
423 ············Type pcType = pc.GetType();
424 ············Class pcClass = GetClass(pc);
425
426 ············// Create new object
427 ············DataTable dt = GetTable(pcType);
428 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
429
430 ············// In case of a Guid oid the value will be computed now.
431 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
432 ············{
433 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
434 ················{
435 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
436 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
437 ····················else
438 ························row[oidColumn.Name] = Guid.NewGuid();
439 ················}
440 ············}
441
442 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
443
444 ············// If the object is merged from an ObjectContainer, the id should be reused,
445 ············// if the id is client generated (not Autoincremented).
446 ············// In every other case, the oid is set to null, to force generating a new oid.
447 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
448 ············if ((object)pc.NDOObjectId != null)
449 ············{
450 ················bool hasAutoincrement = false;
451 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
452 ················{
453 ····················if (oidColumn.AutoIncremented)
454 ····················{
455 ························hasAutoincrement = true;
456 ························break;
457 ····················}
458 ················}
459 ················if (hasAutoincrement) // can't store existing id
460 ················{
461 ····················pc.NDOObjectId = null;
462 ····················fireIdGeneration = true;
463 ················}
464 ············}
465
466 ············// In case of a dependent class the oid has to be read from the fields according to the relations
467 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
468
469 ············if ((object)pc.NDOObjectId == null)
470 ············{
471 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
472 ············}
473
474 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
475 ············{
476 ················if (fireIdGeneration)
477 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
478 ················// At this place the oid might have been
479 ················// - deserialized (MergeObjectContainer)
480 ················// - created using NewObjectId
481 ················// - defined by the IdGenerationEvent
482
483 ················// At this point we have a valid oid.
484 ················// If the object has a field mapped to the oid we have
485 ················// to write back the oid to the field
486 ················int i = 0;
487 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
488 ················{
489 ····················if (oidColumn.FieldName != null)
490 ····················{
491 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
492 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
493 ····················}
494 ····················i++;
495 ················});
496
497
498
499 ················// Now write back the data into the row
500 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
501 ············}
502
503 ············
504 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
505 ············dt.Rows.Add(row);
506
507 ············cache.Register(pc);
508
509 ············// new object that has never been written to the DS
510 ············pc.NDOObjectState = NDOObjectState.Created;
511 ············// Mark all Relations as loaded
512 ············SetRelationState(pc);
513
514 ············if (checkRelations)
515 ············{
516 ················// Handle related objects:
517 ················foreach(Relation r in pcClass.Relations)
518 ················{
519 ····················if (r.Multiplicity == RelationMultiplicity.Element)
520 ····················{
521 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
522 ························if(child != null)
523 ························{
524 ····························AddRelatedObject(pc, r, child);
525 ························}
526 ····················}
527 ····················else
528 ····················{
529 ························IList list = mappings.GetRelationContainer(pc, r);
530 ························if(list != null)
531 ························{
532 ····························foreach(IPersistenceCapable relObj in list)
533 ····························{
534 ································if (relObj != null)
535 ····································AddRelatedObject(pc, r, relObj);
536 ····························}
537 ························}
538 ····················}
539 ················}
540 ············}
541
542 ············var relations··= CollectRelationStates(pc);
543 ············cache.Lock(pc, row, relations);
544 ········}
545
546
547 ········/// <summary>
548 ········/// Make an object persistent.
549 ········/// </summary>
550 ········/// <param name="o">the transient object that should be made persistent</param>
551 ········public void MakePersistent(object o)
552 ········{
553 ············IPersistenceCapable pc = CheckPc(o);
554
555 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
556 ············//Debug.Indent();
557
558 ············if (pc.NDOObjectState != NDOObjectState.Transient)
559 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
560
561 ············InternalMakePersistent(pc, true);
562
563 ········}
564
565
566
567 ········//········/// <summary>
568 ········//········/// Checks, if an object has a valid id, which was created by the database
569 ········//········/// </summary>
570 ········//········/// <param name="pc"></param>
571 ········//········/// <returns></returns>
572 ········//········private bool HasValidId(IPersistenceCapable pc)
573 ········//········{
574 ········//············if (this.IdGenerationEvent != null)
575 ········//················return true;
576 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
577 ········//········}
578
579
580 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
581 ········{
582 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
583 ············if(r.HasSubclasses)
584 ············{
585 ················// we don't support 1:n with foreign fields in subclasses because we would have to
586 ················// search for objects in all subclasses! Instead use a mapping table.
587 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
588 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
589 ············}
590
591 ············if (!makeRelObjPersistent)
592 ················MarkDirty(relObj);
593 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
594 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
595
596 ············if (relObjRow == null)
597 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
598
599 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
600
601 ············if (relObj.NDOLoadState.LostRowInfo == null)
602 ············{
603 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
604 ············}
605 ············else
606 ············{
607 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
608 ············}
609 ········}
610
611 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
612 ········{
613 ············switch(relObj.NDOObjectState)
614 ············{
615 ················case NDOObjectState.Persistent:
616 ····················MarkDirty(relObj);
617 ····················break;
618 ················case NDOObjectState.Hollow:
619 ····················LoadData(relObj);
620 ····················MarkDirty(relObj);
621 ····················break;
622 ············}
623
624 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
625 ············{
626 ················IPersistenceCapable newpc;
627 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
628 ················{
629 ····················if (newpc != pc)
630 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
631 ················}
632 ················else
633 ················{
634 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
635 ················}
636 ············}
637 ············else
638 ············{
639 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
640 ····················LoadRelation(relObj, r.ForeignRelation, true);
641 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
642 ················if(l == null)
643 ················{
644 ····················try
645 ····················{
646 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
647 ····················}
648 ····················catch
649 ····················{
650 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
651 ····················}
652 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
653 ················}
654 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
655 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
656 ················if (!ObjectListManipulator.Contains(l, pc))
657 ····················l.Add(pc);
658 ············}
659 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
660 ········}
661
662
663 ········/// <summary>
664 ········/// Add a related object to the specified object.
665 ········/// </summary>
666 ········/// <param name="pc">the parent object</param>
667 ········/// <param name="fieldName">the field name of the relation</param>
668 ········/// <param name="relObj">the related object that should be added</param>
669 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
670 ········{
671 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
672 ············Relation r = mappings.FindRelation(pc, fieldName);
673 ············AddRelatedObject(pc, r, relObj);
674 ········}
675
676 ········/// <summary>
677 ········/// Core functionality to add an object to a relation container or relation field.
678 ········/// </summary>
679 ········/// <param name="pc"></param>
680 ········/// <param name="r"></param>
681 ········/// <param name="relObj"></param>
682 ········/// <param name="isMerging"></param>
683 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
684 ········{
685 ············
686 ············// avoid recursion
687 ············if (!addLock.GetLock(relObj))
688 ················return;
689
690 ············try
691 ············{
692 ················//TODO: We need a relation management, which is independent of
693 ················//the state management of an object. Currently the relation
694 ················//lists or elements are cached for restore, if an object is marked dirty.
695 ················//Thus we have to mark dirty our parent object in any case at the moment.
696 ················MarkDirty(pc);
697
698 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
699 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
700 ················//The latter happens in CreateAddedObjectRow
701
702 ················Class relClass = GetClass(relObj);
703
704 ················if (r.Multiplicity == RelationMultiplicity.Element
705 ····················&& r.HasSubclasses
706 ····················&& r.MappingTable == null················
707 ····················&& !this.HasOwnerCreatedIds
708 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
709 ····················&& !relClass.HasGuidOid)
710 ················{
711 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
712 ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table.");
713 ····················if (r.Composition)
714 ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's.");
715 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
716 ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." );
717 ················}
718
719 ················bool isDependent = relClass.Oid.IsDependent;
720
721 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
722 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
723
724 ················// Need to patch pc into the relation relObj->pc, because
725 ················// the oid is built on base of this information
726 ················if (isDependent)
727 ················{
728 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
729 ················}
730
731 ················if (r.Composition || isDependent)
732 ················{
733 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
734 ························MakePersistent(relObj);
735 ················}
736
737 ················if(r.MappingTable == null)
738 ················{
739 ····················if (r.Bidirectional)
740 ····················{
741 ························// This object hasn't been saved yet, so the key is wrong.
742 ························// Therefore, the child must be written twice to update the foreign key.
743 #if trace
744 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
745 ························if (r.Multiplicity == RelationMultiplicity.Element)
746 ····························sb.Append("1");
747 ························else
748 ····························sb.Append("n");
749 ························sb.Append(":");
750 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
751 ····························sb.Append("1");
752 ························else
753 ····························sb.Append("n");
754 ························sb.Append ("OwnCreatedOther");
755 ························sb.Append(relObj.NDOObjectState.ToString());
756 ························sb.Append(' ');
757
758 ························sb.Append(types[0].ToString());
759 ························sb.Append(' ');
760 ························sb.Append(types[1].ToString());
761 ························Debug.WriteLine(sb.ToString());
762 #endif
763 ························//························if (r.Multiplicity == RelationMultiplicity.Element
764 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
765 ························//························{
766 ························// Element means:
767 ························// pc is keyholder
768 ························// -> relObj is saved first
769 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
770 ························// Both are Created - use type sort order
771 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
772 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
773 ························{
774 ····························if (mappings.GetUpdateOrder(pc.GetType())
775 ································< mappings.GetUpdateOrder(relObj.GetType()))
776 ································createdDirectObjects.Add(pc);
777 ····························else
778 ································createdDirectObjects.Add( relObj );
779 ························}
780 ····················}
781 ····················if (r.Multiplicity == RelationMultiplicity.List)
782 ····················{
783 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
784 ····················}
785 ················}
786 ················else
787 ················{
788 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
789 ················}
790 ················if(r.Bidirectional)
791 ················{
792 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
793 ····················{
794 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
795 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
796 ····················}
797 ····················else if ( !addLock.IsLocked( pc ) )
798 ····················{
799 ························PatchForeignRelation( pc, r, relObj );
800 ····················}
801 ················}
802
803 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
804 ············}
805 ············finally
806 ············{
807 ················addLock.Unlock(relObj);
808 ················//Debug.Unindent();
809 ············}
810 ········}
811
812 ········/// <summary>
813 ········/// Returns an integer value which determines the rank of the given type in the update order list.
814 ········/// </summary>
815 ········/// <param name="t">The type to determine the update order.</param>
816 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
817 ········/// <remarks>
818 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
819 ········/// </remarks>
820 ········public int GetUpdateRank(Type t)
821 ········{
822 ············return mappings.GetUpdateOrder(t);
823 ········}
824
825 ········/// <summary>
826 ········/// Add a related object to the specified object.
827 ········/// </summary>
828 ········/// <param name="pc">the parent object</param>
829 ········/// <param name="r">the relation mapping info</param>
830 ········/// <param name="relObj">the related object that should be added</param>
831 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
832 ········{
833 ············//············string idstr;
834 ············//············if (relObj.NDOObjectId == null)
835 ············//················idstr = relObj.GetType().ToString();
836 ············//············else
837 ············//················idstr = relObj.NDOObjectId.Dump();
838 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
839 ············//Debug.Indent();
840
841 ············Class relClass = GetClass(relObj);
842 ············bool isDependent = relClass.Oid.IsDependent;
843
844 ············// Do some checks to guarantee that the assignment is correct
845 ············if(r.Composition)
846 ············{
847 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
848 ················{
849 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
850 ················}
851 ············}
852 ············else
853 ············{
854 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
855 ················{
856 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
857 ················}
858 ············}
859
860 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
861 ············{
862 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
863 ············}
864
865 ············InternalAddRelatedObject(pc, r, relObj, false);
866
867 ········}
868
869 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
870 ········{
871 ············// Need to patch pc into the relation relObj->pc, because
872 ············// the oid is built on base of this information
873 ············// The second relation has to be set before adding relObj
874 ············// to the relation list.
875 ············PatchForeignRelation(pc, r, relObj);
876 ············IPersistenceCapable parent;
877 ············foreach (Relation oidRelation in relClass.Oid.Relations)
878 ············{
879 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
880 ················if (parent == null)
881 ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects.");
882 ················if (parent.NDOObjectState == NDOObjectState.Transient)
883 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
884
885 ············}
886 ········}
887
888
889 ········/// <summary>
890 ········/// Remove a related object from the specified object.
891 ········/// </summary>
892 ········/// <param name="pc">the parent object</param>
893 ········/// <param name="fieldName">Field name of the relation</param>
894 ········/// <param name="relObj">the related object that should be removed</param>
895 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
896 ········{
897 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
898 ············Relation r = mappings.FindRelation(pc, fieldName);
899 ············InternalRemoveRelatedObject(pc, r, relObj, true);
900 ········}
901
902 ········/// <summary>
903 ········/// Registers a listener which will be notified, if a new connection is opened.
904 ········/// </summary>
905 ········/// <param name="listener">Delegate of a listener function</param>
906 ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks>
907 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
908 ········{
909 ············this.openConnectionListener = listener;
910 ········}
911
912 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
913 ········{
914 ············if (openConnectionListener != null)
915 ················return openConnectionListener(conn);
916 ············return conn.Name;
917 ········}
918
919
920 ········/*
921 ········doCommit should be:
922 ········
923 ····················Query····Save····Save(true)
924 ········Optimistic····1········1········0
925 ········Pessimistic····0········1········0
926 ············
927 ········Deferred Mode············
928 ····················Query····Save····Save(true)
929 ········Optimistic····0········1········0
930 ········Pessimistic····0········1········0
931 ········ */
932
933 ········internal void CheckEndTransaction(bool doCommit)
934 ········{
935 ············if (doCommit)
936 ············{
937 ················TransactionScope.Complete();
938 ············}
939 ········}
940
941 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
942 ········{
943 ············CheckTransaction(handler, this.GetClass(t).Connection);
944 ········}
945
946 ········/// <summary>
947 ········/// Each and every database operation has to be preceded by a call to this function.
948 ········/// </summary>
949 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
950 ········{
951 ············TransactionScope.CheckTransaction();
952 ············
953 ············if (handler.Connection == null)
954 ············{
955 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
956 ················{
957 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
958 ····················string connStr = this.OnNewConnection( ndoConn );
959 ····················var connection = p.NewConnection( connStr );
960 ····················if (connection == null)
961 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
962 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
963 ····················return connection;
964 ················} );
965 ············}
966
967 ············if (TransactionMode != TransactionMode.None)
968 ············{
969 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
970 ············}
971
972 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
973 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
974 ············{
975 ················handler.Connection.Open();
976 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
977 ············}
978 ········}
979
980 ········/// <summary>
981 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
982 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
983 ········/// This is possible if there is a listener for the CollisionEvent.
984 ········/// Else we throw an exception.
985 ········/// </summary>
986 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
987 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
988 ········{
989 ············DataRow row = ex.Row;
990 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
991 ················throw(ex);
992 ············if (row.RowState == DataRowState.Detached)
993 ················return;
994 ············foreach (Cache.Entry e in cache.LockedObjects)
995 ············{
996 ················if (e.row == row)
997 ················{
998 ····················CollisionEvent(e.pc);
999 ····················return;
1000 ················}
1001 ············}
1002 ············throw ex;
1003 ········}
1004
1005
1006 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1007 ········{
1008 ············Class cl = GetClass(pc);
1009 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1010 ············Dictionary<string,MemberInfo> persistentFields = null;
1011 ············if (etypes.Length > 0)
1012 ············{
1013 ················FieldMap fm = new FieldMap(cl);
1014 ················persistentFields = fm.PersistentFields;
1015 ············}
1016 ············foreach(string s in etypes)
1017 ············{
1018 ················try
1019 ················{
1020 ····················NDO.Mapping.Field f = cl.FindField(s);
1021 ····················if (f == null)
1022 ························continue;
1023 ····················object o = row[f.Column.Name];
1024 ····················string[] arr = s.Split('.');
1025 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1026 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1027 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1028 ····················// Hole das Embedded Object
1029 ····················object parentOb = parentFi.GetValue(pc);
1030
1031 ····················if (parentOb == null)
1032 ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0]));
1033
1034 ····················// Suche darin das Feld mit Namen Arr[1]
1035
1036 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1037 ····················Type childType = childFi.FieldType;
1038
1039 ····················// Don't initialize value types, if DBNull is stored in the field.
1040 ····················// Exception: DateTime and Guid.
1041 ····················if (o == DBNull.Value && childType.IsValueType
1042 ························&& childType != typeof(Guid)
1043 ························&& childType != typeof(DateTime))
1044 ························continue;
1045
1046 ····················if (childType == typeof(DateTime))
1047 ····················{
1048 ························if (o == DBNull.Value)
1049 ····························o = DateTime.MinValue;
1050 ····················}
1051 ····················if (childType.IsClass)
1052 ····················{
1053 ························if (o == DBNull.Value)
1054 ····························o = null;
1055 ····················}
1056
1057 ····················if (childType == typeof (Guid))
1058 ····················{
1059 ························if (o == DBNull.Value)
1060 ····························o = Guid.Empty;
1061 ························if (o is string)
1062 ························{
1063 ····························childFi.SetValue(parentOb, new Guid((string)o));
1064 ························}
1065 ························else if (o is Guid)
1066 ························{
1067 ····························childFi.SetValue(parentOb, o);
1068 ························}
1069 ························else if (o is byte[])
1070 ························{
1071 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1072 ························}
1073 ························else
1074 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1075 ····················}
1076 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1077 ····················{
1078 ························object childOb = childFi.GetValue(parentOb);
1079 ························FieldInfo valueFi = childType.GetField("value__");
1080 ························valueFi.SetValue(childOb, o);
1081 ························childFi.SetValue(parentOb, childOb);
1082 ····················}
1083 ····················else
1084 ····················{
1085 ························childFi.SetValue(parentOb, o);
1086 ····················}
1087 ················}
1088 ················catch (Exception ex)
1089 ················{
1090 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1091
1092 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1093 ················}
1094
1095 ············}
1096 ············
1097 ············try
1098 ············{
1099 ················if (cl.HasEncryptedFields)
1100 ················{
1101 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1102 ····················{
1103 ························string name = field.Column.Name;
1104 ························string s = (string) row[name];
1105 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1106 ························row[name] = es;
1107 ····················}
1108 ················}
1109 ················pc.NDORead(row, fieldNames, startIndex);
1110 ············}
1111 ············catch (Exception ex)
1112 ············{
1113 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1114 ····················+ ex.Message);
1115 ············}
1116 ········}
1117
1118 ········/// <summary>
1119 ········/// Executes a sql script to generate the database tables.
1120 ········/// The function will execute any sql statements in the script
1121 ········/// which are valid according to the
1122 ········/// rules of the underlying database. Result sets are ignored.
1123 ········/// </summary>
1124 ········/// <param name="scriptFile">The script file to execute.</param>
1125 ········/// <param name="conn">A connection object, containing the connection
1126 ········/// string to the database, which should be altered.</param>
1127 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1128 ········/// <remarks>
1129 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1130 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1131 ········/// Their message property will appear in the result array.
1132 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1133 ········/// </remarks>
1134 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1135 ········{
1136 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1137 ········}
1138
1139 ········/// <summary>
1140 ········/// Executes a sql script to generate the database tables.
1141 ········/// The function will execute any sql statements in the script
1142 ········/// which are valid according to the
1143 ········/// rules of the underlying database. Result sets are ignored.
1144 ········/// </summary>
1145 ········/// <param name="scriptFile">The script file to execute.</param>
1146 ········/// <param name="conn">A connection object, containing the connection
1147 ········/// string to the database, which should be altered.</param>
1148 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1149 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1150 ········/// <remarks>
1151 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1152 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1153 ········/// Their message property will appear in the result array.
1154 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1155 ········/// </remarks>
1156 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1157 ········{
1158 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1159 ············string s = sr.ReadToEnd();
1160 ············sr.Close();
1161 ············string[] arr = s.Split(';');
1162 ············string last = arr[arr.Length - 1];
1163 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1164 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1165 ············using (var handler = GetSqlPassThroughHandler())
1166 ············{
1167 ················int i = 0;
1168 ················string ok = "OK";
1169 ················foreach (string statement in arr)
1170 ················{
1171 ····················if (statement != null && statement.Trim() != string.Empty)
1172 ····················{
1173 ························try
1174 ························{
1175 ····························handler.Execute(statement);
1176 ····························result[i] = ok;
1177 ························}
1178 ························catch (Exception ex)
1179 ························{
1180 ····························result[i] = ex.Message;
1181 ························}
1182 ····················}
1183 ····················i++;
1184 ················}
1185 ················CheckEndTransaction(true);
1186 ············}
1187 ············return result;
1188 ········}
1189
1190 ········/// <summary>
1191 ········/// Executes a sql script to generate the database tables.
1192 ········/// The function will execute any sql statements in the script
1193 ········/// which are valid according to the
1194 ········/// rules of the underlying database. Result sets are ignored.
1195 ········/// </summary>
1196 ········/// <param name="scriptFile">The script file to execute.</param>
1197 ········/// <returns></returns>
1198 ········/// <remarks>
1199 ········/// This function takes the first Connection object in the Connections list
1200 ········/// of the Mapping file und executes the script using that connection.
1201 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1202 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1203 ········/// Their message property will appear in the result array.
1204 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1205 ········/// </remarks>
1206 ········public string[] BuildDatabase(string scriptFile)
1207 ········{
1208 ············if (!File.Exists(scriptFile))
1209 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1210 ············if (!this.mappings.Connections.Any())
1211 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1212 ············Connection conn = new Connection( this.mappings );
1213 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1214 ············conn.Name = OnNewConnection( originalConnection );
1215 ············conn.Type = originalConnection.Type;
1216 ············//Connection conn = (Connection) this.mappings.Connections[0];
1217 ············return BuildDatabase(scriptFile, conn);
1218 ········}
1219
1220 ········/// <summary>
1221 ········/// Executes a sql script to generate the database tables.
1222 ········/// The function will execute any sql statements in the script
1223 ········/// which are valid according to the
1224 ········/// rules of the underlying database. Result sets are ignored.
1225 ········/// </summary>
1226 ········/// <returns>
1227 ········/// A string array, containing the error messages produced by the statements
1228 ········/// contained in the script.
1229 ········/// </returns>
1230 ········/// <remarks>
1231 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1232 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1233 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1234 ········/// This function takes the first Connection object in the Connections list
1235 ········/// of the Mapping file und executes the script using that connection.
1236 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1237 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1238 ········/// Their message property will appear in the result array.
1239 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1240 ········/// </remarks>
1241 ········public string[] BuildDatabase()
1242 ········{
1243 ············Assembly ass = Assembly.GetEntryAssembly();
1244 ············if (ass == null)
1245 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1246 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1247 ············return BuildDatabase(file);
1248 ········}
1249
1250 ········/// <summary>
1251 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1252 ········/// </summary>
1253 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1254 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1255 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1256 ········{
1257 ············if (!this.mappings.Connections.Any())
1258 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1259 ············if (conn == null)
1260 ············{
1261 ················conn = new Connection( this.mappings );
1262 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1263 ················conn.Name = OnNewConnection( originalConnection );
1264 ················conn.Type = originalConnection.Type;
1265 ············}
1266
1267 ············return new SqlPassThroughHandler( this, conn );
1268 ········}
1269
1270 ········/// <summary>
1271 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1272 ········/// </summary>
1273 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1274 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1275 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1276 ········{
1277 ············if (!this.mappings.Connections.Any())
1278 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1279 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1280 ············if (conn == null)
1281 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1282 ············return GetSqlPassThroughHandler( conn );
1283 ········}
1284
1285 ········/// <summary>
1286 ········/// Executes a xml script to generate the database tables.
1287 ········/// The function will generate and execute sql statements to perform
1288 ········/// the changes described by the xml.
1289 ········/// </summary>
1290 ········/// <returns></returns>
1291 ········/// <remarks>
1292 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1293 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1294 ········/// This function takes the first Connection object in the Connections list
1295 ········/// of the Mapping file und executes the script using that connection.
1296 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1297 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1298 ········/// Their message property will appear in the result string array.
1299 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1300 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1301 ········/// </remarks>
1302 ········public string[] PerformSchemaTransitions()
1303 ········{
1304 ············Assembly ass = Assembly.GetEntryAssembly();
1305 ············if (ass == null)
1306 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1307 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1308 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1309 ············if (fileNames.Count == 0)
1310 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1311 ············if (fileNames.Count > 1)
1312 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1313 ············return PerformSchemaTransitions( fileNames[0] );
1314 ········}
1315
1316
1317 ········/// <summary>
1318 ········/// Executes a xml script to generate the database tables.
1319 ········/// The function will generate and execute sql statements to perform
1320 ········/// the changes described by the xml.
1321 ········/// </summary>
1322 ········/// <param name="scriptFile">The script file to execute.</param>
1323 ········/// <returns></returns>
1324 ········/// <remarks>
1325 ········/// This function takes the first Connection object in the Connections list
1326 ········/// of the Mapping file und executes the script using that connection.
1327 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1328 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1329 ········/// Their message property will appear in the result string array.
1330 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1331 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1332 ········/// </remarks>
1333 ········public string[] PerformSchemaTransitions(string scriptFile)
1334 ········{
1335 ············if (!File.Exists(scriptFile))
1336 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1337
1338 ············if (!this.mappings.Connections.Any())
1339 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1340 ············Connection conn = new Connection( mappings );
1341 ············Connection originalConnection = mappings.Connections.First();
1342 ············conn.Name = OnNewConnection( originalConnection );
1343 ············conn.Type = originalConnection.Type;
1344 ············return PerformSchemaTransitions(scriptFile, conn);
1345 ········}
1346
1347
1348 ········int CompareFileName( string fn1, string fn2)
1349 ········{
1350 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1351 ············Match match = regex.Match( fn1 );
1352 ············string v1 = match.Groups[1].Value;
1353 ············match = regex.Match( fn2 );
1354 ············string v2 = match.Groups[1].Value;
1355 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1356 ········}
1357
1358 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1359 ········{
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············var resultList = new List<Guid>();
1362
1363 ············using (var handler = GetSqlPassThroughHandler())
1364 ············{
1365 ················string[] TableNames = provider.GetTableNames( connection );
1366 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1367 ················{
1368 ····················var schemaIds = provider.GetQualifiedTableName("NDOSchemaIds");
1369 ····················var sn = provider.GetQuotedName("SchemaName");
1370 ····················var id = provider.GetQuotedName("Id");
1371 ····················string sql = $"SELECT {id} from {schemaIds} WHERE {sn} ";
1372 ····················if (String.IsNullOrEmpty(schemaName))
1373 ························sql += "IS NULL;";
1374 ····················else
1375 ························sql += $"LIKE '{schemaName}'";
1376
1377 ····················using(IDataReader dr = handler.Execute(sql, true))
1378 ····················{
1379 ························while (dr.Read())
1380 ····························resultList.Add( dr.GetGuid( 0 ) );
1381 ····················}
1382 ················}
1383 ················else
1384 ················{
1385 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1386 ····················var gt = typeof(Guid);
1387 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1388 ····················var st = typeof(String);
1389 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1390 ····················var dt = typeof(DateTime);
1391 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1392 ····················string transition = $@"<NdoSchemaTransition>
1393 ····<CreateTable name=""NDOSchemaIds"">
1394 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1395 ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" isPrimary=""True"" />
1396 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1397 ····</CreateTable>
1398 </NdoSchemaTransition>";
1399 ····················XElement transitionElement = XElement.Parse(transition);
1400
1401 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1402 ····················handler.Execute(sql);
1403 ················}
1404 ················handler.CommitTransaction();
1405 ············}
1406
1407 ············return resultList.ToArray();
1408 ········}
1409
1410 ········/// <summary>
1411 ········/// Executes a xml script to generate the database tables.
1412 ········/// The function will generate and execute sql statements to perform
1413 ········/// the changes described by the xml.
1414 ········/// </summary>
1415 ········/// <param name="scriptFile">The xml script file.</param>
1416 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1417 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1418 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1419 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1420 ········{
1421 ············string schemaName = null;
1422 ············// Gespeicherte Version ermitteln.
1423 ············XElement transitionElements = XElement.Load( scriptFile );
1424 ············if (transitionElements.Attribute( "schemaName" ) != null)
1425 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1426
1427 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1428 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1429 ············var newIds = new List<Guid>();
1430 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1431 MemoryStream ms = new MemoryStream( ) ;
1432 StreamWriter sw = new StreamWriter( ms, Encoding. UTF8) ;
1433 bool hasChanges = false;
 
 
 
 
 
1434
1435 foreach ( XElement transitionElement in transitionElements. Elements( "NdoSchemaTransition") )
 
 
 
1436 ············{
1437 ················var id = transitionElement.Attribute("id")?.Value;
1438 ················if (id == null)
1439 ····················continue;
1440 ················var gid = new Guid(id);
1441 ················if (installedIds.Contains( gid ))
1442 ····················continue;
1443 hasChanges = true;
1444 sw. WriteLine( schemaTransitionGenerator. Generate( transitionElement ) ) ;
 
1445 ················newIds.Add( gid );
1446 ············}
1447
1448 if ( !hasChanges)
1449 ················return new string[] { };
1450
1451 ············// dtLiteral contains the leading and trailing quotes
1452 ············var dtLiteral = provider.GetSqlLiteral( DateTime.Now );
1453
1454 var ndoSchemaIds = provider. GetQualifiedTableName( "NDOSchemaIds") ;
1455 ············var schName = provider.GetQuotedName("SchemaName");
1456 ············var idCol = provider.GetQuotedName("Id");
1457 ············var insertTime = provider.GetQuotedName("InsertTime");
1458 ············foreach (var tid in newIds)
1459 ············{
1460 ················sw.WriteLine( $"INSERT INTO {ndoSchemaIds} ({schName},{idCol},{insertTime}) VALUES ('{schemaName}','{tid}',{dtLiteral});" );
1461 ············}
1462
1463 sw. Flush( ) ;
1464 ············ms.Position = 0L;
1465
1466 ············StreamReader sr = new StreamReader(ms, Encoding.UTF8);
1467 ············string s = sr.ReadToEnd();
1468 ············sr.Close();
1469
1470 ············return InternalPerformSchemaTransitions( ndoConn, s );
1471 ········}
1472
1473 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1474 ········{
1475 ············string[] arr = sql.Split( ';' );
1476
1477 ············string last = arr[arr.Length - 1];
1478 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1479 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1480 ············int i = 0;
1481 ············string ok = "OK";
1482 ············using (var handler = GetSqlPassThroughHandler())
1483 ············{
1484 ················handler.BeginTransaction();
1485 ················var doCommit = true;
1486 ················foreach (string statement in arr)
1487 ················{
1488 ····················if (!String.IsNullOrWhiteSpace(statement))
1489 ····················{
1490 ························try
1491 ························{
1492 ····························handler.Execute( statement.Trim() );
1493 ····························result[i] = ok;
1494 ························}
1495 ························catch (Exception ex)
1496 ························{
1497 ····························result[i] = ex.Message;
1498 ····························doCommit = false;
1499 ························}
1500 ····················}
1501 ····················i++;
1502 ················}
1503 ················if (doCommit)
1504 ····················handler.CommitTransaction();
1505 ················else
1506 ····················AbortTransaction();
1507 ············}
1508
1509 ············return result;
1510 ········}
1511 ········
1512 ········/// <summary>
1513 ········/// Transfers Data from the object to the DataRow
1514 ········/// </summary>
1515 ········/// <param name="pc"></param>
1516 ········/// <param name="row"></param>
1517 ········/// <param name="fieldNames"></param>
1518 ········/// <param name="startIndex"></param>
1519 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1520 ········{
1521 ············Class cl = GetClass( pc );
1522 ············try
1523 ············{
1524 ················pc.NDOWrite(row, fieldNames, startIndex);
1525 ················if (cl.HasEncryptedFields)
1526 ················{
1527 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1528 ····················{
1529 ························string name = field.Column.Name;
1530 ························string s = (string) row[name];
1531 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1532 ························row[name] = es;
1533 ····················}
1534 ················}
1535 ············}
1536 ············catch (Exception ex)
1537 ············{
1538 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1539 ····················+ ex.Message);
1540 ············}
1541
1542 ············if (cl.TypeNameColumn != null)
1543 ············{
1544 ················Type t = pc.GetType();
1545 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1546 ············}
1547
1548 ············var etypes = cl.EmbeddedTypes;
1549 ············foreach(string s in etypes)
1550 ············{
1551 ················try
1552 ················{
1553 ····················NDO.Mapping.Field f = cl.FindField(s);
1554 ····················if (f == null)
1555 ························continue;
1556 ····················string[] arr = s.Split('.');
1557 ····················// Suche Feld mit Namen arr[0] als object
1558 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1559 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1560 ····················Object parentOb = parentFi.GetValue(pc);
1561 ····················if (parentOb == null)
1562 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1563 ····················// Suche darin das Feld mit Namen Arr[1]
1564 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1565 ····················object o = childFi.GetValue(parentOb);
1566 ····················if (o == null
1567 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1568 ························|| o is Guid && (Guid) o == Guid.Empty)
1569 ························o = DBNull.Value;
1570 ····················row[f.Column.Name] = o;
1571 ················}
1572 ················catch (Exception ex)
1573 ················{
1574 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1575
1576 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1577 ················}
1578 ············}
1579 ········}
1580
1581 ········/// <summary>
1582 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1583 ········/// </summary>
1584 ········/// <param name="o">The parent object.</param>
1585 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1586 ········public virtual void LoadField(object o, int fieldOrdinal)
1587 ········{
1588 ············IPersistenceCapable pc = CheckPc(o);
1589 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1590 ············{
1591 ················LoadState ls = pc.NDOLoadState;
1592 ················if (ls.FieldLoadState != null)
1593 ················{
1594 ····················if (ls.FieldLoadState[fieldOrdinal])
1595 ························return;
1596 ················}
1597 ················else
1598 ················{
1599 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1600 ················}
1601 ················LoadData(o);
1602 ········}
1603 ········}
1604
1605 #pragma warning disable 419
1606 ········/// <summary>
1607 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1608 ········/// </summary>
1609 ········/// <param name="o">The hollow object.</param>
1610 ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks>
1611 #pragma warning restore 419
1612 ········public virtual void LoadData( object o )
1613 ········{
1614 ············IPersistenceCapable pc = CheckPc(o);
1615 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1616 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1617 ················return;
1618 ············Class cl = GetClass(pc);
1619 ············IQuery q;
1620 ············q = CreateOidQuery(pc, cl);
1621 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1622
1623 ············var objects = q.Execute();
1624 ············var count = objects.Count;
1625
1626 ············if (count > 1)
1627 ············{
1628 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1629 ············}
1630 ············else if (count == 0)
1631 ············{
1632 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1633 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1634 ············}
1635 ········}
1636
1637 ········/// <summary>
1638 ········/// Creates a new IQuery object for the given type
1639 ········/// </summary>
1640 ········/// <param name="t"></param>
1641 ········/// <param name="oql"></param>
1642 ········/// <param name="hollow"></param>
1643 ········/// <param name="queryLanguage"></param>
1644 ········/// <returns></returns>
1645 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1646 ········{
1647 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1648 ············Type qt = template.MakeGenericType( t );
1649 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1650 ········}
1651
1652 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1653 ········{
1654 ············ArrayList parameters = new ArrayList();
1655 ············string oql = "oid = {0}";
1656 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1657 ············q.Parameters.Add( pc.NDOObjectId );
1658 ············q.AllowSubclasses = false;
1659 ············return q;
1660 ········}
1661
1662 ········/// <summary>
1663 ········/// Mark the object dirty. The current state is
1664 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1665 ········/// object is locked in the cache.
1666 ········/// </summary>
1667 ········/// <param name="pc"></param>
1668 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1669 ········{
1670 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1671 ················return;
1672 ············SaveObjectState(pc);
1673 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1674 ········}
1675
1676 ········/// <summary>
1677 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1678 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1679 ········/// This is done, to allow easy rollback later. Also, the
1680 ········/// object is locked in the cache.
1681 ········/// </summary>
1682 ········/// <param name="pc"></param>
1683 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1684 ········{
1685 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1686
1687 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1688 ············{
1689 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1690 ············}
1691
1692 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1693 ················LoadData(pc);
1694
1695 ············// state is either (Created), Persistent, PersistentDirty
1696 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1697 ············{
1698 ················MarkDirty(pc);
1699 ············}
1700 ········}
1701
1702
1703
1704 ········/// <summary>
1705 ········/// Save current object state in DS and lock the object in the cache.
1706 ········/// The saved state can be used later to retrieve the original object value if the
1707 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1708 ········/// </summary>
1709 ········/// <param name="pc">The object that should be saved</param>
1710 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1711 ········/// <remarks>
1712 ········/// In a data row there are the following things:
1713 ········/// Item································Responsible for writing
1714 ········/// State (own, inherited, embedded)····WriteObject
1715 ········/// TimeStamp····························NDOPersistenceHandler
1716 ········/// Oid····································WriteId
1717 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1718 ········/// </remarks>
1719 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1720 ········{
1721 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1722 ············
1723 ············DataTable table = GetTable(pc);
1724 ············DataRow row = table.NewRow();
1725 ············Class cl = GetClass(pc);
1726 ············WriteObject(pc, row, cl.ColumnNames, 0);
1727 ············WriteIdToRow(pc, row);
1728 ············if (!isDeleting)
1729 ················WriteLostForeignKeysToRow(cl, pc, row);
1730 ············table.Rows.Add(row);
1731 ············row.AcceptChanges();
1732 ············
1733 ············var relations = CollectRelationStates(pc);
1734 ············cache.Lock(pc, row, relations);
1735 ········}
1736
1737 ········private void SaveFakeRow(IPersistenceCapable pc)
1738 ········{
1739 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1740 ············
1741 ············DataTable table = GetTable(pc);
1742 ············DataRow row = table.NewRow();
1743 ············Class pcClass = GetClass(pc);
1744 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1745 ············Class cl = GetClass(pc);
1746 ············//WriteObject(pc, row, cl.FieldNames, 0);
1747 ············WriteIdToRow(pc, row);
1748 ············table.Rows.Add(row);
1749 ············row.AcceptChanges();
1750 ············
1751 ············cache.Lock(pc, row, null);
1752 ········}
1753
1754 ········/// <summary>
1755 ········/// This defines one column of the row, in which we use the
1756 ········/// ColumnError property to determine, if the row is a fake row.
1757 ········/// </summary>
1758 ········/// <param name="pcClass"></param>
1759 ········/// <returns></returns>
1760 ········private string GetFakeRowOidColumnName(Class pcClass)
1761 ········{
1762 ············// In case of several OidColumns the first column defined in the mapping
1763 ············// will be the one, holding the fake row info.
1764 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1765 ········}
1766
1767 ········private bool IsFakeRow(Class cl, DataRow row)
1768 ········{
1769 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1770 ········}
1771
1772 ········/// <summary>
1773 ········/// Make a list of objects persistent.
1774 ········/// </summary>
1775 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1776 ········public void MakePersistent(System.Collections.IList list)
1777 ········{
1778 ············foreach (IPersistenceCapable pc in list)
1779 ············{
1780 ················MakePersistent(pc);
1781 ············}
1782 ········}
1783
1784 ········/// <summary>
1785 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1786 ········/// not duplicated.
1787 ········/// </summary>
1788 ········/// <param name="pc">the parent object of all relations</param>
1789 ········/// <returns></returns>
1790 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1791 ········{
1792 ············// Save state of relations
1793 ············Class c = GetClass(pc);
1794 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1795 ············foreach(Relation r in c.Relations)
1796 ············{
1797 ················if (r.Multiplicity == RelationMultiplicity.Element)
1798 ················{
1799 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1800 ················}
1801 ················else
1802 ················{
1803 ····················IList l = mappings.GetRelationContainer(pc, r);
1804 ····················if(l != null)
1805 ····················{
1806 ························l = (IList) ListCloner.CloneList(l);
1807 ····················}
1808 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1809 ················}
1810 ············}
1811
1812 ············return relations;
1813 ········}
1814
1815
1816 ········/// <summary>
1817 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1818 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1819 ········/// the list had at the beginning of the transaction.
1820 ········/// </summary>
1821 ········/// <param name="pc"></param>
1822 ········/// <param name="relations"></param>
1823 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1824 ········{
1825 ············Class c = GetClass(pc);
1826
1827 ············foreach(var entry in relations)
1828 ············{
1829 ················var r = entry.Key;
1830 ················if (r.Multiplicity == RelationMultiplicity.Element)
1831 ················{
1832 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1833 ················}
1834 ················else
1835 ················{
1836 ····················if (pc.NDOGetLoadState(r.Ordinal))
1837 ····················{
1838 ························// Help GC by clearing lists
1839 ························IList l = mappings.GetRelationContainer(pc, r);
1840 ························if(l != null)
1841 ························{
1842 ····························l.Clear();
1843 ························}
1844 ························// Restore relation
1845 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1846 ····················}
1847 ················}
1848 ············}
1849 ········}
1850
1851
1852 ········/// <summary>
1853 ········/// Generates a query for related objects without mapping table.
1854 ········/// Note: this function can't be called in polymorphic scenarios,
1855 ········/// since they need a mapping table.
1856 ········/// </summary>
1857 ········/// <returns></returns>
1858 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1859 ········{
1860 ············// At this point of execution we know,
1861 ············// that the target type is not polymorphic and is not 1:1.
1862
1863 ············// We can't fetch these objects with an NDOql query
1864 ············// since this would require a relation in the opposite direction
1865
1866 ············IList relatedObjects;
1867 ············if (l != null)
1868 ················relatedObjects = l;
1869 ············else
1870 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1871
1872 ············Type t = r.ReferencedType;
1873 ············Class cl = GetClass( t );
1874 ············var provider = cl.Provider;
1875
1876 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1877 ············var relClass = GetClass( r.ReferencedType );
1878 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1879 ············sb.Append( " WHERE " );
1880 ············int i = 0;
1881 ············List<object> parameters = new List<object>();
1882 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1883 ·············· {
1884 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1885 ·················· sb.Append( " = {" );
1886 ·················· sb.Append(i);
1887 ·················· sb.Append( '}' );
1888 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1889 ·················· if (!isLastElement)
1890 ······················ sb.Append( " AND " );
1891 ·················· i++;
1892 ·············· } );
1893
1894 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1895 ············{
1896 ················sb.Append( " AND " );
1897 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1898 ················sb.Append( " = " );
1899 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1900 ············}
1901
1902 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1903
1904 ············foreach (var p in parameters)
1905 ············{
1906 ················q.Parameters.Add( p );
1907 ············}
1908
1909 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1910
1911 ············IList l2 = q.Execute();
1912
1913 ············foreach (object o in l2)
1914 ················relatedObjects.Add( o );
1915
1916 ············return relatedObjects;
1917 ········}
1918
1919
1920 ········/// <summary>
1921 ········/// Resolves an relation. The loaded objects will be hollow.
1922 ········/// </summary>
1923 ········/// <param name="o">The parent object.</param>
1924 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1925 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1926 ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks>
1927 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1928 ········{
1929 ············IPersistenceCapable pc = CheckPc(o);
1930 ············LoadRelationInternal(pc, fieldName, hollow);
1931 ········}
1932
1933 ········
1934
1935 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1936 ········{
1937 ············IList result = null;
1938
1939 ············if (pc.NDOObjectState == NDOObjectState.Created)
1940 ················return null;
1941
1942 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1943 ················LoadData(pc);
1944
1945 ············if(r.MappingTable == null)
1946 ············{
1947 ················// 1:1 are loaded with LoadData
1948 ················if (r.Multiplicity == RelationMultiplicity.List)
1949 ················{
1950 ····················// Help GC by clearing lists
1951 ····················IList l = mappings.GetRelationContainer(pc, r);
1952 ····················if(l != null)
1953 ························l.Clear();
1954 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1955 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1956 ····················result = relatedObjects;
1957 ················}
1958 ············}
1959 ············else
1960 ············{
1961 ················DataTable dt = null;
1962
1963 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1964 ················{
1965 ····················CheckTransaction( handler, r.MappingTable.Connection );
1966 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1967 ················}
1968
1969 ················IList relatedObjects;
1970 ················if(r.Multiplicity == RelationMultiplicity.Element)
1971 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1972 ················else
1973 ················{
1974 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1975 ····················if(relatedObjects != null)
1976 ························relatedObjects.Clear();··// Objects will be reread
1977 ····················else
1978 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1979 ················}
1980 ····················
1981 ················foreach(DataRow objRow in dt.Rows)
1982 ················{
1983 ····················Type relType;
1984
1985 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1986 ····················{
1987 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1988 ························if (typeCodeObj is System.DBNull)
1989 ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) );
1990 ························relType = typeManager[(int)typeCodeObj];
1991 ························if (relType == null)
1992 ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName));
1993 ····················}························
1994 ····················else
1995 ····················{
1996 ························relType = r.ReferencedType;
1997 ····················}
1998
1999 ····················//TODO: Generic Types: Exctract the type description from the type name column
2000 ····················if (relType.IsGenericTypeDefinition)
2001 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
2002 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
2003 ····················IPersistenceCapable relObj = FindObject(id);
2004 ····················relatedObjects.Add(relObj);
2005 ················}····
2006 ················if (r.Multiplicity == RelationMultiplicity.Element)
2007 ················{
2008 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
2009 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
2010 ················}
2011 ················else
2012 ················{
2013 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
2014 ····················result = relatedObjects;
2015 ················}
2016 ············}
2017 ············// Mark relation as loaded
2018 ············pc.NDOSetLoadState(r.Ordinal, true);
2019 ············return result;
2020 ········}
2021
2022 ········/// <summary>
2023 ········/// Loads elements of a relation
2024 ········/// </summary>
2025 ········/// <param name="pc">The object which needs to load the relation</param>
2026 ········/// <param name="relationName">The name of the relation</param>
2027 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2028 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2029 ········{
2030 ············if (pc.NDOObjectState == NDOObjectState.Created)
2031 ················return null;
2032 ············Class cl = GetClass(pc);
2033
2034 ············Relation r = cl.FindRelation(relationName);
2035
2036 ············if ( r == null )
2037 ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) );
2038
2039 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2040 ················return null;
2041
2042 ············return LoadRelation(pc, r, hollow);
2043 ········}
2044
2045 ········/// <summary>
2046 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2047 ········/// a list of objects that has been read from the DB.
2048 ········/// </summary>
2049 ········/// <param name="pc">The parent object of the relations</param>
2050 ········/// <param name="row">A data row containing the state of the object</param>
2051 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2052 ········{
2053 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2054 ············Class cl = GetClass(pc);
2055 ············foreach(Relation r in cl.Relations)
2056 ············{
2057 ················if(r.MappingTable == null)
2058 ················{
2059 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2060 ····················{
2061 ························bool isNull = false;
2062 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2063 ························{
2064 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2065 ························}
2066 ························if (isNull)
2067 ························{
2068 ····························mappings.SetRelationField(pc, r.FieldName, null);
2069 ························}
2070 ························else
2071 ························{
2072 ····························Type relType;
2073 ····························if (r.HasSubclasses)
2074 ····························{
2075 ································object o = row[r.ForeignKeyTypeColumnName];
2076 ································if (o == DBNull.Value)
2077 ····································throw new NDOException(75, String.Format(
2078 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2079 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2080 ································relType = typeManager[(int)o];
2081 ····························}
2082 ····························else
2083 ····························{
2084 ································relType = r.ReferencedType;
2085 ····························}
2086 ····························if (relType == null)
2087 ····························{
2088 ································throw new NDOException(75, String.Format(
2089 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2090 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2091 ····························}
2092 ····
2093 ····························int count = r.ForeignKeyColumns.Count();
2094 ····························object[] keydata = new object[count];
2095 ····························int i = 0;
2096 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2097 ····························{
2098 ································keydata[i++] = row[fkColumn.Name];
2099 ····························}
2100
2101 ····························Type oidType = relType;
2102 ····························if (oidType.IsGenericTypeDefinition)
2103 ································oidType = mappings.GetRelationFieldType(r);
2104
2105 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2106 ····························if(childOid.IsValid())
2107 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2108 ····························else
2109 ································mappings.SetRelationField(pc, r.FieldName, null);
2110
2111 ························}
2112 ························pc.NDOSetLoadState(r.Ordinal, true);
2113 ····················}
2114 ················}
2115 ············}
2116 ········}
2117
2118 ········
2119 ········/// <summary>
2120 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2121 ········/// </summary>
2122 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2123 ········/// <param name="t">The type of the object, the id will belong to.</param>
2124 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2125 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2126 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2127 ········{
2128 ············return new ObjectId(oid.Id, t);
2129 ········}
2130
2131 ········/*
2132 ········/// <summary>
2133 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2134 ········/// </summary>
2135 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2136 ········/// <param name="t">The type of the object.</param>
2137 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2138 ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks>
2139 ········public ObjectId NewObjectId(object keyData, Type t)
2140 ········{
2141 ············if (keyData == null || keyData == DBNull.Value)
2142 ················return ObjectId.InvalidId;
2143
2144 ············Class cl =··GetClass(t);············
2145 ············
2146 ············if (cl.Oid.FieldType == typeof(int))
2147 ················return new ObjectId(new Int32Key(t, (int)keyData));
2148 ············else if (cl.Oid.FieldType == typeof(string))
2149 ················return new ObjectId(new StringKey(t, (String) keyData));
2150 ············else if (cl.Oid.FieldType == typeof(Guid))
2151 ················if (keyData is string)
2152 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2153 ················else
2154 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2155 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2156 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2157 ············else
2158 ················return ObjectId.InvalidId;
2159 ········}
2160 ········*/
2161
2162
2163 ········/*
2164 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2165 ····················{
2166 ························// The column, which hold the oid gets overwritten, if
2167 ························// Oid.FieldName contains a value.
2168 */
2169 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2170 ········{
2171 ············Class cl = GetClass(pc);
2172
2173 ············if (cl.Oid.IsDependent)
2174 ················return;
2175
2176 ············Key key = pc.NDOObjectId.Id;
2177
2178 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2179 ············{
2180 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2181 ················if (oidColumn.FieldName != null)
2182 ················{
2183 ····················row[oidColumn.Name] = key[i];
2184 ················}
2185 ············}
2186 ········}
2187
2188 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2189 ········{
2190 ············NDO.Mapping.Class cl = GetClass(pc);
2191 ············ObjectId oid = pc.NDOObjectId;
2192
2193 ············if (cl.TimeStampColumn != null)
2194 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2195
2196 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2197 ················return;
2198
2199 ············Key key = oid.Id;
2200
2201 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2202 ············{
2203 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2204 ················row[oidColumn.Name] = key[i];
2205 ············}
2206 ········}
2207
2208 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2209 ········{
2210 ············ObjectId oid = pc.NDOObjectId;
2211 ············NDO.Mapping.Class cl = GetClass(pc);
2212
2213 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2214 ················return;
2215
2216 ············Key key = oid.Id;
2217
2218 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2219 ············{
2220 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2221 ················object o = row[oidColumn.Name];
2222 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2223 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2224 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2225 ····················key[i] = new Guid((string)o);
2226 ················else
2227 ····················key[i] = o;
2228 ············}
2229
2230 ········}
2231
2232 ········private void ReadId (Cache.Entry e)
2233 ········{
2234 ············ReadIdFromRow(e.pc, e.row);
2235 ········}
2236
2237 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2238 ········{
2239 ············WriteObject(pc, row, fieldNames, 0);
2240 ········}
2241
2242 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2243 ········{
2244 ············if (cl.TimeStampColumn == null)
2245 ················return;
2246 ············object col = row[cl.TimeStampColumn];
2247 ············if (col is String)
2248 ················pc.NDOTimeStamp = new Guid(col.ToString());
2249 ············else
2250 ················pc.NDOTimeStamp = (Guid) col;
2251 ········}
2252
2253
2254
2255 ········private void ReadTimeStamp(Cache.Entry e)
2256 ········{
2257 ············IPersistenceCapable pc = e.pc;
2258 ············NDO.Mapping.Class cl = GetClass(pc);
2259 ············Debug.Assert(!IsFakeRow(cl, e.row));
2260 ············if (cl.TimeStampColumn == null)
2261 ················return;
2262 ············if (e.row[cl.TimeStampColumn] is String)
2263 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2264 ············else
2265 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2266 ········}
2267
2268 ········/// <summary>
2269 ········/// Determines, if any objects are new, changed or deleted.
2270 ········/// </summary>
2271 ········public virtual bool HasChanges
2272 ········{
2273 ············get
2274 ············{
2275 ················return cache.LockedObjects.Count > 0;
2276 ············}
2277 ········}
2278
2279 ········/// <summary>
2280 ········/// Do the update for all rows in the ds.
2281 ········/// </summary>
2282 ········/// <param name="types">Types with changes.</param>
2283 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2284 ········/// <remarks>
2285 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2286 ········/// </remarks>
2287 ········private void UpdateTypes(IList types, bool delete)
2288 ········{
2289 ············foreach(Type t in types)
2290 ············{
2291 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2292 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2293 ················{
2294 ····················CheckTransaction( handler, t );
2295 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2296 ····················handler.ConcurrencyError += ceh;
2297 ····················try
2298 ····················{
2299 ························DataTable dt = GetTable(t);
2300 ························if (delete)
2301 ····························handler.UpdateDeletedObjects( dt );
2302 ························else
2303 ····························handler.Update( dt );
2304 ····················}
2305 ····················finally
2306 ····················{
2307 ························handler.ConcurrencyError -= ceh;
2308 ····················}
2309 ················}
2310 ············}
2311 ········}
2312
2313 ········internal void UpdateCreatedMappingTableEntries()
2314 ········{
2315 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2316 ············{
2317 ················if (!e.DeleteEntry)
2318 ····················WriteMappingTableEntry(e);
2319 ············}
2320 ············// Now update all mapping tables
2321 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2322 ············{
2323 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2324 ················handler.Update(ds);
2325 ············}
2326 ········}
2327
2328 ········internal void UpdateDeletedMappingTableEntries()
2329 ········{
2330 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2331 ············{
2332 ················if (e.DeleteEntry)
2333 ····················WriteMappingTableEntry(e);
2334 ············}
2335 ············// Now update all mapping tables
2336 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2337 ············{
2338 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2339 ················handler.Update(ds);
2340 ············}
2341 ········}
2342
2343 ········/// <summary>
2344 ········/// Save all changed object into the DataSet and update the DB.
2345 ········/// When a newly created object is written to DB, the key might change. Therefore,
2346 ········/// the id is updated and the object is removed and re-inserted into the cache.
2347 ········/// </summary>
2348 ········public virtual void Save(bool deferCommit = false)
2349 ········{
2350 ············this.DeferredMode = deferCommit;
2351 ············var htOnSaving = new HashSet<ObjectId>();
2352 ············for(;;)
2353 ············{
2354 ················// We need to work on a copy of the locked objects list,
2355 ················// since the handlers might add more objects to the cache
2356 ················var lockedObjects = cache.LockedObjects.ToList();
2357 ················int count = lockedObjects.Count;
2358 ················foreach(Cache.Entry e in lockedObjects)
2359 ················{
2360 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2361 ····················{
2362 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2363 ························if (ipn != null)
2364 ························{
2365 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2366 ····························{
2367 ································ipn.OnSaving();
2368 ································htOnSaving.Add(e.pc.NDOObjectId);
2369 ····························}
2370 ························}
2371 ····················}
2372 ················}
2373 ················// The system is stable, if the count doesn't change
2374 ················if (cache.LockedObjects.Count == count)
2375 ····················break;
2376 ············}
2377
2378 ············if (this.OnSavingEvent != null)
2379 ············{
2380 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2381 ················foreach(Cache.Entry e in cache.LockedObjects)
2382 ····················onSavingObjects.Add(e.pc);
2383 ················OnSavingEvent(onSavingObjects);
2384 ············}
2385
2386 ············List<Type> types = new List<Type>();
2387 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2388 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2389 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2390 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2391 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2392
2393 ············// Save current state in DataSet
2394 ············foreach (Cache.Entry e in cache.LockedObjects)
2395 ············{
2396 ················Type objType = e.pc.GetType();
2397
2398 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2399 ····················objType = objType.GetGenericTypeDefinition();
2400
2401 ················Class cl = GetClass(e.pc);
2402 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2403 ················if(!types.Contains(objType))
2404 ················{
2405 ····················//Debug.WriteLine("Added··type " + objType.Name);
2406 ····················types.Add(objType);
2407 ················}
2408 ················NDOObjectState objectState = e.pc.NDOObjectState;
2409 ················if(objectState == NDOObjectState.Deleted)
2410 ················{
2411 ····················deletedObjects.Add(e.pc);
2412 ················}
2413 ················else if(objectState == NDOObjectState.Created)
2414 ················{
2415 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2416 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2417 ····················WriteForeignKeysToRow(e.pc, e.row);
2418 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2419 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2420 ····················//····················i = 0;
2421 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2422 ····················//····················{
2423 ····················//························rows[i++] = e2.row;
2424 ····················//····················}
2425 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2426 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2427
2428 ····················addedCacheEntries.Add(e);
2429 ····················addedObjects.Add( e.pc );
2430 ····················//····················objectState = NDOObjectState.Persistent;
2431 ················}
2432 ················else
2433 ················{
2434 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2435 ························changedObjects.Add( e.pc );
2436 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2437 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2438 ················}
2439 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2440 ················{
2441 ····················hollowModeObjects.Add(e.pc);
2442 ················}
2443 ············}
2444
2445 ············// Before we delete any db rows, we have to make sure, to delete mapping
2446 ············// table entries first, which might have relations to the db rows to be deleted
2447 ············UpdateDeletedMappingTableEntries();
2448
2449 ············// Update DB
2450 ············if (ds.HasChanges())
2451 ············{
2452
2453 ················// We need the reversed update order for deletions.
2454 ················types.Sort( ( t1, t2 ) =>
2455 ················{
2456 ····················int i1 = mappings.GetUpdateOrder( t1 );
2457 ····················int i2 = mappings.GetUpdateOrder( t2 );
2458 ····················if (i1 < i2)
2459 ····················{
2460 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2461 ····························i1 += 100000;
2462 ····················}
2463 ····················else
2464 ····················{
2465 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2466 ····························i2 += 100000;
2467 ····················}
2468 ····················return i2 - i1;
2469 ················} );
2470
2471 ················// Delete records first
2472
2473 ················UpdateTypes(types, true);
2474
2475 ················// Now do all other updates in correct order.
2476 ················types.Reverse();
2477
2478 ················UpdateTypes(types, false);
2479 ················
2480 ················ds.AcceptChanges();
2481 ················if(createdDirectObjects.Count > 0)
2482 ················{
2483 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2484 ····················// They must be written again to store the correct foreign keys.
2485 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2486 ····················{
2487 ························Class cl = GetClass(pc);
2488 ························DataRow r = this.cache.GetDataRow(pc);
2489 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2490 ························object o = r[fakeColumnName];
2491 ························r[fakeColumnName] = o;
2492 ····················}
2493
2494 ····················UpdateTypes(types, false);
2495 ················}
2496
2497 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2498 ················foreach(Cache.Entry e in addedCacheEntries)
2499 ················{
2500 ····················cache.DeregisterLockedObject(e.pc);
2501 ····················ReadId(e);
2502 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2503 ················}
2504
2505 ················// Now update all mapping tables. Because of possible subclasses, there is no
2506 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2507 ················// tables only after all other objects have been written to ensure correct foreign keys.
2508 ················UpdateCreatedMappingTableEntries();
2509
2510 ················// The rows may contain now new Ids, which should be
2511 ················// stored in the lostRowInfo's before the rows get detached
2512 ················foreach(Cache.Entry e in cache.LockedObjects)
2513 ················{
2514 ····················if (e.row.RowState != DataRowState.Detached)
2515 ····················{
2516 ························IPersistenceCapable pc = e.pc;
2517 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2518 ····················}
2519 ················}
2520
2521 ················ds.AcceptChanges();
2522 ············}
2523
2524 ············EndSave(!deferCommit);
2525
2526 ············foreach(IPersistenceCapable pc in deletedObjects)
2527 ············{
2528 ················MakeObjectTransient(pc, false);
2529 ············}
2530
2531 ············ds.Clear();
2532 ············mappingHandler.Clear();
2533 ············createdDirectObjects.Clear();
2534 ············createdMappingTableObjects.Clear();
2535 ············this.relationChanges.Clear();
2536
2537 ············if(hollowMode)
2538 ············{
2539 ················MakeHollow(hollowModeObjects);
2540 ············}
2541
2542 ············if (this.OnSavedEvent != null)
2543 ············{
2544 ················AuditSet auditSet = new AuditSet()
2545 ················{
2546 ····················ChangedObjects = changedObjects,
2547 ····················CreatedObjects = addedObjects,
2548 ····················DeletedObjects = deletedObjects
2549 ················};
2550 ················this.OnSavedEvent( auditSet );
2551 ············}
2552 ········}
2553
2554 ········private void EndSave(bool forceCommit)
2555 ········{
2556 ············foreach(Cache.Entry e in cache.LockedObjects)
2557 ············{
2558 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2559 ····················this.ReadTimeStamp(e);
2560 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2561 ············}
2562
2563 ············cache.UnlockAll();
2564
2565 ············CheckEndTransaction(forceCommit);
2566 ········}
2567
2568 ········/// <summary>
2569 ········/// Write all foreign keys for 1:1-relations.
2570 ········/// </summary>
2571 ········/// <param name="pc">The persistent object.</param>
2572 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2573 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2574 ········{
2575 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2576 ············{
2577 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2578 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2579
2580 ················if ( relObj != null )
2581 ················{
2582 ····················int i = 0;
2583 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2584 ····················{
2585 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2586 ····················}
2587 ····················if ( r.ForeignKeyTypeColumnName != null )
2588 ····················{
2589 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2590 ····················}
2591 ················}
2592 ················else
2593 ················{
2594 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2595 ····················{
2596 ························pcRow[fkColumn.Name] = DBNull.Value;
2597 ····················}
2598 ····················if ( r.ForeignKeyTypeColumnName != null )
2599 ····················{
2600 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2601 ····················}
2602 ················}
2603 ············}
2604 ········}
2605
2606
2607
2608 ········/// <summary>
2609 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2610 ········/// </summary>
2611 ········/// <param name="e">the mapping table entry</param>
2612 ········private void WriteMappingTableEntry(MappingTableEntry e)
2613 ········{
2614 ············IPersistenceCapable pc = e.ParentObject;
2615 ············IPersistenceCapable relObj = e.RelatedObject;
2616 ············Relation r = e.Relation;
2617 ············DataTable dt = GetTable(r.MappingTable.TableName);
2618 ············DataRow row = dt.NewRow();
2619 ············int i = 0;
2620 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2621 ············{
2622 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2623 ············}
2624 ············i = 0;
2625 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2626 ············{
2627 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2628 ············}
2629
2630 ············if (r.ForeignKeyTypeColumnName != null)
2631 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2632 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2633 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2634
2635 ············dt.Rows.Add(row);
2636 ············if(e.DeleteEntry)
2637 ············{
2638 ················row.AcceptChanges();
2639 ················row.Delete();
2640 ············}
2641
2642 ············IMappingTableHandler handler;
2643 ············if (!mappingHandler.TryGetValue( r, out handler ))
2644 ············{
2645 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2646 ············}
2647 ········}
2648
2649
2650 ········/// <summary>
2651 ········/// Undo changes of a certain object
2652 ········/// </summary>
2653 ········/// <param name="o">Object to undo</param>
2654 ········public void Restore(object o)
2655 ········{············
2656 ············IPersistenceCapable pc = CheckPc(o);
2657 ············Cache.Entry e = null;
2658 ············foreach (Cache.Entry entry in cache.LockedObjects)
2659 ············{
2660 ················if (entry.pc == pc)
2661 ················{
2662 ····················e = entry;
2663 ····················break;
2664 ················}
2665 ············}
2666 ············if (e == null)
2667 ················return;
2668 ············Class cl = GetClass(e.pc);
2669 ············switch (pc.NDOObjectState)
2670 ············{
2671 ················case NDOObjectState.PersistentDirty:
2672 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2673 ····················foreach(Relation r in cl.Relations)
2674 ····················{
2675 ························if (r.Multiplicity == RelationMultiplicity.Element)
2676 ························{
2677 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2678 ····························if (subPc != null && cache.IsLocked(subPc))
2679 ································Restore(subPc);
2680 ························}
2681 ························else
2682 ························{
2683 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2684 ································continue;
2685 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2686 ····························if (subList != null)
2687 ····························{
2688 ································foreach(IPersistenceCapable subPc2 in subList)
2689 ································{
2690 ····································if (cache.IsLocked(subPc2))
2691 ········································Restore(subPc2);
2692 ································}
2693 ····························}
2694 ························}
2695 ····················}
2696 ····················RestoreRelatedObjects(pc, e.relations);
2697 ····················e.row.RejectChanges();
2698 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2699 ····················cache.Unlock(pc);
2700 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2701 ····················break;
2702 ················case NDOObjectState.Created:
2703 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2704 ····················cache.Unlock(pc);
2705 ····················MakeObjectTransient(pc, true);
2706 ····················break;
2707 ················case NDOObjectState.Deleted:
2708 ····················if (!this.IsFakeRow(cl, e.row))
2709 ····················{
2710 ························e.row.RejectChanges();
2711 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2712 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2713 ····················}
2714 ····················else
2715 ····················{
2716 ························e.row.RejectChanges();
2717 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2718 ····················}
2719 ····················cache.Unlock(pc);
2720 ····················break;
2721
2722 ············}
2723 ········}
2724
2725 ········/// <summary>
2726 ········/// Aborts a pending transaction without restoring the object state.
2727 ········/// </summary>
2728 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2729 ········public virtual void AbortTransaction()
2730 ········{
2731 ············TransactionScope.Dispose();
2732 ········}
2733
2734 ········/// <summary>
2735 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2736 ········/// </summary>
2737 ········public virtual void Abort()
2738 ········{
2739 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2740 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2741 ············// individual row.
2742 ············createdDirectObjects.Clear();
2743 ············createdMappingTableObjects.Clear();
2744 ············ArrayList deletedObjects = new ArrayList();
2745 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2746
2747 ············// Read all objects from DataSet
2748 ············foreach (Cache.Entry e in cache.LockedObjects)
2749 ············{
2750 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2751
2752 ················Class cl = GetClass(e.pc);
2753 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2754 ················if (!isFakeRow)
2755 ················{
2756 ····················RestoreRelatedObjects(e.pc, e.relations);
2757 ················}
2758 ················else
2759 ················{
2760 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2761 ················}
2762
2763 ················switch(e.pc.NDOObjectState)
2764 ················{
2765 ····················case NDOObjectState.Created:
2766 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2767 ························deletedObjects.Add(e.pc);
2768 ························break;
2769
2770 ····················case NDOObjectState.PersistentDirty:
2771 ························e.row.RejectChanges();
2772 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2773 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2774 ························break;
2775
2776 ····················case NDOObjectState.Deleted:
2777 ························if (!isFakeRow)
2778 ························{
2779 ····························e.row.RejectChanges();
2780 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2781 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2782 ························}
2783 ························else
2784 ························{
2785 ····························e.row.RejectChanges();
2786 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2787 ························}
2788 ························break;
2789
2790 ····················default:
2791 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2792 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2793 ························//break;
2794 ················}
2795 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2796 ················{
2797 ····················hollowModeObjects.Add(e.pc);
2798 ················}
2799 ············}
2800 ············cache.UnlockAll();
2801 ············foreach(IPersistenceCapable pc in deletedObjects)
2802 ············{
2803 ················MakeObjectTransient(pc, true);
2804 ············}
2805 ············ds.Clear();
2806 ············mappingHandler.Clear();
2807 ············if(hollowMode)
2808 ············{
2809 ················MakeHollow(hollowModeObjects);
2810 ············}
2811
2812 ············this.relationChanges.Clear();
2813
2814
2815 ············AbortTransaction();
2816 ········}
2817
2818
2819 ········/// <summary>
2820 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2821 ········/// disable future access.
2822 ········/// </summary>
2823 ········/// <param name="pc"></param>
2824 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2825 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2826 ········{
2827 ············cache.Deregister(pc);
2828 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2829 ············if(removeId)
2830 ············{
2831 ················pc.NDOObjectId = null;
2832 ············}
2833 ············pc.NDOObjectState = NDOObjectState.Transient;
2834 ············pc.NDOStateManager = null;
2835 ········}
2836
2837 ········/// <summary>
2838 ········/// Makes an object transient.
2839 ········/// The object can be used afterwards, but changes will not be saved in the database.
2840 ········/// </summary>
2841 ········/// <remarks>
2842 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2843 ········/// </remarks>
2844 ········/// <param name="o">The object to detach.</param>
2845 ········public void MakeTransient(object o)
2846 ········{
2847 ············IPersistenceCapable pc = CheckPc(o);
2848 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2849 ············{
2850 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2851 ············}
2852
2853 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2854 ············{
2855 ················LoadData(pc);
2856 ············}
2857 ············MakeObjectTransient(pc, true);
2858 ········}
2859
2860
2861 ········/// <summary>
2862 ········/// Make all objects of a list transient.
2863 ········/// </summary>
2864 ········/// <param name="list">the list of transient objects</param>
2865 ········public void MakeTransient(System.Collections.IList list)
2866 ········{
2867 ············foreach (IPersistenceCapable pc in list)
2868 ················MakeTransient(pc);
2869 ········}
2870
2871 ········/// <summary>
2872 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2873 ········/// </summary>
2874 ········/// <param name="o">The object to remove</param>
2875 ········public void Delete(object o)
2876 ········{
2877 ············IPersistenceCapable pc = CheckPc(o);
2878 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2879 ············{
2880 ················throw new NDOException( 120, "Can't delete transient object" );
2881 ············}
2882 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2883 ············{
2884 ················Delete(pc, true);
2885 ············}
2886 ········}
2887
2888
2889 ········private void LoadAllRelations(object o)
2890 ········{
2891 ············IPersistenceCapable pc = CheckPc(o);
2892 ············Class cl = GetClass(pc);
2893 ············foreach(Relation r in cl.Relations)
2894 ············{
2895 ················if (!pc.NDOGetLoadState(r.Ordinal))
2896 ····················LoadRelation(pc, r, true);
2897 ············}
2898 ········}
2899
2900
2901 ········/// <summary>
2902 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2903 ········/// </summary>
2904 ········/// <remarks>
2905 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2906 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2907 ········/// the parent object.
2908 ········/// </remarks>
2909 ········/// <param name="pc">the object to remove</param>
2910 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2911 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2912 ········{
2913 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2914 ············//Debug.Indent();
2915 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2916 ············if (idn != null)
2917 ················idn.OnDelete();
2918
2919 ············LoadAllRelations(pc);
2920 ············DeleteRelatedObjects(pc, checkAssoziations);
2921
2922 ············switch(pc.NDOObjectState)
2923 ············{
2924 ················case NDOObjectState.Transient:
2925 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2926
2927 ················case NDOObjectState.Created:
2928 ····················DataRow row = cache.GetDataRow(pc);
2929 ····················row.Delete();
2930 ····················ArrayList cdosToDelete = new ArrayList();
2931 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2932 ························if ((object)cdo == (object)pc)
2933 ····························cdosToDelete.Add(cdo);
2934 ····················foreach (object o in cdosToDelete)
2935 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2936 ····················MakeObjectTransient(pc, true);
2937 ····················break;
2938 ················case NDOObjectState.Persistent:
2939 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2940 ························SaveObjectState(pc, true);
2941 ····················row = cache.GetDataRow(pc);
2942 ····················row.Delete();
2943 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2944 ····················break;
2945 ················case NDOObjectState.Hollow:
2946 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2947 ························SaveFakeRow(pc);
2948 ····················row = cache.GetDataRow(pc);
2949 ····················row.Delete();
2950 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2951 ····················break;
2952
2953 ················case NDOObjectState.PersistentDirty:
2954 ····················row = cache.GetDataRow(pc);
2955 ····················row.Delete();
2956 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2957 ····················break;
2958
2959 ················case NDOObjectState.Deleted:
2960 ····················break;
2961 ············}
2962
2963 ············//Debug.Unindent();
2964 ········}
2965
2966
2967 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2968 ········{
2969 ············MappingTableEntry mte = null;
2970 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2971 ············{
2972 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2973 ················{
2974 ····················mte = e;
2975 ····················break;
2976 ················}
2977 ············}
2978
2979 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2980 ············{
2981 ················if (mte != null)
2982 ····················createdMappingTableObjects.Remove(mte);
2983 ············}
2984 ············else
2985 ············{
2986 ················if (mte == null)
2987 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2988 ············}
2989 ········}
2990
2991 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2992 ········{
2993 ············// Two tasks: a) Null the foreign key
2994 ············//··············b) remove the element from the foreign container
2995
2996 ············if (!r.Bidirectional)
2997 ················return;
2998
2999 ············// this keeps the oid valid
3000 ············if (GetClass(child.GetType()).Oid.IsDependent)
3001 ················return;
3002
3003 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
3004 ············{
3005 ················LoadAndMarkDirty(child);
3006 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3007 ············}
3008 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
3009 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
3010 ············{
3011 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
3012 ····················LoadRelation(child, r.ForeignRelation, true);
3013 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
3014 ················if (l == null)
3015 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3016 ················ObjectListManipulator.Remove(l, pc);
3017 ················// Don't need to delete the mapping table entry, because that was done
3018 ················// through the parent.
3019 ············}
3020 ········}
3021
3022 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3023 ········{
3024 ············// 1) Element····nomap····ass
3025 ············// 2) Element····nomap····comp
3026 ············// 3) Element····map········ass
3027 ············// 4) Element····map········comp
3028 ············// 5) List········nomap····ass
3029 ············// 6) List········nomap····comp
3030 ············// 7) List········map········ass
3031 ············// 8) List········map········comp
3032
3033 ············// Two tasks: Null foreign key and, if Composition, delete the child
3034
3035 ············// If Mapping Table, delete the entry - 3,7
3036 ············// If List and assoziation, null the foreign key in the foreign class - 5
3037 ············// If Element, null the foreign key in the own class 1,2,3,4
3038 ············// If composition, delete the child 2,4,6,8
3039
3040 ············// If the relObj is newly created
3041 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3042 ············Class childClass = GetClass(child);
3043
3044 ············if (r.MappingTable != null)··// 3,7
3045 ············{················
3046 ················DeleteMappingTableEntry(pc, r, child);
3047 ············}
3048 ············else if (r.Multiplicity == RelationMultiplicity.List
3049 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3050 ············{················
3051 ················LoadAndMarkDirty(child);
3052 ················DataRow row = this.cache.GetDataRow(child);
3053 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3054 ················{
3055 ····················row[fkColumnn.Name] = DBNull.Value;
3056 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3057 ················}
3058 ············}
3059 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3060 ············{
3061 ················LoadAndMarkDirty(pc);
3062 ············}
3063 ············if (r.Composition || childClass.Oid.IsDependent)
3064 ············{
3065 #if DEBUG
3066 ················if (child.NDOObjectState == NDOObjectState.Transient)
3067 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3068 #endif
3069 ················// Deletes the foreign key in case of List multiplicity
3070 ················// In case of Element multiplicity, the parent is either deleted,
3071 ················// or RemoveRelatedObject is called because the relation has been nulled.
3072 ················Delete(child);··
3073 ············}
3074 ········}
3075
3076 ········/// <summary>
3077 ········/// Remove a related object
3078 ········/// </summary>
3079 ········/// <param name="pc"></param>
3080 ········/// <param name="r"></param>
3081 ········/// <param name="child"></param>
3082 ········/// <param name="calledFromStateManager"></param>
3083 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3084 ········{
3085 ············//TODO: We need a relation management, which is independent of
3086 ············//the state management of an object. At the moment the relation
3087 ············//lists or elements are cached for restore, if an object is marked dirty.
3088 ············//Thus we have to mark dirty our parent object in any case at the moment.
3089 ············if (calledFromStateManager)
3090 ················MarkDirty(pc);
3091
3092 ············// Object can be deleted in an OnDelete-Handler
3093 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3094 ················return;
3095 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3096 ············// Preconditions
3097 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3098 ············// The Object state is checked there
3099
3100 ············// If there is a composition in the opposite direction
3101 ············// && the other direction hasn't been processed
3102 ············// throw an exception.
3103 ············// If an exception is thrown at this point, have a look at IsLocked....
3104 ············if (r.Bidirectional && r.ForeignRelation.Composition
3105 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3106 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3107
3108 ············if (!removeLock.GetLock(pc, r, child))
3109 ················return;
3110
3111 ············try
3112 ············{
3113 ················// must be in this order, since the child
3114 ················// can be deleted in DeleteOrNullRelation
3115 ················//if (changeForeignRelations)
3116 ················DeleteOrNullForeignRelation(pc, r, child);
3117 ················DeleteOrNullRelation(pc, r, child);
3118 ············}
3119 ············finally
3120 ············{
3121 ················removeLock.Unlock(pc, r, child);
3122 ············}
3123
3124 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3125 ········}
3126
3127 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3128 ········{
3129 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3130 ············//············Debug.Indent();
3131 ············if (r.Multiplicity == RelationMultiplicity.Element)
3132 ············{
3133 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3134 ················if(child != null)
3135 ················{
3136 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3137 ····················//····················{
3138 ····················//························if (!r.ForeignRelation.Composition)
3139 ····················//························{
3140 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3141 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3142 ····················//························}
3143 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3144 ····················//························else
3145 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3146 ····················//····················}
3147 ····················InternalRemoveRelatedObject(pc, r, child, false);
3148 ················}
3149 ············}
3150 ············else
3151 ············{
3152 ················IList list = mappings.GetRelationContainer(pc, r);
3153 ················if(list != null && list.Count > 0)
3154 ················{
3155 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3156 ····················//····················{
3157 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3158 ····················//····················}
3159 ····················// Since RemoveRelatedObjects probably changes the list,
3160 ····················// we iterate through a copy of the list.
3161 ····················ArrayList al = new ArrayList(list);
3162 ····················foreach(IPersistenceCapable relObj in al)
3163 ····················{
3164 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3165 ····················}
3166 ················}
3167 ············}
3168 ············//············Debug.Unindent();
3169 ········}
3170
3171 ········/// <summary>
3172 ········/// Remove all related objects from a parent.
3173 ········/// </summary>
3174 ········/// <param name="pc">the parent object</param>
3175 ········/// <param name="checkAssoziations"></param>
3176 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3177 ········{
3178 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3179 ············//············Debug.Indent();
3180 ············// delete all related objects:
3181 ············Class parentClass = GetClass(pc);
3182 ············// Remove Assoziations first
3183 ············foreach(Relation r in parentClass.Relations)
3184 ············{
3185 ················if (!r.Composition)
3186 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3187 ············}
3188 ············foreach(Relation r in parentClass.Relations)
3189 ············{
3190 ················if (r.Composition)
3191 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3192 ············}
3193
3194 ············//············Debug.Unindent();
3195 ········}
3196
3197 ········/// <summary>
3198 ········/// Delete a list of objects
3199 ········/// </summary>
3200 ········/// <param name="list">the list of objects to remove</param>
3201 ········public void Delete(IList list)
3202 ········{
3203 ············for (int i = 0; i < list.Count; i++)
3204 ············{
3205 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3206 ················Delete(pc);
3207 ············}
3208 ········}
3209
3210 ········/// <summary>
3211 ········/// Make object hollow. All relations will be unloaded and object data will be
3212 ········/// newly fetched during the next touch of a persistent field.
3213 ········/// </summary>
3214 ········/// <param name="o"></param>
3215 ········public virtual void MakeHollow(object o)
3216 ········{
3217 ············IPersistenceCapable pc = CheckPc(o);
3218 ············MakeHollow(pc, false);
3219 ········}
3220
3221 ········/// <summary>
3222 ········/// Make the object hollow if it is persistent. Unload all complex data.
3223 ········/// </summary>
3224 ········/// <param name="o"></param>
3225 ········/// <param name="recursive">if true then unload related objects as well</param>
3226 ········public virtual void MakeHollow(object o, bool recursive)
3227 ········{
3228 ············IPersistenceCapable pc = CheckPc(o);
3229 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3230 ················return;
3231 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3232 ············{
3233 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3234 ············}
3235 ············pc.NDOObjectState = NDOObjectState.Hollow;
3236 ············MakeRelationsHollow(pc, recursive);
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all objects of a list hollow.
3241 ········/// </summary>
3242 ········/// <param name="list">the list of objects that should be made hollow</param>
3243 ········public virtual void MakeHollow(System.Collections.IList list)
3244 ········{
3245 ············MakeHollow(list, false);
3246 ········}
3247
3248 ········/// <summary>
3249 ········/// Make all objects of a list hollow.
3250 ········/// </summary>
3251 ········/// <param name="list">the list of objects that should be made hollow</param>
3252 ········/// <param name="recursive">if true then unload related objects as well</param>
3253 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3254 ········{
3255 ············foreach (IPersistenceCapable pc in list)
3256 ················MakeHollow(pc, recursive);················
3257 ········}
3258
3259 ········/// <summary>
3260 ········/// Make all unlocked objects in the cache hollow.
3261 ········/// </summary>
3262 ········public virtual void MakeAllHollow()
3263 ········{
3264 ············foreach(var pc in cache.UnlockedObjects)
3265 ············{
3266 ················MakeHollow(pc, false);
3267 ············}
3268 ········}
3269
3270 ········/// <summary>
3271 ········/// Make relations hollow.
3272 ········/// </summary>
3273 ········/// <param name="pc">The parent object</param>
3274 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3275 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3276 ········{
3277 ············Class c = GetClass(pc);
3278 ············foreach(Relation r in c.Relations)
3279 ············{
3280 ················if (r.Multiplicity == RelationMultiplicity.Element)
3281 ················{
3282 ····················mappings.SetRelationField(pc, r.FieldName, null);
3283 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3284 ····················//····················if((null != child) && recursive)
3285 ····················//····················{
3286 ····················//························MakeHollow(child, true);
3287 ····················//····················}
3288 ················}
3289 ················else
3290 ················{
3291 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3292 ························continue;
3293 ····················// Help GC by clearing lists
3294 ····················IList l = mappings.GetRelationContainer(pc, r);
3295 ····················if(l != null)
3296 ····················{
3297 ························if(recursive)
3298 ························{
3299 ····························MakeHollow(l, true);
3300 ························}
3301 ························l.Clear();
3302 ····················}
3303 ····················// Hollow relation
3304 ····················mappings.SetRelationContainer(pc, r, null);
3305 ················}
3306 ············}
3307 ············ClearRelationState(pc);
3308 ········}
3309
3310 ········private void ClearRelationState(IPersistenceCapable pc)
3311 ········{
3312 ············Class cl = GetClass(pc);
3313 ············foreach(Relation r in cl.Relations)
3314 ················pc.NDOSetLoadState(r.Ordinal, false);
3315 ········}
3316
3317 ········private void SetRelationState(IPersistenceCapable pc)
3318 ········{
3319 ············Class cl = GetClass(pc);
3320 ············// Due to a bug in the enhancer the constructors are not always patched right,
3321 ············// so NDOLoadState might be uninitialized
3322 ············if (pc.NDOLoadState == null)
3323 ············{
3324 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3325 ················if (fi == null)
3326 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3327 ················fi.SetValue(pc, new LoadState());
3328 ············}
3329
3330 ············// After serialization the relation load state is null
3331 ············if (pc.NDOLoadState.RelationLoadState == null)
3332 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3333 ············foreach(Relation r in cl.Relations)
3334 ················pc.NDOSetLoadState(r.Ordinal, true);
3335 ········}
3336
3337 ········/// <summary>
3338 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3339 ········/// </summary>
3340 ········/// <param name="t">The type of the persistent object</param>
3341 ········/// <returns></returns>
3342 ········public IPersistenceCapable CreateObject(Type t)
3343 ········{
3344 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3345 ········}
3346
3347 ········/// <summary>
3348 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3349 ········/// </summary>
3350 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3351 ········/// <returns></returns>
3352 ········public T CreateObject<T>()
3353 ········{
3354 ············return (T)CreateObject( typeof( T ) );
3355 ········}
3356
3357 ········/// <summary>
3358 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3359 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3360 ········/// is performed.
3361 ········/// </summary>
3362 ········/// <param name="t">The type of the object to retrieve.</param>
3363 ········/// <param name="keyData">The key value to build the object id.</param>
3364 ········/// <returns>A hollow object</returns>
3365 ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks>
3366 ········public IPersistenceCapable FindObject(Type t, object keyData)
3367 ········{
3368 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3369 ············return FindObject(oid);
3370 ········}
3371
3372 ········/// <summary>
3373 ········/// Finds an object using a short id.
3374 ········/// </summary>
3375 ········/// <param name="encodedShortId"></param>
3376 ········/// <returns></returns>
3377 ········public IPersistenceCapable FindObject(string encodedShortId)
3378 ········{
3379 ············string shortId = encodedShortId.Decode();
3380 ············string[] arr = shortId.Split( '-' );
3381 ············if (arr.Length != 3)
3382 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3383 ············Type t = shortId.GetObjectType(this);··// try readable format
3384 ············if (t == null)
3385 ············{
3386 ················int typeCode = 0;
3387 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3388 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3389 ················t = this.typeManager[typeCode];
3390 ················if (t == null)
3391 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3392 ············}
3393
3394 ············Class cls = GetClass( t );
3395 ············if (cls == null)
3396 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3397
3398 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3399 ············string[] oidValues = arr[2].Split( ' ' );
3400
3401 ············int i = 0;
3402 ············foreach (var oidValue in oidValues)
3403 ············{
3404 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3405 ················if (oidType == typeof( int ))
3406 ················{
3407 ····················int key;
3408 ····················if (!int.TryParse( oidValue, out key ))
3409 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3410 ····················if (key > (int.MaxValue >> 1))
3411 ························key = -(int.MaxValue - key);
3412 ····················keydata[i] = key;
3413 ················}
3414 ················else if (oidType == typeof( Guid ))
3415 ················{
3416 ····················Guid key;
3417 ····················if (!Guid.TryParse( oidValue, out key ))
3418 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3419 ····················keydata[i] = key;
3420 ················}
3421 ················else if (oidType == typeof( string ))
3422 ················{
3423 ····················keydata[i] = oidValue;
3424 ················}
3425 ················else
3426 ················{
3427 ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) );
3428 ················}
3429
3430 ················i++;
3431 ············}
3432
3433 ············if (keydata.Length == 1)
3434 ················return FindObject( t, keydata[0] );
3435
3436 ············return FindObject( t, keydata );············
3437 ········}
3438
3439 ········/// <summary>
3440 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3441 ········/// instance of the object is returned. In either case, the DB is not accessed!
3442 ········/// </summary>
3443 ········/// <param name="id">Object id</param>
3444 ········/// <returns>The object to retrieve in hollow state</returns>········
3445 ········public IPersistenceCapable FindObject(ObjectId id)
3446 ········{
3447 ············if(id == null)
3448 ············{
3449 ················throw new ArgumentNullException("id");
3450 ············}
3451
3452 ············if(!id.IsValid())
3453 ············{
3454 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3455 ············}
3456
3457 ············IPersistenceCapable pc = cache.GetObject(id);
3458 ············if(pc == null)
3459 ············{
3460 ················pc = CreateObject(id.Id.Type);
3461 ················pc.NDOObjectId = id;
3462 ················pc.NDOStateManager = sm;
3463 ················pc.NDOObjectState = NDOObjectState.Hollow;
3464 ················cache.UpdateCache(pc);
3465 ············}
3466 ············return pc;
3467 ········}
3468
3469
3470 ········/// <summary>
3471 ········/// Reload an object from the database.
3472 ········/// </summary>
3473 ········/// <param name="o">The object to be reloaded.</param>
3474 ········public virtual void Refresh(object o)
3475 ········{
3476 ············IPersistenceCapable pc = CheckPc(o);
3477 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3478 ············{
3479 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3480 ············}
3481
3482 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3483 ················return; // Cannot update objects in current transaction
3484
3485 ············MakeHollow(pc);
3486 ············LoadData(pc);
3487 ········}
3488
3489 ········/// <summary>
3490 ········/// Refresh a list of objects.
3491 ········/// </summary>
3492 ········/// <param name="list">The list of objects to be refreshed.</param>
3493 ········public virtual void Refresh(IList list)
3494 ········{
3495 ············foreach (IPersistenceCapable pc in list)
3496 ················Refresh(pc);························
3497 ········}
3498
3499 ········/// <summary>
3500 ········/// Refreshes all unlocked objects in the cache.
3501 ········/// </summary>
3502 ········public virtual void RefreshAll()
3503 ········{
3504 ············Refresh( cache.UnlockedObjects.ToList() );
3505 ········}
3506
3507 ········/// <summary>
3508 ········/// Closes the PersistenceManager and releases all resources.
3509 ········/// </summary>
3510 ········public override void Close()
3511 ········{
3512 ············if (this.isClosing)
3513 ················return;
3514 ············this.isClosing = true;
3515 ············TransactionScope.Dispose();
3516 ············UnloadCache();
3517 ············base.Close();
3518 ········}
3519
3520 ········internal void LogIfVerbose( string msg )
3521 ········{
3522 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3523 ················Logger.LogDebug( msg );
3524 ········}
3525
3526
3527 ········#endregion
3528
3529
3530 #region Class extent
3531 ········/// <summary>
3532 ········/// Gets all objects of a given class.
3533 ········/// </summary>
3534 ········/// <param name="t">the type of the class</param>
3535 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3536 ········public virtual IList GetClassExtent(Type t)
3537 ········{
3538 ············return GetClassExtent(t, true);
3539 ········}
3540
3541 ········/// <summary>
3542 ········/// Gets all objects of a given class.
3543 ········/// </summary>
3544 ········/// <param name="t">The type of the class.</param>
3545 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3546 ········/// <returns>A list of all persistent objects of the given class.</returns>
3547 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3548 ········public virtual IList GetClassExtent(Type t, bool hollow)
3549 ········{
3550 ············IQuery q = NewQuery( t, null, hollow );
3551 ············return q.Execute();
3552 ········}
3553
3554 #endregion
3555
3556 #region Query engine
3557
3558
3559 ········/// <summary>
3560 ········/// Returns a virtual table for Linq queries.
3561 ········/// </summary>
3562 ········/// <typeparam name="T"></typeparam>
3563 ········/// <returns></returns>
3564 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3565 ········{
3566 ············return new VirtualTable<T>( this );
3567 ········}
3568
3569 ········
3570 ········/// <summary>
3571 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3572 ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have
3573 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3574 ········/// not lose the foreign key it will be stored as part of the object.
3575 ········/// </summary>
3576 ········/// <param name="cl"></param>
3577 ········/// <param name="pc"></param>
3578 ········/// <param name="row"></param>
3579 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3580 ········{
3581 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3582 ············{
3583 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3584 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3585 ················foreach(string colName in cl.FKColumnNames)
3586 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3587 ················pc.NDOLoadState.LostRowInfo = kvl;················
3588 ············}
3589 ········}
3590
3591 ········/// <summary>
3592 ········/// Writes information into the data row, which cannot be stored in the object.
3593 ········/// </summary>
3594 ········/// <param name="cl"></param>
3595 ········/// <param name="pc"></param>
3596 ········/// <param name="row"></param>
3597 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3598 ········{
3599 ············if (cl.FKColumnNames != null)
3600 ············{
3601 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3602 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3603 ················if (kvl == null)
3604 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3605 ················foreach (KeyValuePair pair in kvl)
3606 ····················row[(string) pair.Key] = pair.Value;
3607 ············}
3608 ········}
3609
3610 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3611 ········{
3612 ············ReadObject(pc, row, cl.ColumnNames, 0);
3613 ············ReadTimeStamp(cl, pc, row);
3614 ············ReadLostForeignKeysFromRow(cl, pc, row);
3615 ············LoadRelated1To1Objects(pc, row);
3616 ············pc.NDOObjectState = NDOObjectState.Persistent;
3617 ········}
3618
3619
3620 ········/// <summary>
3621 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3622 ········/// </summary>
3623 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3624 ········{
3625 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3626 ············if (rows.Count == 0)
3627 ················return queryResult;
3628
3629 ············IList callbackObjects = new ArrayList();
3630 ············Class cl = GetClass(t);
3631 ············if (t.IsGenericTypeDefinition)
3632 ············{
3633 ················if (cl.TypeNameColumn == null)
3634 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3635 ················IEnumerator en = rows.GetEnumerator();
3636 ················en.MoveNext();··// Move to the first element
3637 ················DataRow testrow = (DataRow)en.Current;
3638 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3639 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3640 ············}
3641
3642 ············foreach(DataRow row in rows)
3643 ············{
3644 ················Type concreteType = t;
3645 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3646 ················{
3647 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3648 ····················{
3649 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3650 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3651 ····················}
3652 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3653 ····················concreteType = Type.GetType(typeStr);
3654 ····················if (concreteType == null)
3655 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3656 ················}
3657 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3658 ················IPersistenceCapable pc = cache.GetObject(id);················
3659 ················if(pc == null)
3660 ················{
3661 ····················pc = CreateObject( concreteType );
3662 ····················pc.NDOObjectId = id;
3663 ····················pc.NDOStateManager = sm;
3664 ····················// If the object shouldn't be hollow, this will be overwritten later.
3665 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3666 ················}
3667 ················// If we have found a non hollow object, the time stamp remains the old one.
3668 ················// In every other case we use the new time stamp.
3669 ················// Note, that there could be a hollow object in the cache.
3670 ················// We need the time stamp in hollow objects in order to correctly
3671 ················// delete objects using fake rows.
3672 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3673 ················{
3674 ····················ReadTimeStamp(cl, pc, row);
3675 ················}
3676 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3677 ················{
3678 ····················Row2Object(cl, pc, row);
3679 ····················if ((pc as IPersistenceNotifiable) != null)
3680 ························callbackObjects.Add(pc);
3681 ················}
3682
3683 ················cache.UpdateCache(pc);
3684 ················queryResult.Add(pc);
3685 ············}
3686 ············// Make shure this is the last statement before returning
3687 ············// to the caller, so the user can recursively use persistent
3688 ············// objects
3689 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3690 ················ipn.OnLoaded();
3691
3692 ············return queryResult;
3693 ········}
3694
3695 #endregion
3696
3697 #region Cache Management
3698 ········/// <summary>
3699 ········/// Remove all unused entries from the cache.
3700 ········/// </summary>
3701 ········public void CleanupCache()
3702 ········{
3703 ············GC.Collect(GC.MaxGeneration);
3704 ············GC.WaitForPendingFinalizers();
3705 ············cache.Cleanup();
3706 ········}
3707
3708 ········/// <summary>
3709 ········/// Remove all unlocked objects from the cache. Use with care!
3710 ········/// </summary>
3711 ········public void UnloadCache()
3712 ········{
3713 ············cache.Unload();
3714 ········}
3715 #endregion
3716
3717 ········/// <summary>
3718 ········/// Default encryption key for NDO
3719 ········/// </summary>
3720 ········/// <remarks>
3721 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3722 ········/// </remarks>
3723 ········public virtual byte[] EncryptionKey
3724 ········{
3725 ············get
3726 ············{
3727 ················if (this.encryptionKey == null)
3728 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3729 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3730 ················return this.encryptionKey;
3731 ············}
3732 ············set { this.encryptionKey = value; }
3733 ········}
3734
3735 ········/// <summary>
3736 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3737 ········/// </summary>
3738 ········public virtual bool HollowMode
3739 ········{
3740 ············get { return hollowMode; }
3741 ············set { hollowMode = value; }
3742 ········}
3743
3744 ········internal TypeManager TypeManager
3745 ········{
3746 ············get { return typeManager; }
3747 ········}
3748
3749 ········/// <summary>
3750 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3751 ········/// </summary>
3752 ········/// <remarks>
3753 ········/// Set this value before you start any transactions.
3754 ········/// </remarks>
3755 ········public TransactionMode TransactionMode
3756 ········{
3757 ············get { return TransactionScope.TransactionMode; }
3758 ············set { TransactionScope.TransactionMode = value; }
3759 ········}
3760
3761 ········/// <summary>
3762 ········/// Sets or gets the Isolation Level.
3763 ········/// </summary>
3764 ········/// <remarks>
3765 ········/// Set this value before you start any transactions.
3766 ········/// </remarks>
3767 ········public IsolationLevel IsolationLevel
3768 ········{
3769 ············get { return TransactionScope.IsolationLevel; }
3770 ············set { TransactionScope.IsolationLevel = value; }
3771 ········}
3772
3773 ········internal class MappingTableEntry
3774 ········{
3775 ············private IPersistenceCapable parentObject;
3776 ············private IPersistenceCapable relatedObject;
3777 ············private Relation relation;
3778 ············private bool deleteEntry;
3779 ············
3780 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3781 ············{
3782 ············}
3783 ············
3784 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3785 ············{
3786 ················parentObject = pc;
3787 ················relatedObject = relObj;
3788 ················relation = r;
3789 ················this.deleteEntry = deleteEntry;
3790 ············}
3791
3792 ············public bool DeleteEntry
3793 ············{
3794 ················get
3795 ················{
3796 ····················return deleteEntry;
3797 ················}
3798 ············}
3799
3800 ············public IPersistenceCapable ParentObject
3801 ············{
3802 ················get
3803 ················{
3804 ····················return parentObject;
3805 ················}
3806 ············}
3807
3808 ············public IPersistenceCapable RelatedObject
3809 ············{
3810 ················get
3811 ················{
3812 ····················return relatedObject;
3813 ················}
3814 ············}
3815
3816 ············public Relation Relation
3817 ············{
3818 ················get
3819 ················{
3820 ····················return relation;
3821 ················}
3822 ············}
3823 ········}
3824
3825 ········/// <summary>
3826 ········/// Get a DataRow representing the given object.
3827 ········/// </summary>
3828 ········/// <param name="o"></param>
3829 ········/// <returns></returns>
3830 ········public DataRow GetClonedDataRow( object o )
3831 ········{
3832 ············IPersistenceCapable pc = CheckPc( o );
3833
3834 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3835 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3836
3837 ············DataRow row = cache.GetDataRow( pc );
3838 ············DataTable newTable = row.Table.Clone();
3839 ············newTable.ImportRow( row );
3840 ············row = newTable.Rows[0];
3841
3842 ············Class cls = mappings.FindClass(o.GetType());
3843 ············WriteObject( pc, row, cls.ColumnNames );
3844 ············WriteForeignKeysToRow( pc, row );
3845
3846 ············return row;
3847 ········}
3848
3849 ········/// <summary>
3850 ········/// Gets an object, which shows all changes applied to the given object.
3851 ········/// This function can be used to build an audit system.
3852 ········/// </summary>
3853 ········/// <param name="o"></param>
3854 ········/// <returns>An ExpandoObject</returns>
3855 ········public ChangeLog GetChangeSet( object o )
3856 ········{
3857 ············var changeLog = new ChangeLog(this);
3858 ············changeLog.Initialize( o );
3859 ············return changeLog;
3860 ········}
3861
3862 ········/// <summary>
3863 ········/// Outputs a revision number representing the assembly version.
3864 ········/// </summary>
3865 ········/// <remarks>This can be used for debugging purposes</remarks>
3866 ········public int Revision
3867 ········{
3868 ············get
3869 ············{
3870 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3871 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3872 ················return int.Parse( vstring );
3873 ············}
3874 ········}
3875 ····}
3876 }
3877
New Commit (e187d8e)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using System.Linq;
33 using System.Xml.Linq;
34
35 using NDO.Mapping;
36 using NDOInterfaces;
37 using NDO.ShortId;
38 using System.Globalization;
39 using NDO.Linq;
40 using NDO.Query;
41 using NDO.ChangeLogging;
42 using Microsoft.Extensions.DependencyInjection;
43 using Microsoft.Extensions.Logging;
44
45 namespace NDO
46 {
47 ····/// <summary>
48 ····/// Delegate type of an handler, which can be registered by the CollisionEvent of the PersistenceManager.
49 ····/// <see cref="NDO.PersistenceManager.CollisionEvent"/>
50 ····/// </summary>
51 ····public delegate void CollisionHandler(object o);
52 ····/// <summary>
53 ····/// Delegate type of an handler, which can be registered by the IdGenerationEvent event of the PersistenceManager.
54 ····/// <see cref="NDO.PersistenceManagerBase.IdGenerationEvent"/>
55 ····/// </summary>
56 ····public delegate void IdGenerationHandler(Type t, ObjectId oid);
57 ····/// <summary>
58 ····/// Delegate type of an handler, which can be registered by the OnSaving event of the PersistenceManager.
59 ····/// </summary>
60 ····public delegate void OnSavingHandler(ICollection l);
61 ····/// <summary>
62 ····/// Delegate type for the OnSavedEvent.
63 ····/// </summary>
64 ····/// <param name="auditSet"></param>
65 ····public delegate void OnSavedHandler(AuditSet auditSet);
66
67 ····/// <summary>
68 ····/// Delegate type of an handler, which can be registered by the ObjectNotPresentEvent of the PersistenceManager. The event will be called, if LoadData doesn't find an object with the given oid.
69 ····/// </summary>
70 ····/// <param name="pc"></param>
71 ····/// <returns>A boolean value which determines, if the handler could solve the situation.</returns>
72 ····/// <remarks>If the handler returns false, NDO will throw an exception.</remarks>
73 ····public delegate bool ObjectNotPresentHandler( IPersistenceCapable pc );
74
75 ····/// <summary>
76 ····/// Standard implementation of the IPersistenceManager interface. Provides transaction like manipulation of data sets.
77 ····/// This is the main class you'll work with in your application code. For more information see the topic "Persistence Manager" in the NDO Documentation.
78 ····/// </summary>
79 ····public class PersistenceManager : PersistenceManagerBase, IPersistenceManager
80 ····{········
81 ········private bool hollowMode = false;
82 ········private Dictionary<Relation, IMappingTableHandler> mappingHandler = new Dictionary<Relation,IMappingTableHandler>(); // currently used handlers
83
84 ········private Hashtable currentRelations = new Hashtable(); // Contains names of current bidirectional relations
85 ········private ObjectLock removeLock = new ObjectLock();
86 ········private ObjectLock addLock = new ObjectLock();
87 ········private ArrayList createdDirectObjects = new ArrayList(); // List of newly created objects that need to be stored twice to update foreign keys.
88 ········// List of created objects that use mapping table and need to be stored in mapping table
89 ········// after they have been stored to the database to update foreign keys.
90 ········private ArrayList createdMappingTableObjects = new ArrayList();··
91 ········private TypeManager typeManager;
92 ········internal bool DeferredMode { get; private set; }
93 ········private INDOTransactionScope transactionScope;
94 ········internal INDOTransactionScope TransactionScope => transactionScope ?? (transactionScope = ServiceProvider.GetRequiredService<INDOTransactionScope>());········
95
96 ········private OpenConnectionListener openConnectionListener;
97
98 ········/// <summary>
99 ········/// Register a listener to this event if you work in concurrent scenarios and you use TimeStamps.
100 ········/// If a collision occurs, this event gets fired and gives the opportunity to handle the situation.
101 ········/// </summary>
102 ········public event CollisionHandler CollisionEvent;
103
104 ········/// <summary>
105 ········/// Register a listener to this event to handle situations where LoadData doesn't find an object.
106 ········/// The listener can determine, whether an exception should be thrown, if the situation occurs.
107 ········/// </summary>
108 ········public event ObjectNotPresentHandler ObjectNotPresentEvent;
109
110 ········/// <summary>
111 ········/// Register a listener to this event, if you want to be notified about the end
112 ········/// of a transaction. The listener gets a ICollection of all objects, which have been changed
113 ········/// during the transaction and are to be saved or deleted.
114 ········/// </summary>
115 ········public event OnSavingHandler OnSavingEvent;
116 ········/// <summary>
117 ········/// This event is fired at the very end of the Save() method. It provides lists of the added, changed, and deleted objects.
118 ········/// </summary>
119 ········public event OnSavedHandler OnSavedEvent;
120 ········
121 ········private const string hollowMarker = "Hollow";
122 ········private byte[] encryptionKey;
123 ········private List<RelationChangeRecord> relationChanges = new List<RelationChangeRecord>();
124 ········private bool isClosing = false;
125
126 ········/// <summary>
127 ········/// Gets a list of structures which represent relation changes, i.e. additions and removals
128 ········/// </summary>
129 ········protected internal List<RelationChangeRecord> RelationChanges
130 ········{
131 ············get { return this.relationChanges; }
132 ········}
133
134 ········/// <summary>
135 ········/// Initializes a new PersistenceManager instance.
136 ········/// </summary>
137 ········/// <param name="mappingFileName"></param>
138 ········protected override void Init(string mappingFileName)
139 ········{
140 ············try
141 ············{
142 ················base.Init(mappingFileName);
143 ············}
144 ············catch (Exception ex)
145 ············{
146 ················if (ex is NDOException)
147 ····················throw;
148 ················throw new NDOException(30, "Persistence manager initialization error: " + ex.ToString());
149 ············}
150
151 ········}
152
153 ········/// <summary>
154 ········/// Initializes the persistence manager
155 ········/// </summary>
156 ········/// <remarks>
157 ········/// Note: This is the method, which will be called from all different ways to instantiate a PersistenceManagerBase.
158 ········/// </remarks>
159 ········/// <param name="mapping"></param>
160 ········internal override void Init( Mappings mapping )
161 ········{
162 ············base.Init( mapping );
163
164 ············ServiceProvider.GetRequiredService<IPersistenceManagerAccessor>().PersistenceManager = this;
165
166 ············string dir = Path.GetDirectoryName( mapping.FileName );
167
168 ············string typesFile = Path.Combine( dir, "NDOTypes.xml" );
169 ············typeManager = new TypeManager( typesFile, this.mappings );
170
171 ············sm = new StateManager( this );
172
173 ············InitClasses();
174 ········}
175
176
177 ········/// <summary>
178 ········/// Standard Constructor.
179 ········/// </summary>
180 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
181 ········/// <remarks>
182 ········/// Searches for a mapping file in the application directory.
183 ········/// The constructor tries to find a file with the same name as
184 ········/// the assembly, but with the extension .ndo.xml. If the file is not found the constructor tries to find a
185 ········/// file called AssemblyName.ndo.mapping in the application directory.
186 ········/// </remarks>
187 ········public PersistenceManager( IServiceProvider scopedServiceProvider = null ) : base( scopedServiceProvider )
188 ········{
189 ········}
190
191 ········/// <summary>
192 ········/// Loads the mapping file from the specified location. This allows to use
193 ········/// different mapping files with different classes mapped in it.
194 ········/// </summary>
195 ········/// <param name="mappingFile">Path to the mapping file.</param>
196 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
197 ········/// <remarks>Only the Professional and Enterprise
198 ········/// Editions can handle more than one mapping file.</remarks>
199 ········public PersistenceManager(string mappingFile, IServiceProvider scopedServiceProvider = null) : base (mappingFile, scopedServiceProvider)
200 ········{
201 ········}
202
203 ········/// <summary>
204 ········/// Constructs a PersistenceManager and reuses a cached NDOMapping.
205 ········/// </summary>
206 ········/// <param name="mapping">The cached mapping object</param>
207 ········/// <param name="scopedServiceProvider">An IServiceProvider instance, which represents a scope (e.g. a request in an AspNet application)</param>
208 ········public PersistenceManager(NDOMapping mapping, IServiceProvider scopedServiceProvider = null) : base (mapping, scopedServiceProvider)
209 ········{
210 ········}
211
212 ········#region Object Container Stuff
213 ········/// <summary>
214 ········/// Gets a container of all loaded objects and tries to load all child objects,
215 ········/// which are reachable through composite relations.
216 ········/// </summary>
217 ········/// <returns>An ObjectContainer object.</returns>
218 ········/// <remarks>
219 ········/// It is not recommended, to transfer objects with a state other than Hollow,
220 ········/// Persistent, or Transient.
221 ········/// The transfer format is binary.
222 ········/// </remarks>
223 ········public ObjectContainer GetObjectContainer()
224 ········{
225 ············IList l = this.cache.AllObjects;
226 ············foreach(IPersistenceCapable pc in l)
227 ············{
228 ················if (pc.NDOObjectState == NDOObjectState.PersistentDirty)
229 ················{
230 ····················if (Logger != null)
231 ························Logger.LogWarning( "Call to GetObjectContainer returns changed objects." );
232 ················}
233 ············}
234
235 ············ObjectContainer oc = new ObjectContainer();
236 ············oc.AddList(l);
237 ············return oc;
238 ········}
239
240 ········/// <summary>
241 ········/// Returns a container of all objects provided in the objects list and searches for
242 ········/// child objects according to the serFlags.
243 ········/// </summary>
244 ········/// <param name="objects">The list of the root objects to add to the container.</param>
245 ········/// <returns>An ObjectContainer object.</returns>
246 ········/// <remarks>
247 ········/// It is not recommended, to transfer objects with a state other than Hollow,
248 ········/// Persistent, or Transient.
249 ········/// </remarks>
250 ········public ObjectContainer GetObjectContainer(IList objects)
251 ········{
252 ············foreach(object o in objects)
253 ············{
254 ················CheckPc(o);
255 ················IPersistenceCapable pc = o as IPersistenceCapable;
256 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
257 ····················LoadData(pc);
258 ············}
259 ············ObjectContainer oc = new ObjectContainer();
260 ············oc.AddList(objects);
261 ············return oc;
262 ········}
263
264
265 ········/// <summary>
266 ········/// Returns a container containing the provided object
267 ········/// and tries to load all child objects
268 ········/// reachable through composite relations.
269 ········/// </summary>
270 ········/// <param name="obj">The object to be added to the container.</param>
271 ········/// <returns>An ObjectContainer object.</returns>
272 ········/// <remarks>
273 ········/// It is not recommended, to transfer objects with a state other than Hollow,
274 ········/// Persistent, or Transient.
275 ········/// The transfer format is binary.
276 ········/// </remarks>
277 ········public ObjectContainer GetObjectContainer(Object obj)
278 ········{
279 ············CheckPc(obj);
280 ············if (((IPersistenceCapable)obj).NDOObjectState == NDOObjectState.Hollow)
281 ················LoadData(obj);
282 ············ObjectContainer oc = new ObjectContainer();
283 ············oc.AddObject(obj);
284 ············return oc;
285 ········}
286
287 ········/// <summary>
288 ········/// Merges an object container to the active objects in the pm. All changes and the state
289 ········/// of the objects will be taken over by the pm.
290 ········/// </summary>
291 ········/// <remarks>
292 ········/// The parameter can be either an ObjectContainer or a ChangeSetContainer.
293 ········/// The flag MarkAsTransient can be used to perform a kind
294 ········/// of object based replication using the ObjectContainer class.
295 ········/// Objects, which are persistent at one machine, can be transfered
296 ········/// to a second machine and treated by the receiving PersistenceManager like a newly created
297 ········/// object. The receiving PersistenceManager will use MakePersistent to store the whole
298 ········/// transient object tree.
299 ········/// There is one difference to freshly created objects: If an object id exists, it will be
300 ········/// serialized. If the NDOOidType-Attribute is valid for the given class, the transfered
301 ········/// oids will be reused by the receiving PersistenceManager.
302 ········/// </remarks>
303 ········/// <param name="ocb">The object container to be merged.</param>
304 ········public void MergeObjectContainer(ObjectContainerBase ocb)
305 ········{
306 ············ChangeSetContainer csc = ocb as ChangeSetContainer;
307 ············if (csc != null)
308 ············{
309 ················MergeChangeSet(csc);
310 ················return;
311 ············}
312 ············ObjectContainer oc = ocb as ObjectContainer;
313 ············if (oc != null)
314 ············{
315 ················InternalMergeObjectContainer(oc);
316 ················return;
317 ············}
318 ············throw new NDOException(42, "Wrong argument type: MergeObjectContainer expects either an ObjectContainer or a ChangeSetContainer object as parameter.");
319 ········}
320
321
322 ········void InternalMergeObjectContainer(ObjectContainer oc)
323 ········{
324 ············// TODO: Check, if other states are useful. Find use scenarios.
325 ············foreach(IPersistenceCapable pc in oc.RootObjects)
326 ············{
327 ················if (pc.NDOObjectState == NDOObjectState.Transient)
328 ····················MakePersistent(pc);
329 ············}
330 ············foreach(IPersistenceCapable pc in oc.RootObjects)
331 ············{
332 ················new OnlineMergeIterator(this.sm, this.cache).Iterate(pc);
333 ············}
334 ········}
335
336 ········void MergeChangeSet(ChangeSetContainer cs)
337 ········{
338 ············foreach(IPersistenceCapable pc in cs.AddedObjects)
339 ············{
340 ················InternalMakePersistent(pc, false);
341 ············}
342 ············foreach(ObjectId oid in cs.DeletedObjects)
343 ············{
344 ················IPersistenceCapable pc2 = FindObject(oid);
345 ················Delete(pc2);
346 ············}
347 ············foreach(IPersistenceCapable pc in cs.ChangedObjects)
348 ············{
349 ················IPersistenceCapable pc2 = FindObject(pc.NDOObjectId);
350 ················Class pcClass = GetClass(pc);
351 ················// Make sure, the object is loaded.
352 ················if (pc2.NDOObjectState == NDOObjectState.Hollow)
353 ····················LoadData(pc2);
354 ················MarkDirty( pc2 );··// This locks the object and generates a LockEntry, which contains a row
355 ················var entry = cache.LockedObjects.FirstOrDefault( e => e.pc.NDOObjectId == pc.NDOObjectId );
356 ················DataRow row = entry.row;
357 ················pc.NDOWrite(row, pcClass.ColumnNames, 0);
358 ················pc2.NDORead(row, pcClass.ColumnNames, 0);
359 ············}
360 ············foreach(RelationChangeRecord rcr in cs.RelationChanges)
361 ············{
362 ················IPersistenceCapable parent = FindObject(rcr.Parent.NDOObjectId);
363 ················IPersistenceCapable child = FindObject(rcr.Child.NDOObjectId);
364 ················Class pcClass = GetClass(parent);
365 ················Relation r = pcClass.FindRelation(rcr.RelationName);
366 ················if (!parent.NDOLoadState.RelationLoadState[r.Ordinal])
367 ····················LoadRelation(parent, r, true);
368 ················if (rcr.IsAdded)
369 ················{
370 ····················InternalAddRelatedObject(parent, r, child, true);
371 ····················if (r.Multiplicity == RelationMultiplicity.Element)
372 ····················{
373 ························mappings.SetRelationField(parent, r.FieldName, child);
374 ····················}
375 ····················else
376 ····················{
377 ························IList l = mappings.GetRelationContainer(parent, r);
378 ························l.Add(child);
379 ····················}
380 ················}
381 ················else
382 ················{
383 ····················RemoveRelatedObject(parent, r.FieldName, child);
384 ····················if (r.Multiplicity == RelationMultiplicity.Element)
385 ····················{
386 ························mappings.SetRelationField(parent, r.FieldName, null);
387 ····················}
388 ····················else
389 ····················{
390 ························IList l = mappings.GetRelationContainer(parent, r);
391 ························try
392 ························{
393 ····························ObjectListManipulator.Remove(l, child);
394 ························}
395 ························catch
396 ························{
397 ····························throw new NDOException(50, "Error while merging a ChangeSetContainer: Child " + child.NDOObjectId.ToString() + " doesn't exist in relation " + parent.GetType().FullName + '.' + r.FieldName);
398 ························}
399 ····················}
400 ················}
401 ············}
402
403 ········}
404 ········#endregion
405
406 ········#region Implementation of IPersistenceManager
407
408 ········// Complete documentation can be found in IPersistenceManager
409
410
411 ········void WriteDependentForeignKeysToRow(IPersistenceCapable pc, Class cl, DataRow row)
412 ········{
413 ············if (!cl.Oid.IsDependent)
414 ················return;
415 ············WriteForeignKeysToRow(pc, row);
416 ········}
417
418 ········void InternalMakePersistent(IPersistenceCapable pc, bool checkRelations)
419 ········{
420 ············// Object is now under control of the state manager
421 ············pc.NDOStateManager = sm;
422
423 ············Type pcType = pc.GetType();
424 ············Class pcClass = GetClass(pc);
425
426 ············// Create new object
427 ············DataTable dt = GetTable(pcType);
428 ············DataRow row = dt.NewRow();·· // In case of autoincremented oid, the row has a temporary oid value
429
430 ············// In case of a Guid oid the value will be computed now.
431 ············foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
432 ············{
433 ················if (oidColumn.SystemType == typeof(Guid) && oidColumn.FieldName == null && oidColumn.RelationName ==null)
434 ················{
435 ····················if (dt.Columns[oidColumn.Name].DataType == typeof(string))
436 ························row[oidColumn.Name] = Guid.NewGuid().ToString();
437 ····················else
438 ························row[oidColumn.Name] = Guid.NewGuid();
439 ················}
440 ············}
441
442 ············WriteObject(pc, row, pcClass.ColumnNames, 0); // save current state in DS
443
444 ············// If the object is merged from an ObjectContainer, the id should be reused,
445 ············// if the id is client generated (not Autoincremented).
446 ············// In every other case, the oid is set to null, to force generating a new oid.
447 ············bool fireIdGeneration = (Object)pc.NDOObjectId == null;
448 ············if ((object)pc.NDOObjectId != null)
449 ············{
450 ················bool hasAutoincrement = false;
451 ················foreach (OidColumn oidColumn in pcClass.Oid.OidColumns)
452 ················{
453 ····················if (oidColumn.AutoIncremented)
454 ····················{
455 ························hasAutoincrement = true;
456 ························break;
457 ····················}
458 ················}
459 ················if (hasAutoincrement) // can't store existing id
460 ················{
461 ····················pc.NDOObjectId = null;
462 ····················fireIdGeneration = true;
463 ················}
464 ············}
465
466 ············// In case of a dependent class the oid has to be read from the fields according to the relations
467 ············WriteDependentForeignKeysToRow(pc, pcClass, row);
468
469 ············if ((object)pc.NDOObjectId == null)
470 ············{
471 ················pc.NDOObjectId = ObjectIdFactory.NewObjectId(pcType, pcClass, row, this.typeManager);
472 ············}
473
474 ············if (!pcClass.Oid.IsDependent) // Dependent keys can't be filled with user defined data
475 ············{
476 ················if (fireIdGeneration)
477 ····················FireIdGenerationEvent(pcType, pc.NDOObjectId);
478 ················// At this place the oid might have been
479 ················// - deserialized (MergeObjectContainer)
480 ················// - created using NewObjectId
481 ················// - defined by the IdGenerationEvent
482
483 ················// At this point we have a valid oid.
484 ················// If the object has a field mapped to the oid we have
485 ················// to write back the oid to the field
486 ················int i = 0;
487 ················new OidColumnIterator(pcClass).Iterate(delegate(OidColumn oidColumn, bool isLastElement)
488 ················{
489 ····················if (oidColumn.FieldName != null)
490 ····················{
491 ························FieldInfo fi = new BaseClassReflector(pcType).GetField(oidColumn.FieldName, BindingFlags.NonPublic | BindingFlags.Instance);
492 ························fi.SetValue(pc, pc.NDOObjectId.Id[i]);
493 ····················}
494 ····················i++;
495 ················});
496
497
498
499 ················// Now write back the data into the row
500 ················pc.NDOObjectId.Id.ToRow(pcClass, row);
501 ············}
502
503 ············
504 ············ReadLostForeignKeysFromRow(pcClass, pc, row);··// they contain all DBNull at the moment
505 ············dt.Rows.Add(row);
506
507 ············cache.Register(pc);
508
509 ············// new object that has never been written to the DS
510 ············pc.NDOObjectState = NDOObjectState.Created;
511 ············// Mark all Relations as loaded
512 ············SetRelationState(pc);
513
514 ············if (checkRelations)
515 ············{
516 ················// Handle related objects:
517 ················foreach(Relation r in pcClass.Relations)
518 ················{
519 ····················if (r.Multiplicity == RelationMultiplicity.Element)
520 ····················{
521 ························IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
522 ························if(child != null)
523 ························{
524 ····························AddRelatedObject(pc, r, child);
525 ························}
526 ····················}
527 ····················else
528 ····················{
529 ························IList list = mappings.GetRelationContainer(pc, r);
530 ························if(list != null)
531 ························{
532 ····························foreach(IPersistenceCapable relObj in list)
533 ····························{
534 ································if (relObj != null)
535 ····································AddRelatedObject(pc, r, relObj);
536 ····························}
537 ························}
538 ····················}
539 ················}
540 ············}
541
542 ············var relations··= CollectRelationStates(pc);
543 ············cache.Lock(pc, row, relations);
544 ········}
545
546
547 ········/// <summary>
548 ········/// Make an object persistent.
549 ········/// </summary>
550 ········/// <param name="o">the transient object that should be made persistent</param>
551 ········public void MakePersistent(object o)
552 ········{
553 ············IPersistenceCapable pc = CheckPc(o);
554
555 ············//Debug.WriteLine("MakePersistent: " + pc.GetType().Name);
556 ············//Debug.Indent();
557
558 ············if (pc.NDOObjectState != NDOObjectState.Transient)
559 ················throw new NDOException(54, "MakePersistent: Object is already persistent: " + pc.NDOObjectId.Dump());
560
561 ············InternalMakePersistent(pc, true);
562
563 ········}
564
565
566
567 ········//········/// <summary>
568 ········//········/// Checks, if an object has a valid id, which was created by the database
569 ········//········/// </summary>
570 ········//········/// <param name="pc"></param>
571 ········//········/// <returns></returns>
572 ········//········private bool HasValidId(IPersistenceCapable pc)
573 ········//········{
574 ········//············if (this.IdGenerationEvent != null)
575 ········//················return true;
576 ········//············return (pc.NDOObjectState != NDOObjectState.Created && pc.NDOObjectState != NDOObjectState.Transient);
577 ········//········}
578
579
580 ········private void CreateAddedObjectRow(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool makeRelObjPersistent)
581 ········{
582 ············// for a "1:n"-Relation w/o mapping table, we add the foreign key here.
583 ············if(r.HasSubclasses)
584 ············{
585 ················// we don't support 1:n with foreign fields in subclasses because we would have to
586 ················// search for objects in all subclasses! Instead use a mapping table.
587 ················// throw new NDOException(55, "1:n Relations with subclasses must use a mapping table: " + r.FieldName);
588 ················Debug.WriteLine("CreateAddedObjectRow: Polymorphic 1:n-relation " + r.Parent.FullName + "." + r.FieldName + " w/o mapping table");
589 ············}
590
591 ············if (!makeRelObjPersistent)
592 ················MarkDirty(relObj);
593 ············// Because we just marked the object as dirty, we know it's in the cache, so we don't supply the idColumn
594 ············DataRow relObjRow = this.cache.GetDataRow(relObj);
595
596 ············if (relObjRow == null)
597 ················throw new InternalException(537, "CreateAddedObjectRow: relObjRow == null");
598
599 ············pc.NDOObjectId.Id.ToForeignKey(r, relObjRow);
600
601 ············if (relObj.NDOLoadState.LostRowInfo == null)
602 ············{
603 ················ReadLostForeignKeysFromRow(GetClass(relObj), relObj, relObjRow);
604 ············}
605 ············else
606 ············{
607 ················relObj.NDOLoadState.ReplaceRowInfos(r, pc.NDOObjectId.Id);
608 ············}
609 ········}
610
611 ········private void PatchForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
612 ········{
613 ············switch(relObj.NDOObjectState)
614 ············{
615 ················case NDOObjectState.Persistent:
616 ····················MarkDirty(relObj);
617 ····················break;
618 ················case NDOObjectState.Hollow:
619 ····················LoadData(relObj);
620 ····················MarkDirty(relObj);
621 ····················break;
622 ············}
623
624 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
625 ············{
626 ················IPersistenceCapable newpc;
627 ················if((newpc = (IPersistenceCapable) mappings.GetRelationField(relObj, r.ForeignRelation.FieldName)) != null)
628 ················{
629 ····················if (newpc != pc)
630 ························throw new NDOException(56, "Object is already part of another relation: " + relObj.NDOObjectId.Dump());
631 ················}
632 ················else
633 ················{
634 ····················mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
635 ················}
636 ············}
637 ············else
638 ············{
639 ················if (!relObj.NDOGetLoadState(r.ForeignRelation.Ordinal))
640 ····················LoadRelation(relObj, r.ForeignRelation, true);
641 ················IList l = mappings.GetRelationContainer(relObj, r.ForeignRelation);
642 ················if(l == null)
643 ················{
644 ····················try
645 ····················{
646 ························l = mappings.CreateRelationContainer(relObj, r.ForeignRelation);
647 ····················}
648 ····················catch
649 ····················{
650 ························throw new NDOException(57, "Can't construct IList member " + relObj.GetType().FullName + "." + r.FieldName + ". Initialize the field in the default class constructor.");
651 ····················}
652 ····················mappings.SetRelationContainer(relObj, r.ForeignRelation, l);
653 ················}
654 ················// Hack: Es sollte erst gar nicht zu diesem Aufruf kommen.
655 ················// Zus�tzlicher Funktions-Parameter addObjectToList oder so.
656 ················if (!ObjectListManipulator.Contains(l, pc))
657 ····················l.Add(pc);
658 ············}
659 ············//AddRelatedObject(relObj, r.ForeignRelation, pc);
660 ········}
661
662
663 ········/// <summary>
664 ········/// Add a related object to the specified object.
665 ········/// </summary>
666 ········/// <param name="pc">the parent object</param>
667 ········/// <param name="fieldName">the field name of the relation</param>
668 ········/// <param name="relObj">the related object that should be added</param>
669 ········internal virtual void AddRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
670 ········{
671 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
672 ············Relation r = mappings.FindRelation(pc, fieldName);
673 ············AddRelatedObject(pc, r, relObj);
674 ········}
675
676 ········/// <summary>
677 ········/// Core functionality to add an object to a relation container or relation field.
678 ········/// </summary>
679 ········/// <param name="pc"></param>
680 ········/// <param name="r"></param>
681 ········/// <param name="relObj"></param>
682 ········/// <param name="isMerging"></param>
683 ········protected virtual void InternalAddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, bool isMerging)
684 ········{
685 ············
686 ············// avoid recursion
687 ············if (!addLock.GetLock(relObj))
688 ················return;
689
690 ············try
691 ············{
692 ················//TODO: We need a relation management, which is independent of
693 ················//the state management of an object. Currently the relation
694 ················//lists or elements are cached for restore, if an object is marked dirty.
695 ················//Thus we have to mark dirty our parent object in any case at the moment.
696 ················MarkDirty(pc);
697
698 ················//We should mark pc as dirty if we have a 1:1 w/o mapping table
699 ················//We should mark relObj as dirty if we have a 1:n w/o mapping table
700 ················//The latter happens in CreateAddedObjectRow
701
702 ················Class relClass = GetClass(relObj);
703
704 ················if (r.Multiplicity == RelationMultiplicity.Element
705 ····················&& r.HasSubclasses
706 ····················&& r.MappingTable == null················
707 ····················&& !this.HasOwnerCreatedIds
708 ····················&& GetClass(pc).Oid.HasAutoincrementedColumn
709 ····················&& !relClass.HasGuidOid)
710 ················{
711 ····················if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient ))
712 ························throw new NDOException(61, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The parent object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table.");
713 ····················if (r.Composition)
714 ························throw new NDOException(62, "Can't assign object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". Can't handle a polymorphic composite relation with cardinality 1 with autonumbered id's. Use a mapping table or client generated id's.");
715 ····················if (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
716 ························throw new NDOException(63, "Can't assign an object of type " + relObj + " to relation " + pc.GetType().FullName + "." + r.FieldName + ". The child object must be saved first to obtain the Id (for example with pm.Save(true)). As an alternative you can use client generated Id's or a mapping table." );
717 ················}
718
719 ················bool isDependent = relClass.Oid.IsDependent;
720
721 ················if (r.Multiplicity == RelationMultiplicity.Element && isDependent)
722 ····················throw new NDOException(28, "Relations to intermediate classes must have RelationMultiplicity.List.");
723
724 ················// Need to patch pc into the relation relObj->pc, because
725 ················// the oid is built on base of this information
726 ················if (isDependent)
727 ················{
728 ····················CheckDependentKeyPreconditions(pc, r, relObj, relClass);
729 ················}
730
731 ················if (r.Composition || isDependent)
732 ················{
733 ····················if (!isMerging || relObj.NDOObjectState == NDOObjectState.Transient)
734 ························MakePersistent(relObj);
735 ················}
736
737 ················if(r.MappingTable == null)
738 ················{
739 ····················if (r.Bidirectional)
740 ····················{
741 ························// This object hasn't been saved yet, so the key is wrong.
742 ························// Therefore, the child must be written twice to update the foreign key.
743 #if trace
744 ························System.Text.StringBuilder sb = new System.Text.StringBuilder();
745 ························if (r.Multiplicity == RelationMultiplicity.Element)
746 ····························sb.Append("1");
747 ························else
748 ····························sb.Append("n");
749 ························sb.Append(":");
750 ························if (r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
751 ····························sb.Append("1");
752 ························else
753 ····························sb.Append("n");
754 ························sb.Append ("OwnCreatedOther");
755 ························sb.Append(relObj.NDOObjectState.ToString());
756 ························sb.Append(' ');
757
758 ························sb.Append(types[0].ToString());
759 ························sb.Append(' ');
760 ························sb.Append(types[1].ToString());
761 ························Debug.WriteLine(sb.ToString());
762 #endif
763 ························//························if (r.Multiplicity == RelationMultiplicity.Element
764 ························//····························&& r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
765 ························//························{
766 ························// Element means:
767 ························// pc is keyholder
768 ························// -> relObj is saved first
769 ························// -> UpdateOrder(pc) > UpdateOrder(relObj)
770 ························// Both are Created - use type sort order
771 ························if (pc.NDOObjectState == NDOObjectState.Created && (relObj.NDOObjectState == NDOObjectState.Created || relObj.NDOObjectState == NDOObjectState.Transient)
772 ····························&& GetClass(pc).Oid.HasAutoincrementedColumn && GetClass(relObj).Oid.HasAutoincrementedColumn)
773 ························{
774 ····························if (mappings.GetUpdateOrder(pc.GetType())
775 ································< mappings.GetUpdateOrder(relObj.GetType()))
776 ································createdDirectObjects.Add(pc);
777 ····························else
778 ································createdDirectObjects.Add( relObj );
779 ························}
780 ····················}
781 ····················if (r.Multiplicity == RelationMultiplicity.List)
782 ····················{
783 ························CreateAddedObjectRow(pc, r, relObj, r.Composition);
784 ····················}
785 ················}
786 ················else
787 ················{
788 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, relObj, r));
789 ················}
790 ················if(r.Bidirectional)
791 ················{
792 ····················if (r.Multiplicity == RelationMultiplicity.List && mappings.GetRelationField(relObj, r.ForeignRelation.FieldName) == null)
793 ····················{
794 ························if ( r.ForeignRelation.Multiplicity == RelationMultiplicity.Element )
795 ····························mappings.SetRelationField(relObj, r.ForeignRelation.FieldName, pc);
796 ····················}
797 ····················else if ( !addLock.IsLocked( pc ) )
798 ····················{
799 ························PatchForeignRelation( pc, r, relObj );
800 ····················}
801 ················}
802
803 ················this.relationChanges.Add( new RelationChangeRecord( pc, relObj, r.FieldName, true ) );
804 ············}
805 ············finally
806 ············{
807 ················addLock.Unlock(relObj);
808 ················//Debug.Unindent();
809 ············}
810 ········}
811
812 ········/// <summary>
813 ········/// Returns an integer value which determines the rank of the given type in the update order list.
814 ········/// </summary>
815 ········/// <param name="t">The type to determine the update order.</param>
816 ········/// <returns>An integer value determining the rank of the given type in the update order list.</returns>
817 ········/// <remarks>
818 ········/// This method is used by NDO for diagnostic purposes. There is no value in using this method in user code.
819 ········/// </remarks>
820 ········public int GetUpdateRank(Type t)
821 ········{
822 ············return mappings.GetUpdateOrder(t);
823 ········}
824
825 ········/// <summary>
826 ········/// Add a related object to the specified object.
827 ········/// </summary>
828 ········/// <param name="pc">the parent object</param>
829 ········/// <param name="r">the relation mapping info</param>
830 ········/// <param name="relObj">the related object that should be added</param>
831 ········protected virtual void AddRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj)
832 ········{
833 ············//············string idstr;
834 ············//············if (relObj.NDOObjectId == null)
835 ············//················idstr = relObj.GetType().ToString();
836 ············//············else
837 ············//················idstr = relObj.NDOObjectId.Dump();
838 ············//Debug.WriteLine("AddRelatedObject " + pc.NDOObjectId.Dump() + " " + idstr);
839 ············//Debug.Indent();
840
841 ············Class relClass = GetClass(relObj);
842 ············bool isDependent = relClass.Oid.IsDependent;
843
844 ············// Do some checks to guarantee that the assignment is correct
845 ············if(r.Composition)
846 ············{
847 ················if(relObj.NDOObjectState != NDOObjectState.Transient)
848 ················{
849 ····················throw new NDOException(58, "Can only add transient objects in Composite relation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
850 ················}
851 ············}
852 ············else
853 ············{
854 ················if(relObj.NDOObjectState == NDOObjectState.Transient && !isDependent)
855 ················{
856 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + pc.GetType().FullName + "->" + r.ReferencedTypeName + ".");
857 ················}
858 ············}
859
860 ············if(!r.ReferencedType.IsAssignableFrom(relObj.GetType()))
861 ············{
862 ················throw new NDOException(60, "AddRelatedObject: Related object must be assignable to type: " + r.ReferencedTypeName + ". Assigned object was: " + relObj.NDOObjectId.Dump() + " Type = " + relObj.GetType());
863 ············}
864
865 ············InternalAddRelatedObject(pc, r, relObj, false);
866
867 ········}
868
869 ········private void CheckDependentKeyPreconditions(IPersistenceCapable pc, Relation r, IPersistenceCapable relObj, Class relClass)
870 ········{
871 ············// Need to patch pc into the relation relObj->pc, because
872 ············// the oid is built on base of this information
873 ············// The second relation has to be set before adding relObj
874 ············// to the relation list.
875 ············PatchForeignRelation(pc, r, relObj);
876 ············IPersistenceCapable parent;
877 ············foreach (Relation oidRelation in relClass.Oid.Relations)
878 ············{
879 ················parent = (IPersistenceCapable)mappings.GetRelationField(relObj, oidRelation.FieldName);
880 ················if (parent == null)
881 ····················throw new NDOException(41, "'" + relClass.FullName + "." + oidRelation.FieldName + "': One of the defining relations of a dependent class object is null - have a look at the documentation about how to initialize dependent class objects.");
882 ················if (parent.NDOObjectState == NDOObjectState.Transient)
883 ····················throw new NDOException(59, "Can only add persistent objects in Assoziation " + relClass.FullName + "." + oidRelation.FieldName + ". Make the object of type " + parent.GetType().FullName + " persistent.");
884
885 ············}
886 ········}
887
888
889 ········/// <summary>
890 ········/// Remove a related object from the specified object.
891 ········/// </summary>
892 ········/// <param name="pc">the parent object</param>
893 ········/// <param name="fieldName">Field name of the relation</param>
894 ········/// <param name="relObj">the related object that should be removed</param>
895 ········internal virtual void RemoveRelatedObject(IPersistenceCapable pc, string fieldName, IPersistenceCapable relObj)
896 ········{
897 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient);
898 ············Relation r = mappings.FindRelation(pc, fieldName);
899 ············InternalRemoveRelatedObject(pc, r, relObj, true);
900 ········}
901
902 ········/// <summary>
903 ········/// Registers a listener which will be notified, if a new connection is opened.
904 ········/// </summary>
905 ········/// <param name="listener">Delegate of a listener function</param>
906 ········/// <remarks>The listener is called the first time a certain connection is used. A call to Save() resets the connection list so that the listener is called again.</remarks>
907 ········public virtual void RegisterConnectionListener(OpenConnectionListener listener)
908 ········{
909 ············this.openConnectionListener = listener;
910 ········}
911
912 ········internal string OnNewConnection(NDO.Mapping.Connection conn)
913 ········{
914 ············if (openConnectionListener != null)
915 ················return openConnectionListener(conn);
916 ············return conn.Name;
917 ········}
918
919
920 ········/*
921 ········doCommit should be:
922 ········
923 ····················Query····Save····Save(true)
924 ········Optimistic····1········1········0
925 ········Pessimistic····0········1········0
926 ············
927 ········Deferred Mode············
928 ····················Query····Save····Save(true)
929 ········Optimistic····0········1········0
930 ········Pessimistic····0········1········0
931 ········ */
932
933 ········internal void CheckEndTransaction(bool doCommit)
934 ········{
935 ············if (doCommit)
936 ············{
937 ················TransactionScope.Complete();
938 ············}
939 ········}
940
941 ········internal void CheckTransaction(IPersistenceHandlerBase handler, Type t)
942 ········{
943 ············CheckTransaction(handler, this.GetClass(t).Connection);
944 ········}
945
946 ········/// <summary>
947 ········/// Each and every database operation has to be preceded by a call to this function.
948 ········/// </summary>
949 ········internal void CheckTransaction( IPersistenceHandlerBase handler, Connection ndoConn )
950 ········{
951 ············TransactionScope.CheckTransaction();
952 ············
953 ············if (handler.Connection == null)
954 ············{
955 ················handler.Connection = TransactionScope.GetConnection(ndoConn.ID, () =>
956 ················{
957 ····················IProvider p = ndoConn.Parent.GetProvider( ndoConn );
958 ····················string connStr = this.OnNewConnection( ndoConn );
959 ····················var connection = p.NewConnection( connStr );
960 ····················if (connection == null)
961 ························throw new NDOException( 119, $"Can't construct connection for {connStr}. The provider returns null." );
962 ····················LogIfVerbose( $"Creating a connection object for {ndoConn.DisplayName}" );
963 ····················return connection;
964 ················} );
965 ············}
966
967 ············if (TransactionMode != TransactionMode.None)
968 ············{
969 ················handler.Transaction = TransactionScope.GetTransaction( ndoConn.ID );
970 ············}
971
972 ············// During the tests, we work with a handler mock that always returns zero for the Connection property.
973 ············if (handler.Connection != null && handler.Connection.State != ConnectionState.Open)
974 ············{
975 ················handler.Connection.Open();
976 ················LogIfVerbose( $"Opening connection {ndoConn.DisplayName}" );
977 ············}
978 ········}
979
980 ········/// <summary>
981 ········/// Event Handler for the ConcurrencyError event of the IPersistenceHandler.
982 ········/// We try to tell the object which caused the concurrency exception, that a collicion occured.
983 ········/// This is possible if there is a listener for the CollisionEvent.
984 ········/// Else we throw an exception.
985 ········/// </summary>
986 ········/// <param name="ex">Concurrency Exception which was catched during update.</param>
987 ········private void OnConcurrencyError(System.Data.DBConcurrencyException ex)
988 ········{
989 ············DataRow row = ex.Row;
990 ············if (row == null || CollisionEvent == null || CollisionEvent.GetInvocationList().Length == 0)
991 ················throw(ex);
992 ············if (row.RowState == DataRowState.Detached)
993 ················return;
994 ············foreach (Cache.Entry e in cache.LockedObjects)
995 ············{
996 ················if (e.row == row)
997 ················{
998 ····················CollisionEvent(e.pc);
999 ····················return;
1000 ················}
1001 ············}
1002 ············throw ex;
1003 ········}
1004
1005
1006 ········private void ReadObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1007 ········{
1008 ············Class cl = GetClass(pc);
1009 ············string[] etypes = cl.EmbeddedTypes.ToArray();
1010 ············Dictionary<string,MemberInfo> persistentFields = null;
1011 ············if (etypes.Length > 0)
1012 ············{
1013 ················FieldMap fm = new FieldMap(cl);
1014 ················persistentFields = fm.PersistentFields;
1015 ············}
1016 ············foreach(string s in etypes)
1017 ············{
1018 ················try
1019 ················{
1020 ····················NDO.Mapping.Field f = cl.FindField(s);
1021 ····················if (f == null)
1022 ························continue;
1023 ····················object o = row[f.Column.Name];
1024 ····················string[] arr = s.Split('.');
1025 ····················// Suche Embedded Type-Feld mit Namen arr[0]
1026 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1027 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1028 ····················// Hole das Embedded Object
1029 ····················object parentOb = parentFi.GetValue(pc);
1030
1031 ····················if (parentOb == null)
1032 ························throw new Exception(String.Format("Can't read subfield {0} of type {1}, because the field {2} is null. Initialize the field {2} in your default constructor.", s, pc.GetType().FullName, arr[0]));
1033
1034 ····················// Suche darin das Feld mit Namen Arr[1]
1035
1036 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1037 ····················Type childType = childFi.FieldType;
1038
1039 ····················// Don't initialize value types, if DBNull is stored in the field.
1040 ····················// Exception: DateTime and Guid.
1041 ····················if (o == DBNull.Value && childType.IsValueType
1042 ························&& childType != typeof(Guid)
1043 ························&& childType != typeof(DateTime))
1044 ························continue;
1045
1046 ····················if (childType == typeof(DateTime))
1047 ····················{
1048 ························if (o == DBNull.Value)
1049 ····························o = DateTime.MinValue;
1050 ····················}
1051 ····················if (childType.IsClass)
1052 ····················{
1053 ························if (o == DBNull.Value)
1054 ····························o = null;
1055 ····················}
1056
1057 ····················if (childType == typeof (Guid))
1058 ····················{
1059 ························if (o == DBNull.Value)
1060 ····························o = Guid.Empty;
1061 ························if (o is string)
1062 ························{
1063 ····························childFi.SetValue(parentOb, new Guid((string)o));
1064 ························}
1065 ························else if (o is Guid)
1066 ························{
1067 ····························childFi.SetValue(parentOb, o);
1068 ························}
1069 ························else if (o is byte[])
1070 ························{
1071 ····························childFi.SetValue(parentOb, new Guid((byte[])o));
1072 ························}
1073 ························else
1074 ····························throw new Exception(string.Format("Can't convert Guid field to column type {0}.", o.GetType().FullName));
1075 ····················}
1076 ····················else if (childType.IsSubclassOf(typeof(System.Enum)))
1077 ····················{
1078 ························object childOb = childFi.GetValue(parentOb);
1079 ························FieldInfo valueFi = childType.GetField("value__");
1080 ························valueFi.SetValue(childOb, o);
1081 ························childFi.SetValue(parentOb, childOb);
1082 ····················}
1083 ····················else
1084 ····················{
1085 ························childFi.SetValue(parentOb, o);
1086 ····················}
1087 ················}
1088 ················catch (Exception ex)
1089 ················{
1090 ····················string msg = "Error while writing the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1091
1092 ····················throw new NDOException(68, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1093 ················}
1094
1095 ············}
1096 ············
1097 ············try
1098 ············{
1099 ················if (cl.HasEncryptedFields)
1100 ················{
1101 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1102 ····················{
1103 ························string name = field.Column.Name;
1104 ························string s = (string) row[name];
1105 ························string es = AesHelper.Decrypt( s, EncryptionKey );
1106 ························row[name] = es;
1107 ····················}
1108 ················}
1109 ················pc.NDORead(row, fieldNames, startIndex);
1110 ············}
1111 ············catch (Exception ex)
1112 ············{
1113 ················throw new NDOException(69, "Error while writing to a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1114 ····················+ ex.Message);
1115 ············}
1116 ········}
1117
1118 ········/// <summary>
1119 ········/// Executes a sql script to generate the database tables.
1120 ········/// The function will execute any sql statements in the script
1121 ········/// which are valid according to the
1122 ········/// rules of the underlying database. Result sets are ignored.
1123 ········/// </summary>
1124 ········/// <param name="scriptFile">The script file to execute.</param>
1125 ········/// <param name="conn">A connection object, containing the connection
1126 ········/// string to the database, which should be altered.</param>
1127 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1128 ········/// <remarks>
1129 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1130 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1131 ········/// Their message property will appear in the result array.
1132 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1133 ········/// </remarks>
1134 ········public string[] BuildDatabase( string scriptFile, Connection conn )
1135 ········{
1136 ············return BuildDatabase( scriptFile, conn, Encoding.UTF8 );
1137 ········}
1138
1139 ········/// <summary>
1140 ········/// Executes a sql script to generate the database tables.
1141 ········/// The function will execute any sql statements in the script
1142 ········/// which are valid according to the
1143 ········/// rules of the underlying database. Result sets are ignored.
1144 ········/// </summary>
1145 ········/// <param name="scriptFile">The script file to execute.</param>
1146 ········/// <param name="conn">A connection object, containing the connection
1147 ········/// string to the database, which should be altered.</param>
1148 ········/// <param name="encoding">The encoding of the script file. Default is UTF8.</param>
1149 ········/// <returns>A string array with error messages or the string "OK" for each of the statements in the file.</returns>
1150 ········/// <remarks>
1151 ········/// If the Connection object is invalid or null, a NDOException with ErrorNumber 44 will be thrown.
1152 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1153 ········/// Their message property will appear in the result array.
1154 ········/// If the script doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1155 ········/// </remarks>
1156 ········public string[] BuildDatabase(string scriptFile, Connection conn, Encoding encoding)
1157 ········{
1158 ············StreamReader sr = new StreamReader(scriptFile, encoding);
1159 ············string s = sr.ReadToEnd();
1160 ············sr.Close();
1161 ············string[] arr = s.Split(';');
1162 ············string last = arr[arr.Length - 1];
1163 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1164 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1165 ············using (var handler = GetSqlPassThroughHandler())
1166 ············{
1167 ················int i = 0;
1168 ················string ok = "OK";
1169 ················foreach (string statement in arr)
1170 ················{
1171 ····················if (statement != null && statement.Trim() != string.Empty)
1172 ····················{
1173 ························try
1174 ························{
1175 ····························handler.Execute(statement);
1176 ····························result[i] = ok;
1177 ························}
1178 ························catch (Exception ex)
1179 ························{
1180 ····························result[i] = ex.Message;
1181 ························}
1182 ····················}
1183 ····················i++;
1184 ················}
1185 ················CheckEndTransaction(true);
1186 ············}
1187 ············return result;
1188 ········}
1189
1190 ········/// <summary>
1191 ········/// Executes a sql script to generate the database tables.
1192 ········/// The function will execute any sql statements in the script
1193 ········/// which are valid according to the
1194 ········/// rules of the underlying database. Result sets are ignored.
1195 ········/// </summary>
1196 ········/// <param name="scriptFile">The script file to execute.</param>
1197 ········/// <returns></returns>
1198 ········/// <remarks>
1199 ········/// This function takes the first Connection object in the Connections list
1200 ········/// of the Mapping file und executes the script using that connection.
1201 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1202 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1203 ········/// Their message property will appear in the result array.
1204 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1205 ········/// </remarks>
1206 ········public string[] BuildDatabase(string scriptFile)
1207 ········{
1208 ············if (!File.Exists(scriptFile))
1209 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1210 ············if (!this.mappings.Connections.Any())
1211 ················throw new NDOException(48, "Mapping file doesn't define a connection.");
1212 ············Connection conn = new Connection( this.mappings );
1213 ············Connection originalConnection = (Connection)this.mappings.Connections.First();
1214 ············conn.Name = OnNewConnection( originalConnection );
1215 ············conn.Type = originalConnection.Type;
1216 ············//Connection conn = (Connection) this.mappings.Connections[0];
1217 ············return BuildDatabase(scriptFile, conn);
1218 ········}
1219
1220 ········/// <summary>
1221 ········/// Executes a sql script to generate the database tables.
1222 ········/// The function will execute any sql statements in the script
1223 ········/// which are valid according to the
1224 ········/// rules of the underlying database. Result sets are ignored.
1225 ········/// </summary>
1226 ········/// <returns>
1227 ········/// A string array, containing the error messages produced by the statements
1228 ········/// contained in the script.
1229 ········/// </returns>
1230 ········/// <remarks>
1231 ········/// The sql script is assumed to be the executable name of the entry assembly with the
1232 ········/// extension .ndo.sql. Use BuildDatabase(string) to provide a path to a script.
1233 ········/// If the executable name can't be determined a NDOException with ErrorNumber 49 will be thrown.
1234 ········/// This function takes the first Connection object in the Connections list
1235 ········/// of the Mapping file und executes the script using that connection.
1236 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1237 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1238 ········/// Their message property will appear in the result array.
1239 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1240 ········/// </remarks>
1241 ········public string[] BuildDatabase()
1242 ········{
1243 ············Assembly ass = Assembly.GetEntryAssembly();
1244 ············if (ass == null)
1245 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to BuildDatabase.");
1246 ············string file = Path.ChangeExtension(ass.Location, ".ndo.sql");
1247 ············return BuildDatabase(file);
1248 ········}
1249
1250 ········/// <summary>
1251 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1252 ········/// </summary>
1253 ········/// <param name="conn">Optional: The NDO-Connection to the database to be used.</param>
1254 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1255 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Connection conn = null )
1256 ········{
1257 ············if (!this.mappings.Connections.Any())
1258 ················throw new NDOException( 48, "Mapping file doesn't define a connection." );
1259 ············if (conn == null)
1260 ············{
1261 ················conn = new Connection( this.mappings );
1262 ················Connection originalConnection = (Connection) this.mappings.Connections.First();
1263 ················conn.Name = OnNewConnection( originalConnection );
1264 ················conn.Type = originalConnection.Type;
1265 ············}
1266
1267 ············return new SqlPassThroughHandler( this, conn );
1268 ········}
1269
1270 ········/// <summary>
1271 ········/// Gets a SqlPassThroughHandler object which can be used to execute raw Sql statements.
1272 ········/// </summary>
1273 ········/// <param name="predicate">A predicate defining which connection has to be used.</param>
1274 ········/// <returns>An ISqlPassThroughHandler implementation</returns>
1275 ········public ISqlPassThroughHandler GetSqlPassThroughHandler( Func<Connection, bool> predicate )
1276 ········{
1277 ············if (!this.mappings.Connections.Any())
1278 ················throw new NDOException( 48, "The Mapping file doesn't define a connection." );
1279 ············Connection conn = this.mappings.Connections.FirstOrDefault( predicate );
1280 ············if (conn == null)
1281 ················throw new NDOException( 48, "The Mapping file doesn't define a connection with this predicate." );
1282 ············return GetSqlPassThroughHandler( conn );
1283 ········}
1284
1285 ········/// <summary>
1286 ········/// Executes a xml script to generate the database tables.
1287 ········/// The function will generate and execute sql statements to perform
1288 ········/// the changes described by the xml.
1289 ········/// </summary>
1290 ········/// <returns></returns>
1291 ········/// <remarks>
1292 ········/// The script file is the first file found with the search string [AssemblyNameWithoutExtension].ndodiff.[SchemaVersion].xml.
1293 ········/// If several files match the search string biggest file name in the default sort order will be executed.
1294 ········/// This function takes the first Connection object in the Connections list
1295 ········/// of the Mapping file und executes the script using that connection.
1296 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1297 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1298 ········/// Their message property will appear in the result string array.
1299 ········/// If no script file exists, a NDOException with ErrorNumber 48 will be thrown.
1300 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1301 ········/// </remarks>
1302 ········public string[] PerformSchemaTransitions()
1303 ········{
1304 ············Assembly ass = Assembly.GetEntryAssembly();
1305 ············if (ass == null)
1306 ················throw new NDOException(49, "Can't determine the path of the entry assembly - please pass a sql script path as argument to PerformSchemaTransitions.");
1307 ············string mask = Path.GetFileNameWithoutExtension( ass.Location ) + ".ndodiff.*.xml";
1308 ············List<string> fileNames = Directory.GetFiles( Path.GetDirectoryName( ass.Location ), mask ).ToList();
1309 ············if (fileNames.Count == 0)
1310 ················return new String[] { String.Format( "No xml script file with a name like {0} found.", mask ) };
1311 ············if (fileNames.Count > 1)
1312 ················fileNames.Sort( ( fn1, fn2 ) => CompareFileName( fn1, fn2 ) );
1313 ············return PerformSchemaTransitions( fileNames[0] );
1314 ········}
1315
1316
1317 ········/// <summary>
1318 ········/// Executes a xml script to generate the database tables.
1319 ········/// The function will generate and execute sql statements to perform
1320 ········/// the changes described by the xml.
1321 ········/// </summary>
1322 ········/// <param name="scriptFile">The script file to execute.</param>
1323 ········/// <returns></returns>
1324 ········/// <remarks>
1325 ········/// This function takes the first Connection object in the Connections list
1326 ········/// of the Mapping file und executes the script using that connection.
1327 ········/// If no default connection exists, a NDOException with ErrorNumber 44 will be thrown.
1328 ········/// Any exceptions thrown while executing a statement in the script, will be caught.
1329 ········/// Their message property will appear in the result string array.
1330 ········/// If the script file doesn't exist, a NDOException with ErrorNumber 48 will be thrown.
1331 ········/// Note that an additional command is executed, which will update the NDOSchemaVersion entry.
1332 ········/// </remarks>
1333 ········public string[] PerformSchemaTransitions(string scriptFile)
1334 ········{
1335 ············if (!File.Exists(scriptFile))
1336 ················throw new NDOException(48, "Script file " + scriptFile + " doesn't exist.");
1337
1338 ············if (!this.mappings.Connections.Any())
1339 ················throw new NDOException(48, "Mapping file doesn't define any connection.");
1340 ············Connection conn = new Connection( mappings );
1341 ············Connection originalConnection = mappings.Connections.First();
1342 ············conn.Name = OnNewConnection( originalConnection );
1343 ············conn.Type = originalConnection.Type;
1344 ············return PerformSchemaTransitions(scriptFile, conn);
1345 ········}
1346
1347
1348 ········int CompareFileName( string fn1, string fn2)
1349 ········{
1350 ············Regex regex = new Regex( @"ndodiff\.(.+)\.xml" );
1351 ············Match match = regex.Match( fn1 );
1352 ············string v1 = match.Groups[1].Value;
1353 ············match = regex.Match( fn2 );
1354 ············string v2 = match.Groups[1].Value;
1355 ············return new Version( v2 ).CompareTo( new Version( v1 ) );
1356 ········}
1357
1358 ········Guid[] GetSchemaIds(Connection ndoConn, string schemaName, IProvider provider)
1359 ········{
1360 ············var connection = provider.NewConnection( ndoConn.Name );
1361 ············var resultList = new List<Guid>();
1362
1363 ············using (var handler = GetSqlPassThroughHandler())
1364 ············{
1365 ················string[] TableNames = provider.GetTableNames( connection );
1366 ················if (TableNames.Any( t => String.Compare( t, "NDOSchemaIds", true ) == 0 ))
1367 ················{
1368 ····················var schemaIds = provider.GetQualifiedTableName("NDOSchemaIds");
1369 ····················var sn = provider.GetQuotedName("SchemaName");
1370 ····················var id = provider.GetQuotedName("Id");
1371 ····················string sql = $"SELECT {id} from {schemaIds} WHERE {sn} ";
1372 ····················if (String.IsNullOrEmpty(schemaName))
1373 ························sql += "IS NULL;";
1374 ····················else
1375 ························sql += $"LIKE '{schemaName}'";
1376
1377 ····················using(IDataReader dr = handler.Execute(sql, true))
1378 ····················{
1379 ························while (dr.Read())
1380 ····························resultList.Add( dr.GetGuid( 0 ) );
1381 ····················}
1382 ················}
1383 ················else
1384 ················{
1385 ····················SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1386 ····················var gt = typeof(Guid);
1387 ····················var gtype = $"{gt.FullName},{ new AssemblyName( gt.Assembly.FullName ).Name }";
1388 ····················var st = typeof(String);
1389 ····················var stype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1390 ····················var dt = typeof(DateTime);
1391 ····················var dtype = $"{st.FullName},{ new AssemblyName( st.Assembly.FullName ).Name }";
1392 ····················string transition = $@"<NdoSchemaTransition>
1393 ····<CreateTable name=""NDOSchemaIds"">
1394 ······<CreateColumn name=""SchemaName"" type=""{stype}"" allowNull=""True"" />
1395 ······<CreateColumn name=""Id"" type=""{gtype}"" size=""36"" isPrimary=""True"" />
1396 ······<CreateColumn name=""InsertTime"" type=""{dtype}"" size=""36"" />
1397 ····</CreateTable>
1398 </NdoSchemaTransition>";
1399 ····················XElement transitionElement = XElement.Parse(transition);
1400
1401 ····················string sql = schemaTransitionGenerator.Generate( transitionElement );
1402 ····················handler.Execute(sql);
1403 ················}
1404 ················handler.CommitTransaction();
1405 ············}
1406
1407 ············return resultList.ToArray();
1408 ········}
1409
1410 ········/// <summary>
1411 ········/// Executes a xml script to generate the database tables.
1412 ········/// The function will generate and execute sql statements to perform
1413 ········/// the changes described by the xml.
1414 ········/// </summary>
1415 ········/// <param name="scriptFile">The xml script file.</param>
1416 ········/// <param name="ndoConn">The connection to be used to perform the schema changes.</param>
1417 ········/// <returns>A list of strings about the states of the different schema change commands.</returns>
1418 ········/// <remarks>Note that an additional command is executed, which will update the NDOSchemaVersion entry.</remarks>
1419 ········public string[] PerformSchemaTransitions(string scriptFile, Connection ndoConn)
1420 ········{
1421 ············string schemaName = null;
1422 ············// Gespeicherte Version ermitteln.
1423 ············XElement transitionElements = XElement.Load( scriptFile );
1424 ············if (transitionElements.Attribute( "schemaName" ) != null)
1425 ················schemaName = transitionElements.Attribute( "schemaName" ).Value;
1426
1427 ············IProvider provider = this.mappings.GetProvider( ndoConn );
1428 ············var installedIds = GetSchemaIds( ndoConn, schemaName, provider );
1429 ············var newIds = new List<Guid>();
1430 ············SchemaTransitionGenerator schemaTransitionGenerator = new SchemaTransitionGenerator( ProviderFactory, ndoConn.Type, this.mappings );
1431
1432 // dtLiteral contains the leading and trailing quotes
1433 var dtLiteral = provider. GetSqlLiteral( DateTime. Now ) ;
1434 ············var ndoSchemaIds = provider.GetQualifiedTableName("NDOSchemaIds");
1435 ············var schName = provider.GetQuotedName("SchemaName");
1436 ············var idCol = provider.GetQuotedName("Id");
1437 ············var insertTime = provider.GetQuotedName("InsertTime");
1438 ············var results = new List<string>();
1439
1440 // Each transitionElement gets it's own transaction.
1441 ············// The insert into NDOSchemaIds is part of the transaction.
1442 ············// If an error occurs, InternalPerformSchemaTransitions aborts the transaction.
1443 ············foreach (XElement transitionElement in transitionElements.Elements( "NdoSchemaTransition" ))
1444 ············{
1445 ················var id = transitionElement.Attribute("id")?.Value;
1446 ················if (id == null)
1447 ····················continue;
1448 ················var gid = new Guid(id);
1449 ················if (installedIds.Contains( gid ))
1450 ····················continue;
1451
1452 var sb = new StringBuilder( ) ;
1453 ················sb.Append( schemaTransitionGenerator.Generate( transitionElement ) );
1454 ················newIds.Add( gid );
 
1455
1456 sb. Append( $"INSERT INTO { ndoSchemaIds} ( { schName} , { idCol} , { insertTime} ) VALUES ( '{ schemaName} ', '{ gid} ', { dtLiteral} ) ;" ) ;
 
 
 
 
1457 ················
1458 results. AddRange( InternalPerformSchemaTransitions( ndoConn, sb. ToString( ) ) ) ;
 
 
 
 
 
 
1459 ············}
1460
1461 return results. ToArray( ) ;
 
 
 
 
 
 
 
1462 ········}
1463
1464 ········private string[] InternalPerformSchemaTransitions( Connection ndoConn, string sql )
1465 ········{
1466 ············string[] arr = sql.Split( ';' );
1467
1468 ············string last = arr[arr.Length - 1];
1469 ············bool lastInvalid = (last == null || last.Trim() == string.Empty);
1470 ············string[] result = new string[arr.Length - (lastInvalid ? 1 : 0)];
1471 ············int i = 0;
1472 ············string ok = "OK";
1473 ············using (var handler = GetSqlPassThroughHandler())
1474 ············{
1475 ················handler.BeginTransaction();
1476 ················var doCommit = true;
1477 ················foreach (string statement in arr)
1478 ················{
1479 ····················if (!String.IsNullOrWhiteSpace(statement))
1480 ····················{
1481 ························try
1482 ························{
1483 ····························handler.Execute( statement.Trim() );
1484 ····························result[i] = ok;
1485 ························}
1486 ························catch (Exception ex)
1487 ························{
1488 ····························result[i] = ex.Message;
1489 ····························doCommit = false;
1490 ························}
1491 ····················}
1492 ····················i++;
1493 ················}
1494 ················if (doCommit)
1495 ····················handler.CommitTransaction();
1496 ················else
1497 ····················AbortTransaction();
1498 ············}
1499
1500 ············return result;
1501 ········}
1502 ········
1503 ········/// <summary>
1504 ········/// Transfers Data from the object to the DataRow
1505 ········/// </summary>
1506 ········/// <param name="pc"></param>
1507 ········/// <param name="row"></param>
1508 ········/// <param name="fieldNames"></param>
1509 ········/// <param name="startIndex"></param>
1510 ········protected virtual void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames, int startIndex)
1511 ········{
1512 ············Class cl = GetClass( pc );
1513 ············try
1514 ············{
1515 ················pc.NDOWrite(row, fieldNames, startIndex);
1516 ················if (cl.HasEncryptedFields)
1517 ················{
1518 ····················foreach (var field in cl.Fields.Where( f => f.Encrypted ))
1519 ····················{
1520 ························string name = field.Column.Name;
1521 ························string s = (string) row[name];
1522 ························string es = AesHelper.Encrypt( s, EncryptionKey );
1523 ························row[name] = es;
1524 ····················}
1525 ················}
1526 ············}
1527 ············catch (Exception ex)
1528 ············{
1529 ················throw new NDOException(70, "Error while reading a field of an object of type " + pc.GetType().FullName + ". Check your mapping file.\n"
1530 ····················+ ex.Message);
1531 ············}
1532
1533 ············if (cl.TypeNameColumn != null)
1534 ············{
1535 ················Type t = pc.GetType();
1536 ················row[cl.TypeNameColumn.Name] = t.FullName + "," + t.Assembly.GetName().Name;
1537 ············}
1538
1539 ············var etypes = cl.EmbeddedTypes;
1540 ············foreach(string s in etypes)
1541 ············{
1542 ················try
1543 ················{
1544 ····················NDO.Mapping.Field f = cl.FindField(s);
1545 ····················if (f == null)
1546 ························continue;
1547 ····················string[] arr = s.Split('.');
1548 ····················// Suche Feld mit Namen arr[0] als object
1549 ····················BaseClassReflector bcr = new BaseClassReflector(pc.GetType());
1550 ····················FieldInfo parentFi = bcr.GetField(arr[0], BindingFlags.NonPublic | BindingFlags.Instance);
1551 ····················Object parentOb = parentFi.GetValue(pc);
1552 ····················if (parentOb == null)
1553 ························throw new Exception(String.Format("The field {0} is null. Initialize the field in your default constructor.", arr[0]));
1554 ····················// Suche darin das Feld mit Namen Arr[1]
1555 ····················FieldInfo childFi = parentFi.FieldType.GetField(arr[1], BindingFlags.NonPublic | BindingFlags.Instance);
1556 ····················object o = childFi.GetValue(parentOb);
1557 ····················if (o == null
1558 ························|| o is DateTime && (DateTime) o == DateTime.MinValue
1559 ························|| o is Guid && (Guid) o == Guid.Empty)
1560 ························o = DBNull.Value;
1561 ····················row[f.Column.Name] = o;
1562 ················}
1563 ················catch (Exception ex)
1564 ················{
1565 ····················string msg = "Error while reading the embedded object {0} of an object of type {1}. Check your mapping file.\n{2}";
1566
1567 ····················throw new NDOException(71, string.Format(msg, s, pc.GetType().FullName, ex.Message));
1568 ················}
1569 ············}
1570 ········}
1571
1572 ········/// <summary>
1573 ········/// Check, if the specific field is loaded. If not, LoadData will be called.
1574 ········/// </summary>
1575 ········/// <param name="o">The parent object.</param>
1576 ········/// <param name="fieldOrdinal">A number to identify the field.</param>
1577 ········public virtual void LoadField(object o, int fieldOrdinal)
1578 ········{
1579 ············IPersistenceCapable pc = CheckPc(o);
1580 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1581 ············{
1582 ················LoadState ls = pc.NDOLoadState;
1583 ················if (ls.FieldLoadState != null)
1584 ················{
1585 ····················if (ls.FieldLoadState[fieldOrdinal])
1586 ························return;
1587 ················}
1588 ················else
1589 ················{
1590 ····················ls.FieldLoadState = new BitArray( GetClass( pc ).Fields.Count() );
1591 ················}
1592 ················LoadData(o);
1593 ········}
1594 ········}
1595
1596 #pragma warning disable 419
1597 ········/// <summary>
1598 ········/// Load the data of a persistent object. This forces the transition of the object state from hollow to persistent.
1599 ········/// </summary>
1600 ········/// <param name="o">The hollow object.</param>
1601 ········/// <remarks>Note, that the relations won't be resolved with this function, with one Exception: 1:1 relations without mapping table will be resolved during LoadData. In all other cases, use <see cref="LoadRelation">LoadRelation</see>, to force resolving a relation.<seealso cref="NDOObjectState"/></remarks>
1602 #pragma warning restore 419
1603 ········public virtual void LoadData( object o )
1604 ········{
1605 ············IPersistenceCapable pc = CheckPc(o);
1606 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Can only load hollow objects");
1607 ············if (pc.NDOObjectState != NDOObjectState.Hollow)
1608 ················return;
1609 ············Class cl = GetClass(pc);
1610 ············IQuery q;
1611 ············q = CreateOidQuery(pc, cl);
1612 ············cache.UpdateCache(pc); // Make sure the object is in the cache
1613
1614 ············var objects = q.Execute();
1615 ············var count = objects.Count;
1616
1617 ············if (count > 1)
1618 ············{
1619 ················throw new NDOException( 72, "Load Data: " + count + " result objects with the same oid" );
1620 ············}
1621 ············else if (count == 0)
1622 ············{
1623 ················if (ObjectNotPresentEvent == null || !ObjectNotPresentEvent(pc))
1624 ················throw new NDOException( 72, "LoadData: Object " + pc.NDOObjectId.Dump() + " is not present in the database." );
1625 ············}
1626 ········}
1627
1628 ········/// <summary>
1629 ········/// Creates a new IQuery object for the given type
1630 ········/// </summary>
1631 ········/// <param name="t"></param>
1632 ········/// <param name="oql"></param>
1633 ········/// <param name="hollow"></param>
1634 ········/// <param name="queryLanguage"></param>
1635 ········/// <returns></returns>
1636 ········public IQuery NewQuery(Type t, string oql, bool hollow = false, QueryLanguage queryLanguage = QueryLanguage.NDOql)
1637 ········{
1638 ············Type template = typeof( NDOQuery<object> ).GetGenericTypeDefinition();
1639 ············Type qt = template.MakeGenericType( t );
1640 ············return (IQuery)Activator.CreateInstance( qt, this, oql, hollow, queryLanguage );
1641 ········}
1642
1643 ········private IQuery CreateOidQuery(IPersistenceCapable pc, Class cl)
1644 ········{
1645 ············ArrayList parameters = new ArrayList();
1646 ············string oql = "oid = {0}";
1647 ············IQuery q = NewQuery(pc.GetType(), oql, false);
1648 ············q.Parameters.Add( pc.NDOObjectId );
1649 ············q.AllowSubclasses = false;
1650 ············return q;
1651 ········}
1652
1653 ········/// <summary>
1654 ········/// Mark the object dirty. The current state is
1655 ········/// saved in a DataRow, which is stored in the DS. This is done, to allow easy rollback later. Also, the
1656 ········/// object is locked in the cache.
1657 ········/// </summary>
1658 ········/// <param name="pc"></param>
1659 ········internal virtual void MarkDirty(IPersistenceCapable pc)
1660 ········{
1661 ············if (pc.NDOObjectState != NDOObjectState.Persistent)
1662 ················return;
1663 ············SaveObjectState(pc);
1664 ············pc.NDOObjectState = NDOObjectState.PersistentDirty;
1665 ········}
1666
1667 ········/// <summary>
1668 ········/// Mark the object dirty, but make sure first, that the object is loaded.
1669 ········/// The current or loaded state is saved in a DataRow, which is stored in the DS.
1670 ········/// This is done, to allow easy rollback later. Also, the
1671 ········/// object is locked in the cache.
1672 ········/// </summary>
1673 ········/// <param name="pc"></param>
1674 ········internal void LoadAndMarkDirty(IPersistenceCapable pc)
1675 ········{
1676 ············Debug.Assert(pc.NDOObjectState != NDOObjectState.Transient, "Transient objects can't be marked as dirty.");
1677
1678 ············if(pc.NDOObjectState == NDOObjectState.Deleted)
1679 ············{
1680 ················throw new NDOException(73, "LoadAndMarkDirty: Access to deleted objects is not allowed.");
1681 ············}
1682
1683 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1684 ················LoadData(pc);
1685
1686 ············// state is either (Created), Persistent, PersistentDirty
1687 ············if(pc.NDOObjectState == NDOObjectState.Persistent)
1688 ············{
1689 ················MarkDirty(pc);
1690 ············}
1691 ········}
1692
1693
1694
1695 ········/// <summary>
1696 ········/// Save current object state in DS and lock the object in the cache.
1697 ········/// The saved state can be used later to retrieve the original object value if the
1698 ········/// current transaction is aborted. Also the state of all relations (not related objects) is stored.
1699 ········/// </summary>
1700 ········/// <param name="pc">The object that should be saved</param>
1701 ········/// <param name="isDeleting">Determines, if the object is about being deletet.</param>
1702 ········/// <remarks>
1703 ········/// In a data row there are the following things:
1704 ········/// Item································Responsible for writing
1705 ········/// State (own, inherited, embedded)····WriteObject
1706 ········/// TimeStamp····························NDOPersistenceHandler
1707 ········/// Oid····································WriteId
1708 ········/// Foreign Keys and their Type Codes····WriteForeignKeys
1709 ········/// </remarks>
1710 ········protected virtual void SaveObjectState(IPersistenceCapable pc, bool isDeleting = false)
1711 ········{
1712 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Persistent, "Object must be unmodified and persistent but is " + pc.NDOObjectState);
1713 ············
1714 ············DataTable table = GetTable(pc);
1715 ············DataRow row = table.NewRow();
1716 ············Class cl = GetClass(pc);
1717 ············WriteObject(pc, row, cl.ColumnNames, 0);
1718 ············WriteIdToRow(pc, row);
1719 ············if (!isDeleting)
1720 ················WriteLostForeignKeysToRow(cl, pc, row);
1721 ············table.Rows.Add(row);
1722 ············row.AcceptChanges();
1723 ············
1724 ············var relations = CollectRelationStates(pc);
1725 ············cache.Lock(pc, row, relations);
1726 ········}
1727
1728 ········private void SaveFakeRow(IPersistenceCapable pc)
1729 ········{
1730 ············Debug.Assert(pc.NDOObjectState == NDOObjectState.Hollow, "Object must be hollow but is " + pc.NDOObjectState);
1731 ············
1732 ············DataTable table = GetTable(pc);
1733 ············DataRow row = table.NewRow();
1734 ············Class pcClass = GetClass(pc);
1735 ············row.SetColumnError(GetFakeRowOidColumnName(pcClass), hollowMarker);
1736 ············Class cl = GetClass(pc);
1737 ············//WriteObject(pc, row, cl.FieldNames, 0);
1738 ············WriteIdToRow(pc, row);
1739 ············table.Rows.Add(row);
1740 ············row.AcceptChanges();
1741 ············
1742 ············cache.Lock(pc, row, null);
1743 ········}
1744
1745 ········/// <summary>
1746 ········/// This defines one column of the row, in which we use the
1747 ········/// ColumnError property to determine, if the row is a fake row.
1748 ········/// </summary>
1749 ········/// <param name="pcClass"></param>
1750 ········/// <returns></returns>
1751 ········private string GetFakeRowOidColumnName(Class pcClass)
1752 ········{
1753 ············// In case of several OidColumns the first column defined in the mapping
1754 ············// will be the one, holding the fake row info.
1755 ············return ((OidColumn)pcClass.Oid.OidColumns[0]).Name;
1756 ········}
1757
1758 ········private bool IsFakeRow(Class cl, DataRow row)
1759 ········{
1760 ············return (row.GetColumnError(GetFakeRowOidColumnName(cl)) == hollowMarker);
1761 ········}
1762
1763 ········/// <summary>
1764 ········/// Make a list of objects persistent.
1765 ········/// </summary>
1766 ········/// <param name="list">the list of IPersistenceCapable objects</param>
1767 ········public void MakePersistent(System.Collections.IList list)
1768 ········{
1769 ············foreach (IPersistenceCapable pc in list)
1770 ············{
1771 ················MakePersistent(pc);
1772 ············}
1773 ········}
1774
1775 ········/// <summary>
1776 ········/// Save state of related objects in the cache. Only the list itself is duplicated and stored. The related objects are
1777 ········/// not duplicated.
1778 ········/// </summary>
1779 ········/// <param name="pc">the parent object of all relations</param>
1780 ········/// <returns></returns>
1781 ········protected internal virtual List<KeyValuePair<Relation,object>> CollectRelationStates(IPersistenceCapable pc)
1782 ········{
1783 ············// Save state of relations
1784 ············Class c = GetClass(pc);
1785 ············List<KeyValuePair<Relation, object>> relations = new List<KeyValuePair<Relation, object>>( c.Relations.Count());
1786 ············foreach(Relation r in c.Relations)
1787 ············{
1788 ················if (r.Multiplicity == RelationMultiplicity.Element)
1789 ················{
1790 ····················relations.Add( new KeyValuePair<Relation, object>( r, mappings.GetRelationField( pc, r.FieldName ) ) );
1791 ················}
1792 ················else
1793 ················{
1794 ····················IList l = mappings.GetRelationContainer(pc, r);
1795 ····················if(l != null)
1796 ····················{
1797 ························l = (IList) ListCloner.CloneList(l);
1798 ····················}
1799 ····················relations.Add( new KeyValuePair<Relation, object>( r, l ) );
1800 ················}
1801 ············}
1802
1803 ············return relations;
1804 ········}
1805
1806
1807 ········/// <summary>
1808 ········/// Restore the saved relations.··Note that the objects are not restored as this is handled transparently
1809 ········/// by the normal persistence mechanism. Only the number and order of objects are restored, e.g. the state,
1810 ········/// the list had at the beginning of the transaction.
1811 ········/// </summary>
1812 ········/// <param name="pc"></param>
1813 ········/// <param name="relations"></param>
1814 ········private void RestoreRelatedObjects(IPersistenceCapable pc, List<KeyValuePair<Relation, object>> relations )
1815 ········{
1816 ············Class c = GetClass(pc);
1817
1818 ············foreach(var entry in relations)
1819 ············{
1820 ················var r = entry.Key;
1821 ················if (r.Multiplicity == RelationMultiplicity.Element)
1822 ················{
1823 ····················mappings.SetRelationField(pc, r.FieldName, entry.Value);
1824 ················}
1825 ················else
1826 ················{
1827 ····················if (pc.NDOGetLoadState(r.Ordinal))
1828 ····················{
1829 ························// Help GC by clearing lists
1830 ························IList l = mappings.GetRelationContainer(pc, r);
1831 ························if(l != null)
1832 ························{
1833 ····························l.Clear();
1834 ························}
1835 ························// Restore relation
1836 ························mappings.SetRelationContainer(pc, r, (IList)entry.Value);
1837 ····················}
1838 ················}
1839 ············}
1840 ········}
1841
1842
1843 ········/// <summary>
1844 ········/// Generates a query for related objects without mapping table.
1845 ········/// Note: this function can't be called in polymorphic scenarios,
1846 ········/// since they need a mapping table.
1847 ········/// </summary>
1848 ········/// <returns></returns>
1849 ········IList QueryRelatedObjects(IPersistenceCapable pc, Relation r, IList l, bool hollow)
1850 ········{
1851 ············// At this point of execution we know,
1852 ············// that the target type is not polymorphic and is not 1:1.
1853
1854 ············// We can't fetch these objects with an NDOql query
1855 ············// since this would require a relation in the opposite direction
1856
1857 ············IList relatedObjects;
1858 ············if (l != null)
1859 ················relatedObjects = l;
1860 ············else
1861 ················relatedObjects = mappings.CreateRelationContainer( pc, r );
1862
1863 ············Type t = r.ReferencedType;
1864 ············Class cl = GetClass( t );
1865 ············var provider = cl.Provider;
1866
1867 ············StringBuilder sb = new StringBuilder("SELECT * FROM ");
1868 ············var relClass = GetClass( r.ReferencedType );
1869 ············sb.Append( GetClass( r.ReferencedType ).GetQualifiedTableName() );
1870 ············sb.Append( " WHERE " );
1871 ············int i = 0;
1872 ············List<object> parameters = new List<object>();
1873 ············new ForeignKeyIterator( r ).Iterate( delegate ( ForeignKeyColumn fkColumn, bool isLastElement )
1874 ·············· {
1875 ·················· sb.Append( fkColumn.GetQualifiedName(relClass) );
1876 ·················· sb.Append( " = {" );
1877 ·················· sb.Append(i);
1878 ·················· sb.Append( '}' );
1879 ·················· parameters.Add( pc.NDOObjectId.Id[i] );
1880 ·················· if (!isLastElement)
1881 ······················ sb.Append( " AND " );
1882 ·················· i++;
1883 ·············· } );
1884
1885 ············if (!(String.IsNullOrEmpty( r.ForeignKeyTypeColumnName )))
1886 ············{
1887 ················sb.Append( " AND " );
1888 ················sb.Append( provider.GetQualifiedTableName( relClass.TableName + "." + r.ForeignKeyTypeColumnName ) );
1889 ················sb.Append( " = " );
1890 ················sb.Append( pc.NDOObjectId.Id.TypeId );
1891 ············}
1892
1893 ············IQuery q = NewQuery( t, sb.ToString(), hollow, Query.QueryLanguage.Sql );
1894
1895 ············foreach (var p in parameters)
1896 ············{
1897 ················q.Parameters.Add( p );
1898 ············}
1899
1900 ············q.AllowSubclasses = false;··// Remember: polymorphic relations always have a mapping table
1901
1902 ············IList l2 = q.Execute();
1903
1904 ············foreach (object o in l2)
1905 ················relatedObjects.Add( o );
1906
1907 ············return relatedObjects;
1908 ········}
1909
1910
1911 ········/// <summary>
1912 ········/// Resolves an relation. The loaded objects will be hollow.
1913 ········/// </summary>
1914 ········/// <param name="o">The parent object.</param>
1915 ········/// <param name="fieldName">The field name of the container or variable, which represents the relation.</param>
1916 ········/// <param name="hollow">True, if the fetched objects should be hollow.</param>
1917 ········/// <remarks>Note: 1:1 relations without mapping table will be resolved during the transition from the hollow to the persistent state. To force this transition, use the <see cref="LoadData">LoadData</see> function.<seealso cref="LoadData"/></remarks>
1918 ········public virtual void LoadRelation(object o, string fieldName, bool hollow)
1919 ········{
1920 ············IPersistenceCapable pc = CheckPc(o);
1921 ············LoadRelationInternal(pc, fieldName, hollow);
1922 ········}
1923
1924 ········
1925
1926 ········internal IList LoadRelation(IPersistenceCapable pc, Relation r, bool hollow)
1927 ········{
1928 ············IList result = null;
1929
1930 ············if (pc.NDOObjectState == NDOObjectState.Created)
1931 ················return null;
1932
1933 ············if (pc.NDOObjectState == NDOObjectState.Hollow)
1934 ················LoadData(pc);
1935
1936 ············if(r.MappingTable == null)
1937 ············{
1938 ················// 1:1 are loaded with LoadData
1939 ················if (r.Multiplicity == RelationMultiplicity.List)
1940 ················{
1941 ····················// Help GC by clearing lists
1942 ····················IList l = mappings.GetRelationContainer(pc, r);
1943 ····················if(l != null)
1944 ························l.Clear();
1945 ····················IList relatedObjects = QueryRelatedObjects(pc, r, l, hollow);
1946 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
1947 ····················result = relatedObjects;
1948 ················}
1949 ············}
1950 ············else
1951 ············{
1952 ················DataTable dt = null;
1953
1954 ················using (IMappingTableHandler handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r ))
1955 ················{
1956 ····················CheckTransaction( handler, r.MappingTable.Connection );
1957 ····················dt = handler.FindRelatedObjects(pc.NDOObjectId, this.ds);
1958 ················}
1959
1960 ················IList relatedObjects;
1961 ················if(r.Multiplicity == RelationMultiplicity.Element)
1962 ····················relatedObjects = GenericListReflector.CreateList(r.ReferencedType, dt.Rows.Count);
1963 ················else
1964 ················{
1965 ····················relatedObjects = mappings.GetRelationContainer(pc, r);
1966 ····················if(relatedObjects != null)
1967 ························relatedObjects.Clear();··// Objects will be reread
1968 ····················else
1969 ························relatedObjects = mappings.CreateRelationContainer(pc, r);
1970 ················}
1971 ····················
1972 ················foreach(DataRow objRow in dt.Rows)
1973 ················{
1974 ····················Type relType;
1975
1976 ····················if (r.MappingTable.ChildForeignKeyTypeColumnName != null)························
1977 ····················{
1978 ························object typeCodeObj = objRow[r.MappingTable.ChildForeignKeyTypeColumnName];
1979 ························if (typeCodeObj is System.DBNull)
1980 ····························throw new NDOException( 75, String.Format( "Can't resolve subclass type code of type {0} in relation '{1}' - the type code in the data row is null.", r.ReferencedTypeName, r.ToString() ) );
1981 ························relType = typeManager[(int)typeCodeObj];
1982 ························if (relType == null)
1983 ····························throw new NDOException(75, String.Format("Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.", objRow[r.MappingTable.ChildForeignKeyTypeColumnName], r.ReferencedTypeName));
1984 ····················}························
1985 ····················else
1986 ····················{
1987 ························relType = r.ReferencedType;
1988 ····················}
1989
1990 ····················//TODO: Generic Types: Exctract the type description from the type name column
1991 ····················if (relType.IsGenericTypeDefinition)
1992 ························throw new NotImplementedException("NDO doesn't support relations to generic types via mapping tables.");
1993 ····················ObjectId id = ObjectIdFactory.NewObjectId(relType, GetClass(relType), objRow, r.MappingTable, this.typeManager);
1994 ····················IPersistenceCapable relObj = FindObject(id);
1995 ····················relatedObjects.Add(relObj);
1996 ················}····
1997 ················if (r.Multiplicity == RelationMultiplicity.Element)
1998 ················{
1999 ····················Debug.Assert(relatedObjects.Count <= 1, "NDO retrieved more than one object for relation with cardinality 1");
2000 ····················mappings.SetRelationField(pc, r.FieldName, relatedObjects.Count > 0 ? relatedObjects[0] : null);
2001 ················}
2002 ················else
2003 ················{
2004 ····················mappings.SetRelationContainer(pc, r, relatedObjects);
2005 ····················result = relatedObjects;
2006 ················}
2007 ············}
2008 ············// Mark relation as loaded
2009 ············pc.NDOSetLoadState(r.Ordinal, true);
2010 ············return result;
2011 ········}
2012
2013 ········/// <summary>
2014 ········/// Loads elements of a relation
2015 ········/// </summary>
2016 ········/// <param name="pc">The object which needs to load the relation</param>
2017 ········/// <param name="relationName">The name of the relation</param>
2018 ········/// <param name="hollow">Determines, if the related objects should be hollow.</param>
2019 ········internal IList LoadRelationInternal(IPersistenceCapable pc, string relationName, bool hollow)
2020 ········{
2021 ············if (pc.NDOObjectState == NDOObjectState.Created)
2022 ················return null;
2023 ············Class cl = GetClass(pc);
2024
2025 ············Relation r = cl.FindRelation(relationName);
2026
2027 ············if ( r == null )
2028 ················throw new NDOException( 76, String.Format( "Error while loading related objects: Can't find relation mapping for the field {0}.{1}. Check your mapping file.", pc.GetType().FullName, relationName ) );
2029
2030 ············if ( pc.NDOGetLoadState( r.Ordinal ) )
2031 ················return null;
2032
2033 ············return LoadRelation(pc, r, hollow);
2034 ········}
2035
2036 ········/// <summary>
2037 ········/// Load the related objects of a parent object. The current value of the relation is replaced by the
2038 ········/// a list of objects that has been read from the DB.
2039 ········/// </summary>
2040 ········/// <param name="pc">The parent object of the relations</param>
2041 ········/// <param name="row">A data row containing the state of the object</param>
2042 ········private void LoadRelated1To1Objects(IPersistenceCapable pc, DataRow row)
2043 ········{
2044 ············// Stripped down to only serve 1:1-Relations w/out mapping table
2045 ············Class cl = GetClass(pc);
2046 ············foreach(Relation r in cl.Relations)
2047 ············{
2048 ················if(r.MappingTable == null)
2049 ················{
2050 ····················if (r.Multiplicity == RelationMultiplicity.Element)
2051 ····················{
2052 ························bool isNull = false;
2053 ························foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2054 ························{
2055 ····························isNull = isNull || (row[fkColumn.Name] == DBNull.Value);
2056 ························}
2057 ························if (isNull)
2058 ························{
2059 ····························mappings.SetRelationField(pc, r.FieldName, null);
2060 ························}
2061 ························else
2062 ························{
2063 ····························Type relType;
2064 ····························if (r.HasSubclasses)
2065 ····························{
2066 ································object o = row[r.ForeignKeyTypeColumnName];
2067 ································if (o == DBNull.Value)
2068 ····································throw new NDOException(75, String.Format(
2069 ········································"Can't resolve subclass type code {0} of type {1} - type code value is DBNull.",
2070 ········································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2071 ································relType = typeManager[(int)o];
2072 ····························}
2073 ····························else
2074 ····························{
2075 ································relType = r.ReferencedType;
2076 ····························}
2077 ····························if (relType == null)
2078 ····························{
2079 ································throw new NDOException(75, String.Format(
2080 ····································"Can't resolve subclass type code {0} of type {1} - check, if your NDOTypes.xml exists.",
2081 ····································row[r.ForeignKeyTypeColumnName], r.ReferencedTypeName));
2082 ····························}
2083 ····
2084 ····························int count = r.ForeignKeyColumns.Count();
2085 ····························object[] keydata = new object[count];
2086 ····························int i = 0;
2087 ····························foreach(ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
2088 ····························{
2089 ································keydata[i++] = row[fkColumn.Name];
2090 ····························}
2091
2092 ····························Type oidType = relType;
2093 ····························if (oidType.IsGenericTypeDefinition)
2094 ································oidType = mappings.GetRelationFieldType(r);
2095
2096 ····························ObjectId childOid = ObjectIdFactory.NewObjectId(oidType, GetClass(relType), keydata, this.typeManager);
2097 ····························if(childOid.IsValid())
2098 ································mappings.SetRelationField(pc, r.FieldName, FindObject(childOid));
2099 ····························else
2100 ································mappings.SetRelationField(pc, r.FieldName, null);
2101
2102 ························}
2103 ························pc.NDOSetLoadState(r.Ordinal, true);
2104 ····················}
2105 ················}
2106 ············}
2107 ········}
2108
2109 ········
2110 ········/// <summary>
2111 ········/// Creates a new ObjectId with the same Key value as a given ObjectId.
2112 ········/// </summary>
2113 ········/// <param name="oid">An ObjectId, which Key value will be used to build the new ObjectId.</param>
2114 ········/// <param name="t">The type of the object, the id will belong to.</param>
2115 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2116 ········/// <remarks>If the type t doesn't have a mapping in the mapping file an Exception of type NDOException is thrown.</remarks>
2117 ········public ObjectId NewObjectId(ObjectId oid, Type t)
2118 ········{
2119 ············return new ObjectId(oid.Id, t);
2120 ········}
2121
2122 ········/*
2123 ········/// <summary>
2124 ········/// Creates a new ObjectId which can be used to retrieve objects from the database.
2125 ········/// </summary>
2126 ········/// <param name="keyData">The id, which will be used to search for the object in the database</param>
2127 ········/// <param name="t">The type of the object.</param>
2128 ········/// <returns>An object of type ObjectId, or ObjectId.InvalidId</returns>
2129 ········/// <remarks>The keyData parameter must be one of the types Int32, String, Byte[] or Guid. If keyData is null or the type of keyData is invalid the function returns ObjectId.InvalidId. If the type t doesn't have a mapping in the mapping file, an Exception of type NDOException is thrown.</remarks>
2130 ········public ObjectId NewObjectId(object keyData, Type t)
2131 ········{
2132 ············if (keyData == null || keyData == DBNull.Value)
2133 ················return ObjectId.InvalidId;
2134
2135 ············Class cl =··GetClass(t);············
2136 ············
2137 ············if (cl.Oid.FieldType == typeof(int))
2138 ················return new ObjectId(new Int32Key(t, (int)keyData));
2139 ············else if (cl.Oid.FieldType == typeof(string))
2140 ················return new ObjectId(new StringKey(t, (String) keyData));
2141 ············else if (cl.Oid.FieldType == typeof(Guid))
2142 ················if (keyData is string)
2143 ····················return new ObjectId(new GuidKey(t, new Guid((String) keyData)));
2144 ················else
2145 ····················return new ObjectId(new GuidKey(t, (Guid) keyData));
2146 ············else if (cl.Oid.FieldType == typeof(MultiKey))
2147 ················return new ObjectId(new MultiKey(t, (object[]) keyData));
2148 ············else
2149 ················return ObjectId.InvalidId;
2150 ········}
2151 ········*/
2152
2153
2154 ········/*
2155 ········ * ····················if (cl.Oid.FieldName != null && HasOwnerCreatedIds)
2156 ····················{
2157 ························// The column, which hold the oid gets overwritten, if
2158 ························// Oid.FieldName contains a value.
2159 */
2160 ········private void WriteIdFieldsToRow(IPersistenceCapable pc, DataRow row)
2161 ········{
2162 ············Class cl = GetClass(pc);
2163
2164 ············if (cl.Oid.IsDependent)
2165 ················return;
2166
2167 ············Key key = pc.NDOObjectId.Id;
2168
2169 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2170 ············{
2171 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2172 ················if (oidColumn.FieldName != null)
2173 ················{
2174 ····················row[oidColumn.Name] = key[i];
2175 ················}
2176 ············}
2177 ········}
2178
2179 ········private void WriteIdToRow(IPersistenceCapable pc, DataRow row)
2180 ········{
2181 ············NDO.Mapping.Class cl = GetClass(pc);
2182 ············ObjectId oid = pc.NDOObjectId;
2183
2184 ············if (cl.TimeStampColumn != null)
2185 ················row[cl.TimeStampColumn] = pc.NDOTimeStamp;
2186
2187 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2188 ················return;
2189
2190 ············Key key = oid.Id;
2191
2192 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2193 ············{
2194 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2195 ················row[oidColumn.Name] = key[i];
2196 ············}
2197 ········}
2198
2199 ········private void ReadIdFromRow(IPersistenceCapable pc, DataRow row)
2200 ········{
2201 ············ObjectId oid = pc.NDOObjectId;
2202 ············NDO.Mapping.Class cl = GetClass(pc);
2203
2204 ············if (cl.Oid.IsDependent)··// Oid data is in relation columns
2205 ················return;
2206
2207 ············Key key = oid.Id;
2208
2209 ············for (int i = 0; i < cl.Oid.OidColumns.Count; i++)
2210 ············{
2211 ················OidColumn oidColumn = (OidColumn)cl.Oid.OidColumns[i];
2212 ················object o = row[oidColumn.Name];
2213 ················if (!(o is Int32) && !(o is Guid) && !(o is String) && !(o is Int64))
2214 ····················throw new NDOException(78, "ReadId: invalid Id Column type in " + oidColumn.Name + ": " + o.GetType().FullName);
2215 ················if (oidColumn.SystemType == typeof(Guid) && (o is String))
2216 ····················key[i] = new Guid((string)o);
2217 ················else
2218 ····················key[i] = o;
2219 ············}
2220
2221 ········}
2222
2223 ········private void ReadId (Cache.Entry e)
2224 ········{
2225 ············ReadIdFromRow(e.pc, e.row);
2226 ········}
2227
2228 ········private void WriteObject(IPersistenceCapable pc, DataRow row, string[] fieldNames)
2229 ········{
2230 ············WriteObject(pc, row, fieldNames, 0);
2231 ········}
2232
2233 ········private void ReadTimeStamp(Class cl, IPersistenceCapable pc, DataRow row)
2234 ········{
2235 ············if (cl.TimeStampColumn == null)
2236 ················return;
2237 ············object col = row[cl.TimeStampColumn];
2238 ············if (col is String)
2239 ················pc.NDOTimeStamp = new Guid(col.ToString());
2240 ············else
2241 ················pc.NDOTimeStamp = (Guid) col;
2242 ········}
2243
2244
2245
2246 ········private void ReadTimeStamp(Cache.Entry e)
2247 ········{
2248 ············IPersistenceCapable pc = e.pc;
2249 ············NDO.Mapping.Class cl = GetClass(pc);
2250 ············Debug.Assert(!IsFakeRow(cl, e.row));
2251 ············if (cl.TimeStampColumn == null)
2252 ················return;
2253 ············if (e.row[cl.TimeStampColumn] is String)
2254 ················e.pc.NDOTimeStamp = new Guid(e.row[cl.TimeStampColumn].ToString());
2255 ············else
2256 ················e.pc.NDOTimeStamp = (Guid) e.row[cl.TimeStampColumn];
2257 ········}
2258
2259 ········/// <summary>
2260 ········/// Determines, if any objects are new, changed or deleted.
2261 ········/// </summary>
2262 ········public virtual bool HasChanges
2263 ········{
2264 ············get
2265 ············{
2266 ················return cache.LockedObjects.Count > 0;
2267 ············}
2268 ········}
2269
2270 ········/// <summary>
2271 ········/// Do the update for all rows in the ds.
2272 ········/// </summary>
2273 ········/// <param name="types">Types with changes.</param>
2274 ········/// <param name="delete">True, if delete operations are to be performed.</param>
2275 ········/// <remarks>
2276 ········/// Delete and Insert/Update operations are to be separated to maintain the type order.
2277 ········/// </remarks>
2278 ········private void UpdateTypes(IList types, bool delete)
2279 ········{
2280 ············foreach(Type t in types)
2281 ············{
2282 ················//Debug.WriteLine("Update Deleted Objects: "··+ t.Name);
2283 ················using (IPersistenceHandler handler = PersistenceHandlerManager.GetPersistenceHandler( t ))
2284 ················{
2285 ····················CheckTransaction( handler, t );
2286 ····················ConcurrencyErrorHandler ceh = new ConcurrencyErrorHandler(this.OnConcurrencyError);
2287 ····················handler.ConcurrencyError += ceh;
2288 ····················try
2289 ····················{
2290 ························DataTable dt = GetTable(t);
2291 ························if (delete)
2292 ····························handler.UpdateDeletedObjects( dt );
2293 ························else
2294 ····························handler.Update( dt );
2295 ····················}
2296 ····················finally
2297 ····················{
2298 ························handler.ConcurrencyError -= ceh;
2299 ····················}
2300 ················}
2301 ············}
2302 ········}
2303
2304 ········internal void UpdateCreatedMappingTableEntries()
2305 ········{
2306 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2307 ············{
2308 ················if (!e.DeleteEntry)
2309 ····················WriteMappingTableEntry(e);
2310 ············}
2311 ············// Now update all mapping tables
2312 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2313 ············{
2314 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2315 ················handler.Update(ds);
2316 ············}
2317 ········}
2318
2319 ········internal void UpdateDeletedMappingTableEntries()
2320 ········{
2321 ············foreach (MappingTableEntry e in createdMappingTableObjects)
2322 ············{
2323 ················if (e.DeleteEntry)
2324 ····················WriteMappingTableEntry(e);
2325 ············}
2326 ············// Now update all mapping tables
2327 ············foreach (IMappingTableHandler handler in mappingHandler.Values)
2328 ············{
2329 ················CheckTransaction( handler, handler.Relation.MappingTable.Connection );
2330 ················handler.Update(ds);
2331 ············}
2332 ········}
2333
2334 ········/// <summary>
2335 ········/// Save all changed object into the DataSet and update the DB.
2336 ········/// When a newly created object is written to DB, the key might change. Therefore,
2337 ········/// the id is updated and the object is removed and re-inserted into the cache.
2338 ········/// </summary>
2339 ········public virtual void Save(bool deferCommit = false)
2340 ········{
2341 ············this.DeferredMode = deferCommit;
2342 ············var htOnSaving = new HashSet<ObjectId>();
2343 ············for(;;)
2344 ············{
2345 ················// We need to work on a copy of the locked objects list,
2346 ················// since the handlers might add more objects to the cache
2347 ················var lockedObjects = cache.LockedObjects.ToList();
2348 ················int count = lockedObjects.Count;
2349 ················foreach(Cache.Entry e in lockedObjects)
2350 ················{
2351 ····················if (e.pc.NDOObjectState != NDOObjectState.Deleted)
2352 ····················{
2353 ························IPersistenceNotifiable ipn = e.pc as IPersistenceNotifiable;
2354 ························if (ipn != null)
2355 ························{
2356 ····························if (!htOnSaving.Contains(e.pc.NDOObjectId))
2357 ····························{
2358 ································ipn.OnSaving();
2359 ································htOnSaving.Add(e.pc.NDOObjectId);
2360 ····························}
2361 ························}
2362 ····················}
2363 ················}
2364 ················// The system is stable, if the count doesn't change
2365 ················if (cache.LockedObjects.Count == count)
2366 ····················break;
2367 ············}
2368
2369 ············if (this.OnSavingEvent != null)
2370 ············{
2371 ················IList onSavingObjects = new ArrayList(cache.LockedObjects.Count);
2372 ················foreach(Cache.Entry e in cache.LockedObjects)
2373 ····················onSavingObjects.Add(e.pc);
2374 ················OnSavingEvent(onSavingObjects);
2375 ············}
2376
2377 ············List<Type> types = new List<Type>();
2378 ············List<IPersistenceCapable> deletedObjects = new List<IPersistenceCapable>();
2379 ············List<IPersistenceCapable> hollowModeObjects = hollowMode ? new List<IPersistenceCapable>() : null;
2380 ············List<IPersistenceCapable> changedObjects = new List<IPersistenceCapable>();
2381 ············List<IPersistenceCapable> addedObjects = new List<IPersistenceCapable>();
2382 ············List<Cache.Entry> addedCacheEntries = new List<Cache.Entry>();
2383
2384 ············// Save current state in DataSet
2385 ············foreach (Cache.Entry e in cache.LockedObjects)
2386 ············{
2387 ················Type objType = e.pc.GetType();
2388
2389 ················if (objType.IsGenericType && !objType.IsGenericTypeDefinition)
2390 ····················objType = objType.GetGenericTypeDefinition();
2391
2392 ················Class cl = GetClass(e.pc);
2393 ················//Debug.WriteLine("Saving: " + objType.Name + " id = " + e.pc.NDOObjectId.Dump());
2394 ················if(!types.Contains(objType))
2395 ················{
2396 ····················//Debug.WriteLine("Added··type " + objType.Name);
2397 ····················types.Add(objType);
2398 ················}
2399 ················NDOObjectState objectState = e.pc.NDOObjectState;
2400 ················if(objectState == NDOObjectState.Deleted)
2401 ················{
2402 ····················deletedObjects.Add(e.pc);
2403 ················}
2404 ················else if(objectState == NDOObjectState.Created)
2405 ················{
2406 ····················WriteObject(e.pc, e.row, cl.ColumnNames);····················
2407 ····················WriteIdFieldsToRow(e.pc, e.row);··// If fields are mapped to Oid, write them into the row
2408 ····················WriteForeignKeysToRow(e.pc, e.row);
2409 ····················//····················Debug.WriteLine(e.pc.GetType().FullName);
2410 ····················//····················DataRow[] rows = new DataRow[cache.LockedObjects.Count];
2411 ····················//····················i = 0;
2412 ····················//····················foreach(Cache.Entry e2 in cache.LockedObjects)
2413 ····················//····················{
2414 ····················//························rows[i++] = e2.row;
2415 ····················//····················}
2416 ····················//····················System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("testCommand");
2417 ····················//····················new SqlDumper(new DebugLogAdapter(), NDOProviderFactory.Instance["Sql"], cmd, cmd, cmd, cmd).Dump(rows);
2418
2419 ····················addedCacheEntries.Add(e);
2420 ····················addedObjects.Add( e.pc );
2421 ····················//····················objectState = NDOObjectState.Persistent;
2422 ················}
2423 ················else
2424 ················{
2425 ····················if (e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2426 ························changedObjects.Add( e.pc );
2427 ····················WriteObject(e.pc, e.row, cl.ColumnNames);
2428 ····················WriteForeignKeysToRow(e.pc, e.row); ····················
2429 ················}
2430 ················if(hollowMode && (objectState == NDOObjectState.Persistent || objectState == NDOObjectState.Created || objectState == NDOObjectState.PersistentDirty) )
2431 ················{
2432 ····················hollowModeObjects.Add(e.pc);
2433 ················}
2434 ············}
2435
2436 ············// Before we delete any db rows, we have to make sure, to delete mapping
2437 ············// table entries first, which might have relations to the db rows to be deleted
2438 ············UpdateDeletedMappingTableEntries();
2439
2440 ············// Update DB
2441 ············if (ds.HasChanges())
2442 ············{
2443
2444 ················// We need the reversed update order for deletions.
2445 ················types.Sort( ( t1, t2 ) =>
2446 ················{
2447 ····················int i1 = mappings.GetUpdateOrder( t1 );
2448 ····················int i2 = mappings.GetUpdateOrder( t2 );
2449 ····················if (i1 < i2)
2450 ····················{
2451 ························if (!addedObjects.Any( pc => pc.GetType() == t1 ))
2452 ····························i1 += 100000;
2453 ····················}
2454 ····················else
2455 ····················{
2456 ························if (!addedObjects.Any( pc => pc.GetType() == t2 ))
2457 ····························i2 += 100000;
2458 ····················}
2459 ····················return i2 - i1;
2460 ················} );
2461
2462 ················// Delete records first
2463
2464 ················UpdateTypes(types, true);
2465
2466 ················// Now do all other updates in correct order.
2467 ················types.Reverse();
2468
2469 ················UpdateTypes(types, false);
2470 ················
2471 ················ds.AcceptChanges();
2472 ················if(createdDirectObjects.Count > 0)
2473 ················{
2474 ····················// Rewrite all children that have foreign keys to parents which have just been saved now.
2475 ····················// They must be written again to store the correct foreign keys.
2476 ····················foreach(IPersistenceCapable pc in createdDirectObjects)
2477 ····················{
2478 ························Class cl = GetClass(pc);
2479 ························DataRow r = this.cache.GetDataRow(pc);
2480 ························string fakeColumnName = GetFakeRowOidColumnName(cl);
2481 ························object o = r[fakeColumnName];
2482 ························r[fakeColumnName] = o;
2483 ····················}
2484
2485 ····················UpdateTypes(types, false);
2486 ················}
2487
2488 ················// Because object id might have changed during DB insertion, re-register newly created objects in the cache.
2489 ················foreach(Cache.Entry e in addedCacheEntries)
2490 ················{
2491 ····················cache.DeregisterLockedObject(e.pc);
2492 ····················ReadId(e);
2493 ····················cache.RegisterLockedObject(e.pc, e.row, e.relations);
2494 ················}
2495
2496 ················// Now update all mapping tables. Because of possible subclasses, there is no
2497 ················// relation between keys in the dataset schema. Therefore, we can update mapping
2498 ················// tables only after all other objects have been written to ensure correct foreign keys.
2499 ················UpdateCreatedMappingTableEntries();
2500
2501 ················// The rows may contain now new Ids, which should be
2502 ················// stored in the lostRowInfo's before the rows get detached
2503 ················foreach(Cache.Entry e in cache.LockedObjects)
2504 ················{
2505 ····················if (e.row.RowState != DataRowState.Detached)
2506 ····················{
2507 ························IPersistenceCapable pc = e.pc;
2508 ························ReadLostForeignKeysFromRow(GetClass(pc), pc, e.row);
2509 ····················}
2510 ················}
2511
2512 ················ds.AcceptChanges();
2513 ············}
2514
2515 ············EndSave(!deferCommit);
2516
2517 ············foreach(IPersistenceCapable pc in deletedObjects)
2518 ············{
2519 ················MakeObjectTransient(pc, false);
2520 ············}
2521
2522 ············ds.Clear();
2523 ············mappingHandler.Clear();
2524 ············createdDirectObjects.Clear();
2525 ············createdMappingTableObjects.Clear();
2526 ············this.relationChanges.Clear();
2527
2528 ············if(hollowMode)
2529 ············{
2530 ················MakeHollow(hollowModeObjects);
2531 ············}
2532
2533 ············if (this.OnSavedEvent != null)
2534 ············{
2535 ················AuditSet auditSet = new AuditSet()
2536 ················{
2537 ····················ChangedObjects = changedObjects,
2538 ····················CreatedObjects = addedObjects,
2539 ····················DeletedObjects = deletedObjects
2540 ················};
2541 ················this.OnSavedEvent( auditSet );
2542 ············}
2543 ········}
2544
2545 ········private void EndSave(bool forceCommit)
2546 ········{
2547 ············foreach(Cache.Entry e in cache.LockedObjects)
2548 ············{
2549 ················if (e.pc.NDOObjectState == NDOObjectState.Created || e.pc.NDOObjectState == NDOObjectState.PersistentDirty)
2550 ····················this.ReadTimeStamp(e);
2551 ················e.pc.NDOObjectState = NDOObjectState.Persistent;
2552 ············}
2553
2554 ············cache.UnlockAll();
2555
2556 ············CheckEndTransaction(forceCommit);
2557 ········}
2558
2559 ········/// <summary>
2560 ········/// Write all foreign keys for 1:1-relations.
2561 ········/// </summary>
2562 ········/// <param name="pc">The persistent object.</param>
2563 ········/// <param name="pcRow">The DataRow of the pesistent object.</param>
2564 ········private void WriteForeignKeysToRow(IPersistenceCapable pc, DataRow pcRow)
2565 ········{
2566 ············foreach(Relation r in mappings.Get1to1Relations(pc.GetType()))
2567 ············{
2568 ················IPersistenceCapable relObj = (IPersistenceCapable)mappings.GetRelationField(pc, r.FieldName);
2569 ················bool isDependent = GetClass(pc).Oid.IsDependent;
2570
2571 ················if ( relObj != null )
2572 ················{
2573 ····················int i = 0;
2574 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2575 ····················{
2576 ························pcRow[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2577 ····················}
2578 ····················if ( r.ForeignKeyTypeColumnName != null )
2579 ····················{
2580 ························pcRow[r.ForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2581 ····················}
2582 ················}
2583 ················else
2584 ················{
2585 ····················foreach ( ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2586 ····················{
2587 ························pcRow[fkColumn.Name] = DBNull.Value;
2588 ····················}
2589 ····················if ( r.ForeignKeyTypeColumnName != null )
2590 ····················{
2591 ························pcRow[r.ForeignKeyTypeColumnName] = DBNull.Value;
2592 ····················}
2593 ················}
2594 ············}
2595 ········}
2596
2597
2598
2599 ········/// <summary>
2600 ········/// Write a mapping table entry to it's corresponding table. This is a pair of foreign keys.
2601 ········/// </summary>
2602 ········/// <param name="e">the mapping table entry</param>
2603 ········private void WriteMappingTableEntry(MappingTableEntry e)
2604 ········{
2605 ············IPersistenceCapable pc = e.ParentObject;
2606 ············IPersistenceCapable relObj = e.RelatedObject;
2607 ············Relation r = e.Relation;
2608 ············DataTable dt = GetTable(r.MappingTable.TableName);
2609 ············DataRow row = dt.NewRow();
2610 ············int i = 0;
2611 ············foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns )
2612 ············{
2613 ················row[fkColumn.Name] = pc.NDOObjectId.Id[i++];
2614 ············}
2615 ············i = 0;
2616 ············foreach (ForeignKeyColumn fkColumn in r.MappingTable.ChildForeignKeyColumns)
2617 ············{
2618 ················row[fkColumn.Name] = relObj.NDOObjectId.Id[i++];
2619 ············}
2620
2621 ············if (r.ForeignKeyTypeColumnName != null)
2622 ················row[r.ForeignKeyTypeColumnName] = pc.NDOObjectId.Id.TypeId;
2623 ············if (r.MappingTable.ChildForeignKeyTypeColumnName != null)
2624 ················row[r.MappingTable.ChildForeignKeyTypeColumnName] = relObj.NDOObjectId.Id.TypeId;
2625
2626 ············dt.Rows.Add(row);
2627 ············if(e.DeleteEntry)
2628 ············{
2629 ················row.AcceptChanges();
2630 ················row.Delete();
2631 ············}
2632
2633 ············IMappingTableHandler handler;
2634 ············if (!mappingHandler.TryGetValue( r, out handler ))
2635 ············{
2636 ················mappingHandler[r] = handler = PersistenceHandlerManager.GetPersistenceHandler( pc ).GetMappingTableHandler( r );
2637 ············}
2638 ········}
2639
2640
2641 ········/// <summary>
2642 ········/// Undo changes of a certain object
2643 ········/// </summary>
2644 ········/// <param name="o">Object to undo</param>
2645 ········public void Restore(object o)
2646 ········{············
2647 ············IPersistenceCapable pc = CheckPc(o);
2648 ············Cache.Entry e = null;
2649 ············foreach (Cache.Entry entry in cache.LockedObjects)
2650 ············{
2651 ················if (entry.pc == pc)
2652 ················{
2653 ····················e = entry;
2654 ····················break;
2655 ················}
2656 ············}
2657 ············if (e == null)
2658 ················return;
2659 ············Class cl = GetClass(e.pc);
2660 ············switch (pc.NDOObjectState)
2661 ············{
2662 ················case NDOObjectState.PersistentDirty:
2663 ····················ObjectListManipulator.Remove(createdDirectObjects, pc);
2664 ····················foreach(Relation r in cl.Relations)
2665 ····················{
2666 ························if (r.Multiplicity == RelationMultiplicity.Element)
2667 ························{
2668 ····························IPersistenceCapable subPc = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
2669 ····························if (subPc != null && cache.IsLocked(subPc))
2670 ································Restore(subPc);
2671 ························}
2672 ························else
2673 ························{
2674 ····························if (!pc.NDOGetLoadState(r.Ordinal))
2675 ································continue;
2676 ····························IList subList = (IList) mappings.GetRelationContainer(pc, r);
2677 ····························if (subList != null)
2678 ····························{
2679 ································foreach(IPersistenceCapable subPc2 in subList)
2680 ································{
2681 ····································if (cache.IsLocked(subPc2))
2682 ········································Restore(subPc2);
2683 ································}
2684 ····························}
2685 ························}
2686 ····················}
2687 ····················RestoreRelatedObjects(pc, e.relations);
2688 ····················e.row.RejectChanges();
2689 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2690 ····················cache.Unlock(pc);
2691 ····················pc.NDOObjectState = NDOObjectState.Persistent;
2692 ····················break;
2693 ················case NDOObjectState.Created:
2694 ····················ReadObject(pc, e.row, cl.ColumnNames, 0);
2695 ····················cache.Unlock(pc);
2696 ····················MakeObjectTransient(pc, true);
2697 ····················break;
2698 ················case NDOObjectState.Deleted:
2699 ····················if (!this.IsFakeRow(cl, e.row))
2700 ····················{
2701 ························e.row.RejectChanges();
2702 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2703 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2704 ····················}
2705 ····················else
2706 ····················{
2707 ························e.row.RejectChanges();
2708 ························e.pc.NDOObjectState = NDOObjectState.Hollow;
2709 ····················}
2710 ····················cache.Unlock(pc);
2711 ····················break;
2712
2713 ············}
2714 ········}
2715
2716 ········/// <summary>
2717 ········/// Aborts a pending transaction without restoring the object state.
2718 ········/// </summary>
2719 ········/// <remarks>Supports both local and EnterpriseService Transactions.</remarks>
2720 ········public virtual void AbortTransaction()
2721 ········{
2722 ············TransactionScope.Dispose();
2723 ········}
2724
2725 ········/// <summary>
2726 ········/// Rejects all changes and restores the original object state. Added Objects will be made transient.
2727 ········/// </summary>
2728 ········public virtual void Abort()
2729 ········{
2730 ············// RejectChanges of the DS cannot be called because newly added rows would be deleted,
2731 ············// and therefore, couldn't be restored. Instead we call RejectChanges() for each
2732 ············// individual row.
2733 ············createdDirectObjects.Clear();
2734 ············createdMappingTableObjects.Clear();
2735 ············ArrayList deletedObjects = new ArrayList();
2736 ············ArrayList hollowModeObjects = hollowMode ? new ArrayList() : null;
2737
2738 ············// Read all objects from DataSet
2739 ············foreach (Cache.Entry e in cache.LockedObjects)
2740 ············{
2741 ················//Debug.WriteLine("Reading: " + e.pc.GetType().Name);
2742
2743 ················Class cl = GetClass(e.pc);
2744 ················bool isFakeRow = this.IsFakeRow(cl, e.row);
2745 ················if (!isFakeRow)
2746 ················{
2747 ····················RestoreRelatedObjects(e.pc, e.relations);
2748 ················}
2749 ················else
2750 ················{
2751 ····················Debug.Assert(e.pc.NDOObjectState == NDOObjectState.Deleted, "Fake row objects can only exist in deleted state");
2752 ················}
2753
2754 ················switch(e.pc.NDOObjectState)
2755 ················{
2756 ····················case NDOObjectState.Created:
2757 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2758 ························deletedObjects.Add(e.pc);
2759 ························break;
2760
2761 ····················case NDOObjectState.PersistentDirty:
2762 ························e.row.RejectChanges();
2763 ························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2764 ························e.pc.NDOObjectState = NDOObjectState.Persistent;
2765 ························break;
2766
2767 ····················case NDOObjectState.Deleted:
2768 ························if (!isFakeRow)
2769 ························{
2770 ····························e.row.RejectChanges();
2771 ····························ReadObject(e.pc, e.row, cl.ColumnNames, 0);
2772 ····························e.pc.NDOObjectState = NDOObjectState.Persistent;
2773 ························}
2774 ························else
2775 ························{
2776 ····························e.row.RejectChanges();
2777 ····························e.pc.NDOObjectState = NDOObjectState.Hollow;
2778 ························}
2779 ························break;
2780
2781 ····················default:
2782 ························throw new InternalException(2082, "Abort(): wrong state detected: " + e.pc.NDOObjectState + " id = " + e.pc.NDOObjectId.Dump());
2783 ························//Debug.Assert(false, "Object with wrong state detected: " + e.pc.NDOObjectState);
2784 ························//break;
2785 ················}
2786 ················if(hollowMode && e.pc.NDOObjectState == NDOObjectState.Persistent)
2787 ················{
2788 ····················hollowModeObjects.Add(e.pc);
2789 ················}
2790 ············}
2791 ············cache.UnlockAll();
2792 ············foreach(IPersistenceCapable pc in deletedObjects)
2793 ············{
2794 ················MakeObjectTransient(pc, true);
2795 ············}
2796 ············ds.Clear();
2797 ············mappingHandler.Clear();
2798 ············if(hollowMode)
2799 ············{
2800 ················MakeHollow(hollowModeObjects);
2801 ············}
2802
2803 ············this.relationChanges.Clear();
2804
2805
2806 ············AbortTransaction();
2807 ········}
2808
2809
2810 ········/// <summary>
2811 ········/// Reset object to its transient state and remove it from the cache. Optinally, remove the object id to
2812 ········/// disable future access.
2813 ········/// </summary>
2814 ········/// <param name="pc"></param>
2815 ········/// <param name="removeId">Indicates if the object id should be nulled</param>
2816 ········private void MakeObjectTransient(IPersistenceCapable pc, bool removeId)
2817 ········{
2818 ············cache.Deregister(pc);
2819 ············// MakeTransient doesn't remove the ID, because delete makes objects transient and we need the id for the ChangeLog············
2820 ············if(removeId)
2821 ············{
2822 ················pc.NDOObjectId = null;
2823 ············}
2824 ············pc.NDOObjectState = NDOObjectState.Transient;
2825 ············pc.NDOStateManager = null;
2826 ········}
2827
2828 ········/// <summary>
2829 ········/// Makes an object transient.
2830 ········/// The object can be used afterwards, but changes will not be saved in the database.
2831 ········/// </summary>
2832 ········/// <remarks>
2833 ········/// Only persistent or hollow objects can be detached. Hollow objects are loaded to ensure valid data.
2834 ········/// </remarks>
2835 ········/// <param name="o">The object to detach.</param>
2836 ········public void MakeTransient(object o)
2837 ········{
2838 ············IPersistenceCapable pc = CheckPc(o);
2839 ············if(pc.NDOObjectState != NDOObjectState.Persistent && pc.NDOObjectState··!= NDOObjectState.Hollow)
2840 ············{
2841 ················throw new NDOException(79, "MakeTransient: Illegal state '" + pc.NDOObjectState + "' for this operation");
2842 ············}
2843
2844 ············if(pc.NDOObjectState··== NDOObjectState.Hollow)
2845 ············{
2846 ················LoadData(pc);
2847 ············}
2848 ············MakeObjectTransient(pc, true);
2849 ········}
2850
2851
2852 ········/// <summary>
2853 ········/// Make all objects of a list transient.
2854 ········/// </summary>
2855 ········/// <param name="list">the list of transient objects</param>
2856 ········public void MakeTransient(System.Collections.IList list)
2857 ········{
2858 ············foreach (IPersistenceCapable pc in list)
2859 ················MakeTransient(pc);
2860 ········}
2861
2862 ········/// <summary>
2863 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2864 ········/// </summary>
2865 ········/// <param name="o">The object to remove</param>
2866 ········public void Delete(object o)
2867 ········{
2868 ············IPersistenceCapable pc = CheckPc(o);
2869 ············if (pc.NDOObjectState == NDOObjectState.Transient)
2870 ············{
2871 ················throw new NDOException( 120, "Can't delete transient object" );
2872 ············}
2873 ············if (pc.NDOObjectState != NDOObjectState.Deleted)
2874 ············{
2875 ················Delete(pc, true);
2876 ············}
2877 ········}
2878
2879
2880 ········private void LoadAllRelations(object o)
2881 ········{
2882 ············IPersistenceCapable pc = CheckPc(o);
2883 ············Class cl = GetClass(pc);
2884 ············foreach(Relation r in cl.Relations)
2885 ············{
2886 ················if (!pc.NDOGetLoadState(r.Ordinal))
2887 ····················LoadRelation(pc, r, true);
2888 ············}
2889 ········}
2890
2891
2892 ········/// <summary>
2893 ········/// Remove an object from the DB. Note that the object itself is not destroyed and may still be used.
2894 ········/// </summary>
2895 ········/// <remarks>
2896 ········/// If checkAssoziations it true, the object cannot be deleted if it is part of a bidirectional assoziation.
2897 ········/// This is the case if delete was called from user code. Internally, an object may be deleted because it is called from
2898 ········/// the parent object.
2899 ········/// </remarks>
2900 ········/// <param name="pc">the object to remove</param>
2901 ········/// <param name="checkAssoziations">true if child of a composition can't be deleted</param>
2902 ········private void Delete(IPersistenceCapable pc, bool checkAssoziations)
2903 ········{
2904 ············//Debug.WriteLine("Delete " + pc.NDOObjectId.Dump());
2905 ············//Debug.Indent();
2906 ············IDeleteNotifiable idn = pc as IDeleteNotifiable;
2907 ············if (idn != null)
2908 ················idn.OnDelete();
2909
2910 ············LoadAllRelations(pc);
2911 ············DeleteRelatedObjects(pc, checkAssoziations);
2912
2913 ············switch(pc.NDOObjectState)
2914 ············{
2915 ················case NDOObjectState.Transient:
2916 ····················throw new NDOException(80, "Cannot delete transient object: " + pc.NDOObjectId);
2917
2918 ················case NDOObjectState.Created:
2919 ····················DataRow row = cache.GetDataRow(pc);
2920 ····················row.Delete();
2921 ····················ArrayList cdosToDelete = new ArrayList();
2922 ····················foreach (IPersistenceCapable cdo in createdDirectObjects)
2923 ························if ((object)cdo == (object)pc)
2924 ····························cdosToDelete.Add(cdo);
2925 ····················foreach (object o in cdosToDelete)
2926 ························ObjectListManipulator.Remove(createdDirectObjects, o);
2927 ····················MakeObjectTransient(pc, true);
2928 ····················break;
2929 ················case NDOObjectState.Persistent:
2930 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2931 ························SaveObjectState(pc, true);
2932 ····················row = cache.GetDataRow(pc);
2933 ····················row.Delete();
2934 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2935 ····················break;
2936 ················case NDOObjectState.Hollow:
2937 ····················if (!cache.IsLocked(pc)) // Deletes k�nnen durchaus mehrmals aufgerufen werden
2938 ························SaveFakeRow(pc);
2939 ····················row = cache.GetDataRow(pc);
2940 ····················row.Delete();
2941 ····················pc.NDOObjectState = NDOObjectState.Deleted;
2942 ····················break;
2943
2944 ················case NDOObjectState.PersistentDirty:
2945 ····················row = cache.GetDataRow(pc);
2946 ····················row.Delete();
2947 ····················pc.NDOObjectState··= NDOObjectState.Deleted;
2948 ····················break;
2949
2950 ················case NDOObjectState.Deleted:
2951 ····················break;
2952 ············}
2953
2954 ············//Debug.Unindent();
2955 ········}
2956
2957
2958 ········private void DeleteMappingTableEntry(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2959 ········{
2960 ············MappingTableEntry mte = null;
2961 ············foreach(MappingTableEntry e in createdMappingTableObjects)
2962 ············{
2963 ················if(e.ParentObject.NDOObjectId == pc.NDOObjectId && e.RelatedObject.NDOObjectId == child.NDOObjectId && e.Relation == r)
2964 ················{
2965 ····················mte = e;
2966 ····················break;
2967 ················}
2968 ············}
2969
2970 ············if(pc.NDOObjectState == NDOObjectState.Created || child.NDOObjectState == NDOObjectState.Created)
2971 ············{
2972 ················if (mte != null)
2973 ····················createdMappingTableObjects.Remove(mte);
2974 ············}
2975 ············else
2976 ············{
2977 ················if (mte == null)
2978 ····················createdMappingTableObjects.Add(new MappingTableEntry(pc, child, r, true));
2979 ············}
2980 ········}
2981
2982 ········private void DeleteOrNullForeignRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
2983 ········{
2984 ············// Two tasks: a) Null the foreign key
2985 ············//··············b) remove the element from the foreign container
2986
2987 ············if (!r.Bidirectional)
2988 ················return;
2989
2990 ············// this keeps the oid valid
2991 ············if (GetClass(child.GetType()).Oid.IsDependent)
2992 ················return;
2993
2994 ············if(r.ForeignRelation.Multiplicity == RelationMultiplicity.Element)
2995 ············{
2996 ················LoadAndMarkDirty(child);
2997 ················mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
2998 ············}
2999 ············else //if(r.Multiplicity == RelationMultiplicity.List &&
3000 ················// r.ForeignRelation.Multiplicity == RelationMultiplicity.List)··
3001 ············{
3002 ················if (!child.NDOGetLoadState(r.ForeignRelation.Ordinal))
3003 ····················LoadRelation(child, r.ForeignRelation, true);
3004 ················IList l = mappings.GetRelationContainer(child, r.ForeignRelation);
3005 ················if (l == null)
3006 ····················throw new NDOException(67, "Can't remove object from the list " + child.GetType().FullName + "." + r.ForeignRelation.FieldName + ". The list is null.");
3007 ················ObjectListManipulator.Remove(l, pc);
3008 ················// Don't need to delete the mapping table entry, because that was done
3009 ················// through the parent.
3010 ············}
3011 ········}
3012
3013 ········private void DeleteOrNullRelation(IPersistenceCapable pc, Relation r, IPersistenceCapable child)
3014 ········{
3015 ············// 1) Element····nomap····ass
3016 ············// 2) Element····nomap····comp
3017 ············// 3) Element····map········ass
3018 ············// 4) Element····map········comp
3019 ············// 5) List········nomap····ass
3020 ············// 6) List········nomap····comp
3021 ············// 7) List········map········ass
3022 ············// 8) List········map········comp
3023
3024 ············// Two tasks: Null foreign key and, if Composition, delete the child
3025
3026 ············// If Mapping Table, delete the entry - 3,7
3027 ············// If List and assoziation, null the foreign key in the foreign class - 5
3028 ············// If Element, null the foreign key in the own class 1,2,3,4
3029 ············// If composition, delete the child 2,4,6,8
3030
3031 ············// If the relObj is newly created
3032 ············ObjectListManipulator.Remove(createdDirectObjects, child);
3033 ············Class childClass = GetClass(child);
3034
3035 ············if (r.MappingTable != null)··// 3,7
3036 ············{················
3037 ················DeleteMappingTableEntry(pc, r, child);
3038 ············}
3039 ············else if (r.Multiplicity == RelationMultiplicity.List
3040 ················&& !r.Composition && !childClass.Oid.IsDependent) // 5
3041 ············{················
3042 ················LoadAndMarkDirty(child);
3043 ················DataRow row = this.cache.GetDataRow(child);
3044 ················foreach (ForeignKeyColumn fkColumnn in r.ForeignKeyColumns)
3045 ················{
3046 ····················row[fkColumnn.Name] = DBNull.Value;
3047 ····················child.NDOLoadState.ReplaceRowInfo(fkColumnn.Name, DBNull.Value);
3048 ················}
3049 ············}
3050 ············else if (r.Multiplicity == RelationMultiplicity.Element) // 1,2,3,4
3051 ············{
3052 ················LoadAndMarkDirty(pc);
3053 ············}
3054 ············if (r.Composition || childClass.Oid.IsDependent)
3055 ············{
3056 #if DEBUG
3057 ················if (child.NDOObjectState == NDOObjectState.Transient)
3058 ····················Debug.WriteLine("***** Object shouldn't be transient: " + child.GetType().FullName);
3059 #endif
3060 ················// Deletes the foreign key in case of List multiplicity
3061 ················// In case of Element multiplicity, the parent is either deleted,
3062 ················// or RemoveRelatedObject is called because the relation has been nulled.
3063 ················Delete(child);··
3064 ············}
3065 ········}
3066
3067 ········/// <summary>
3068 ········/// Remove a related object
3069 ········/// </summary>
3070 ········/// <param name="pc"></param>
3071 ········/// <param name="r"></param>
3072 ········/// <param name="child"></param>
3073 ········/// <param name="calledFromStateManager"></param>
3074 ········protected virtual void InternalRemoveRelatedObject(IPersistenceCapable pc, Relation r, IPersistenceCapable child, bool calledFromStateManager)
3075 ········{
3076 ············//TODO: We need a relation management, which is independent of
3077 ············//the state management of an object. At the moment the relation
3078 ············//lists or elements are cached for restore, if an object is marked dirty.
3079 ············//Thus we have to mark dirty our parent object in any case at the moment.
3080 ············if (calledFromStateManager)
3081 ················MarkDirty(pc);
3082
3083 ············// Object can be deleted in an OnDelete-Handler
3084 ············if (child.NDOObjectState == NDOObjectState.Deleted)
3085 ················return;
3086 ············//············Debug.WriteLine("InternalRemoveRelatedObject " + pc.GetType().Name + " " + r.FieldName + " " + child.GetType());
3087 ············// Preconditions
3088 ············// This is called either by DeleteRelatedObjects or by RemoveRelatedObject
3089 ············// The Object state is checked there
3090
3091 ············// If there is a composition in the opposite direction
3092 ············// && the other direction hasn't been processed
3093 ············// throw an exception.
3094 ············// If an exception is thrown at this point, have a look at IsLocked....
3095 ············if (r.Bidirectional && r.ForeignRelation.Composition
3096 ················&& !removeLock.IsLocked(child, r.ForeignRelation, pc))
3097 ················throw new NDOException(82, "Cannot remove related object " + child.GetType().FullName + " from parent " + pc.NDOObjectId.Dump() + ". Object must be removed through the parent.");
3098
3099 ············if (!removeLock.GetLock(pc, r, child))
3100 ················return;
3101
3102 ············try
3103 ············{
3104 ················// must be in this order, since the child
3105 ················// can be deleted in DeleteOrNullRelation
3106 ················//if (changeForeignRelations)
3107 ················DeleteOrNullForeignRelation(pc, r, child);
3108 ················DeleteOrNullRelation(pc, r, child);
3109 ············}
3110 ············finally
3111 ············{
3112 ················removeLock.Unlock(pc, r, child);
3113 ············}
3114
3115 ············this.relationChanges.Add( new RelationChangeRecord( pc, child, r.FieldName, false ) );
3116 ········}
3117
3118 ········private void DeleteRelatedObjects2(IPersistenceCapable pc, Class parentClass, bool checkAssoziations, Relation r)
3119 ········{
3120 ············//············Debug.WriteLine("DeleteRelatedObjects2 " + pc.GetType().Name + " " + r.FieldName);
3121 ············//············Debug.Indent();
3122 ············if (r.Multiplicity == RelationMultiplicity.Element)
3123 ············{
3124 ················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3125 ················if(child != null)
3126 ················{
3127 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3128 ····················//····················{
3129 ····················//························if (!r.ForeignRelation.Composition)
3130 ····················//························{
3131 ····················//····························mappings.SetRelationField(pc, r.FieldName, null);
3132 ····················//····························mappings.SetRelationField(child, r.ForeignRelation.FieldName, null);
3133 ····················//························}
3134 ····················//····························//System.Diagnostics.Debug.WriteLine("Nullen: pc = " + pc.GetType().Name + " child = " + child.GetType().Name);
3135 ····················//························else
3136 ····················//····························throw new NDOException(83, "Can't remove object of type " + pc.GetType().FullName + "; It is contained by an object of type " + child.GetType().FullName);
3137 ····················//····················}
3138 ····················InternalRemoveRelatedObject(pc, r, child, false);
3139 ················}
3140 ············}
3141 ············else
3142 ············{
3143 ················IList list = mappings.GetRelationContainer(pc, r);
3144 ················if(list != null && list.Count > 0)
3145 ················{
3146 ····················//····················if(checkAssoziations && r.Bidirectional && !r.Composition)
3147 ····················//····················{
3148 ····················//························throw new xxNDOException(84, "Cannot delete object " + pc.NDOObjectId + " in an assoziation. Remove related objects first.");
3149 ····················//····················}
3150 ····················// Since RemoveRelatedObjects probably changes the list,
3151 ····················// we iterate through a copy of the list.
3152 ····················ArrayList al = new ArrayList(list);
3153 ····················foreach(IPersistenceCapable relObj in al)
3154 ····················{
3155 ························InternalRemoveRelatedObject(pc, r, relObj, false);
3156 ····················}
3157 ················}
3158 ············}
3159 ············//············Debug.Unindent();
3160 ········}
3161
3162 ········/// <summary>
3163 ········/// Remove all related objects from a parent.
3164 ········/// </summary>
3165 ········/// <param name="pc">the parent object</param>
3166 ········/// <param name="checkAssoziations"></param>
3167 ········private void DeleteRelatedObjects(IPersistenceCapable pc, bool checkAssoziations)
3168 ········{
3169 ············//············Debug.WriteLine("DeleteRelatedObjects " + pc.NDOObjectId.Dump());
3170 ············//············Debug.Indent();
3171 ············// delete all related objects:
3172 ············Class parentClass = GetClass(pc);
3173 ············// Remove Assoziations first
3174 ············foreach(Relation r in parentClass.Relations)
3175 ············{
3176 ················if (!r.Composition)
3177 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3178 ············}
3179 ············foreach(Relation r in parentClass.Relations)
3180 ············{
3181 ················if (r.Composition)
3182 ····················DeleteRelatedObjects2(pc, parentClass, checkAssoziations, r);
3183 ············}
3184
3185 ············//············Debug.Unindent();
3186 ········}
3187
3188 ········/// <summary>
3189 ········/// Delete a list of objects
3190 ········/// </summary>
3191 ········/// <param name="list">the list of objects to remove</param>
3192 ········public void Delete(IList list)
3193 ········{
3194 ············for (int i = 0; i < list.Count; i++)
3195 ············{
3196 ················IPersistenceCapable pc = (IPersistenceCapable) list[i];
3197 ················Delete(pc);
3198 ············}
3199 ········}
3200
3201 ········/// <summary>
3202 ········/// Make object hollow. All relations will be unloaded and object data will be
3203 ········/// newly fetched during the next touch of a persistent field.
3204 ········/// </summary>
3205 ········/// <param name="o"></param>
3206 ········public virtual void MakeHollow(object o)
3207 ········{
3208 ············IPersistenceCapable pc = CheckPc(o);
3209 ············MakeHollow(pc, false);
3210 ········}
3211
3212 ········/// <summary>
3213 ········/// Make the object hollow if it is persistent. Unload all complex data.
3214 ········/// </summary>
3215 ········/// <param name="o"></param>
3216 ········/// <param name="recursive">if true then unload related objects as well</param>
3217 ········public virtual void MakeHollow(object o, bool recursive)
3218 ········{
3219 ············IPersistenceCapable pc = CheckPc(o);
3220 ············if(pc.NDOObjectState == NDOObjectState.Hollow)
3221 ················return;
3222 ············if(pc.NDOObjectState != NDOObjectState.Persistent)
3223 ············{
3224 ················throw new NDOException(85, "MakeHollow: Illegal state for this operation (" + pc.NDOObjectState.ToString() + ")");
3225 ············}
3226 ············pc.NDOObjectState = NDOObjectState.Hollow;
3227 ············MakeRelationsHollow(pc, recursive);
3228 ········}
3229
3230 ········/// <summary>
3231 ········/// Make all objects of a list hollow.
3232 ········/// </summary>
3233 ········/// <param name="list">the list of objects that should be made hollow</param>
3234 ········public virtual void MakeHollow(System.Collections.IList list)
3235 ········{
3236 ············MakeHollow(list, false);
3237 ········}
3238
3239 ········/// <summary>
3240 ········/// Make all objects of a list hollow.
3241 ········/// </summary>
3242 ········/// <param name="list">the list of objects that should be made hollow</param>
3243 ········/// <param name="recursive">if true then unload related objects as well</param>
3244 ········public void MakeHollow(System.Collections.IList list, bool recursive)
3245 ········{
3246 ············foreach (IPersistenceCapable pc in list)
3247 ················MakeHollow(pc, recursive);················
3248 ········}
3249
3250 ········/// <summary>
3251 ········/// Make all unlocked objects in the cache hollow.
3252 ········/// </summary>
3253 ········public virtual void MakeAllHollow()
3254 ········{
3255 ············foreach(var pc in cache.UnlockedObjects)
3256 ············{
3257 ················MakeHollow(pc, false);
3258 ············}
3259 ········}
3260
3261 ········/// <summary>
3262 ········/// Make relations hollow.
3263 ········/// </summary>
3264 ········/// <param name="pc">The parent object</param>
3265 ········/// <param name="recursive">If true, the function unloads related objects as well.</param>
3266 ········private void MakeRelationsHollow(IPersistenceCapable pc, bool recursive)
3267 ········{
3268 ············Class c = GetClass(pc);
3269 ············foreach(Relation r in c.Relations)
3270 ············{
3271 ················if (r.Multiplicity == RelationMultiplicity.Element)
3272 ················{
3273 ····················mappings.SetRelationField(pc, r.FieldName, null);
3274 ····················//····················IPersistenceCapable child = (IPersistenceCapable) mappings.GetRelationField(pc, r.FieldName);
3275 ····················//····················if((null != child) && recursive)
3276 ····················//····················{
3277 ····················//························MakeHollow(child, true);
3278 ····················//····················}
3279 ················}
3280 ················else
3281 ················{
3282 ····················if (!pc.NDOGetLoadState(r.Ordinal))
3283 ························continue;
3284 ····················// Help GC by clearing lists
3285 ····················IList l = mappings.GetRelationContainer(pc, r);
3286 ····················if(l != null)
3287 ····················{
3288 ························if(recursive)
3289 ························{
3290 ····························MakeHollow(l, true);
3291 ························}
3292 ························l.Clear();
3293 ····················}
3294 ····················// Hollow relation
3295 ····················mappings.SetRelationContainer(pc, r, null);
3296 ················}
3297 ············}
3298 ············ClearRelationState(pc);
3299 ········}
3300
3301 ········private void ClearRelationState(IPersistenceCapable pc)
3302 ········{
3303 ············Class cl = GetClass(pc);
3304 ············foreach(Relation r in cl.Relations)
3305 ················pc.NDOSetLoadState(r.Ordinal, false);
3306 ········}
3307
3308 ········private void SetRelationState(IPersistenceCapable pc)
3309 ········{
3310 ············Class cl = GetClass(pc);
3311 ············// Due to a bug in the enhancer the constructors are not always patched right,
3312 ············// so NDOLoadState might be uninitialized
3313 ············if (pc.NDOLoadState == null)
3314 ············{
3315 ················FieldInfo fi = new BaseClassReflector(pc.GetType()).GetField("_ndoLoadState", BindingFlags.Instance | BindingFlags.NonPublic);
3316 ················if (fi == null)
3317 ····················throw new InternalException(3131, "pm.SetRelationState: No FieldInfo for _ndoLoadState");
3318 ················fi.SetValue(pc, new LoadState());
3319 ············}
3320
3321 ············// After serialization the relation load state is null
3322 ············if (pc.NDOLoadState.RelationLoadState == null)
3323 ················pc.NDOLoadState.RelationLoadState = new BitArray(LoadState.RelationLoadStateSize);
3324 ············foreach(Relation r in cl.Relations)
3325 ················pc.NDOSetLoadState(r.Ordinal, true);
3326 ········}
3327
3328 ········/// <summary>
3329 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3330 ········/// </summary>
3331 ········/// <param name="t">The type of the persistent object</param>
3332 ········/// <returns></returns>
3333 ········public IPersistenceCapable CreateObject(Type t)
3334 ········{
3335 ············return (IPersistenceCapable) ActivatorUtilities.CreateInstance( ServiceProvider, t );
3336 ········}
3337
3338 ········/// <summary>
3339 ········/// Creates an object of a given type and resolves constructor parameters using the container.
3340 ········/// </summary>
3341 ········/// <typeparam name="T">The type of the object to create.</typeparam>
3342 ········/// <returns></returns>
3343 ········public T CreateObject<T>()
3344 ········{
3345 ············return (T)CreateObject( typeof( T ) );
3346 ········}
3347
3348 ········/// <summary>
3349 ········/// Gets the requested object. It first builds an ObjectId using the type and the
3350 ········/// key data. Then it uses FindObject to retrieve the object. No database access
3351 ········/// is performed.
3352 ········/// </summary>
3353 ········/// <param name="t">The type of the object to retrieve.</param>
3354 ········/// <param name="keyData">The key value to build the object id.</param>
3355 ········/// <returns>A hollow object</returns>
3356 ········/// <remarks>If the key value is of a wrong type, an exception will be thrown, if the object state changes from hollow to persistent.</remarks>
3357 ········public IPersistenceCapable FindObject(Type t, object keyData)
3358 ········{
3359 ············ObjectId oid = ObjectIdFactory.NewObjectId(t, GetClass(t), keyData, this.typeManager);
3360 ············return FindObject(oid);
3361 ········}
3362
3363 ········/// <summary>
3364 ········/// Finds an object using a short id.
3365 ········/// </summary>
3366 ········/// <param name="encodedShortId"></param>
3367 ········/// <returns></returns>
3368 ········public IPersistenceCapable FindObject(string encodedShortId)
3369 ········{
3370 ············string shortId = encodedShortId.Decode();
3371 ············string[] arr = shortId.Split( '-' );
3372 ············if (arr.Length != 3)
3373 ················throw new ArgumentException( "The format of the string is not valid", "shortId" );
3374 ············Type t = shortId.GetObjectType(this);··// try readable format
3375 ············if (t == null)
3376 ············{
3377 ················int typeCode = 0;
3378 ················if (!int.TryParse( arr[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out typeCode ))
3379 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3380 ················t = this.typeManager[typeCode];
3381 ················if (t == null)
3382 ····················throw new ArgumentException( "The string doesn't represent a loadable type", "shortId" );
3383 ············}
3384
3385 ············Class cls = GetClass( t );
3386 ············if (cls == null)
3387 ················throw new ArgumentException( "The type identified by the string is not persistent or is not managed by the given mapping file", "shortId" );
3388
3389 ············object[] keydata = new object[cls.Oid.OidColumns.Count];
3390 ············string[] oidValues = arr[2].Split( ' ' );
3391
3392 ············int i = 0;
3393 ············foreach (var oidValue in oidValues)
3394 ············{
3395 ················Type oidType = cls.Oid.OidColumns[i].SystemType;
3396 ················if (oidType == typeof( int ))
3397 ················{
3398 ····················int key;
3399 ····················if (!int.TryParse( oidValue, out key ))
3400 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an int value", nameof(encodedShortId) );
3401 ····················if (key > (int.MaxValue >> 1))
3402 ························key = -(int.MaxValue - key);
3403 ····················keydata[i] = key;
3404 ················}
3405 ················else if (oidType == typeof( Guid ))
3406 ················{
3407 ····················Guid key;
3408 ····················if (!Guid.TryParse( oidValue, out key ))
3409 ························throw new ArgumentException( $"The ShortId value at index {i} doesn't contain an Guid value", nameof( encodedShortId ) );
3410 ····················keydata[i] = key;
3411 ················}
3412 ················else if (oidType == typeof( string ))
3413 ················{
3414 ····················keydata[i] = oidValue;
3415 ················}
3416 ················else
3417 ················{
3418 ····················throw new ArgumentException( $"The oid type at index {i} of the persistent type {t} can't be used by a ShortId: {oidType.FullName}", nameof( encodedShortId ) );
3419 ················}
3420
3421 ················i++;
3422 ············}
3423
3424 ············if (keydata.Length == 1)
3425 ················return FindObject( t, keydata[0] );
3426
3427 ············return FindObject( t, keydata );············
3428 ········}
3429
3430 ········/// <summary>
3431 ········/// Gets the requested object. If it is in the cache, the cached object is returned, otherwise, a new (hollow)
3432 ········/// instance of the object is returned. In either case, the DB is not accessed!
3433 ········/// </summary>
3434 ········/// <param name="id">Object id</param>
3435 ········/// <returns>The object to retrieve in hollow state</returns>········
3436 ········public IPersistenceCapable FindObject(ObjectId id)
3437 ········{
3438 ············if(id == null)
3439 ············{
3440 ················throw new ArgumentNullException("id");
3441 ············}
3442
3443 ············if(!id.IsValid())
3444 ············{
3445 ················throw new NDOException(86, "FindObject: Invalid object id. Object does not exist");
3446 ············}
3447
3448 ············IPersistenceCapable pc = cache.GetObject(id);
3449 ············if(pc == null)
3450 ············{
3451 ················pc = CreateObject(id.Id.Type);
3452 ················pc.NDOObjectId = id;
3453 ················pc.NDOStateManager = sm;
3454 ················pc.NDOObjectState = NDOObjectState.Hollow;
3455 ················cache.UpdateCache(pc);
3456 ············}
3457 ············return pc;
3458 ········}
3459
3460
3461 ········/// <summary>
3462 ········/// Reload an object from the database.
3463 ········/// </summary>
3464 ········/// <param name="o">The object to be reloaded.</param>
3465 ········public virtual void Refresh(object o)
3466 ········{
3467 ············IPersistenceCapable pc = CheckPc(o);
3468 ············if(pc.NDOObjectState == NDOObjectState.Transient || pc.NDOObjectState == NDOObjectState.Deleted)
3469 ············{
3470 ················throw new NDOException(87, "Refresh: Illegal state " + pc.NDOObjectState + " for this operation");
3471 ············}
3472
3473 ············if(pc.NDOObjectState == NDOObjectState.Created || pc.NDOObjectState == NDOObjectState.PersistentDirty)
3474 ················return; // Cannot update objects in current transaction
3475
3476 ············MakeHollow(pc);
3477 ············LoadData(pc);
3478 ········}
3479
3480 ········/// <summary>
3481 ········/// Refresh a list of objects.
3482 ········/// </summary>
3483 ········/// <param name="list">The list of objects to be refreshed.</param>
3484 ········public virtual void Refresh(IList list)
3485 ········{
3486 ············foreach (IPersistenceCapable pc in list)
3487 ················Refresh(pc);························
3488 ········}
3489
3490 ········/// <summary>
3491 ········/// Refreshes all unlocked objects in the cache.
3492 ········/// </summary>
3493 ········public virtual void RefreshAll()
3494 ········{
3495 ············Refresh( cache.UnlockedObjects.ToList() );
3496 ········}
3497
3498 ········/// <summary>
3499 ········/// Closes the PersistenceManager and releases all resources.
3500 ········/// </summary>
3501 ········public override void Close()
3502 ········{
3503 ············if (this.isClosing)
3504 ················return;
3505 ············this.isClosing = true;
3506 ············TransactionScope.Dispose();
3507 ············UnloadCache();
3508 ············base.Close();
3509 ········}
3510
3511 ········internal void LogIfVerbose( string msg )
3512 ········{
3513 ············if (Logger != null && Logger.IsEnabled( LogLevel.Debug ))
3514 ················Logger.LogDebug( msg );
3515 ········}
3516
3517
3518 ········#endregion
3519
3520
3521 #region Class extent
3522 ········/// <summary>
3523 ········/// Gets all objects of a given class.
3524 ········/// </summary>
3525 ········/// <param name="t">the type of the class</param>
3526 ········/// <returns>A list of all persistent objects of the given class. Subclasses will not be included in the result set.</returns>
3527 ········public virtual IList GetClassExtent(Type t)
3528 ········{
3529 ············return GetClassExtent(t, true);
3530 ········}
3531
3532 ········/// <summary>
3533 ········/// Gets all objects of a given class.
3534 ········/// </summary>
3535 ········/// <param name="t">The type of the class.</param>
3536 ········/// <param name="hollow">If true, return objects in hollow state instead of persistent state.</param>
3537 ········/// <returns>A list of all persistent objects of the given class.</returns>
3538 ········/// <remarks>Subclasses of the given type are not fetched.</remarks>
3539 ········public virtual IList GetClassExtent(Type t, bool hollow)
3540 ········{
3541 ············IQuery q = NewQuery( t, null, hollow );
3542 ············return q.Execute();
3543 ········}
3544
3545 #endregion
3546
3547 #region Query engine
3548
3549
3550 ········/// <summary>
3551 ········/// Returns a virtual table for Linq queries.
3552 ········/// </summary>
3553 ········/// <typeparam name="T"></typeparam>
3554 ········/// <returns></returns>
3555 ········public VirtualTable<T> Objects<T>() //where T: IPersistenceCapable
3556 ········{
3557 ············return new VirtualTable<T>( this );
3558 ········}
3559
3560 ········
3561 ········/// <summary>
3562 ········/// Suppose, you had a directed 1:n relation from class A to class B. If you load an object of type B,
3563 ········/// a foreign key pointing to a row in the table A is read as part of the B row. But since B doesn't have
3564 ········/// a relation to A the foreign key would get lost if we discard the row after building the B object. To
3565 ········/// not lose the foreign key it will be stored as part of the object.
3566 ········/// </summary>
3567 ········/// <param name="cl"></param>
3568 ········/// <param name="pc"></param>
3569 ········/// <param name="row"></param>
3570 ········void ReadLostForeignKeysFromRow(Class cl, IPersistenceCapable pc, DataRow row)
3571 ········{
3572 ············if (cl.FKColumnNames != null && pc.NDOLoadState != null)
3573 ············{
3574 ················//················Debug.WriteLine("GetLostForeignKeysFromRow " + pc.NDOObjectId.Dump());
3575 ················KeyValueList kvl = new KeyValueList(cl.FKColumnNames.Count());
3576 ················foreach(string colName in cl.FKColumnNames)
3577 ····················kvl.Add(new KeyValuePair(colName, row[colName]));
3578 ················pc.NDOLoadState.LostRowInfo = kvl;················
3579 ············}
3580 ········}
3581
3582 ········/// <summary>
3583 ········/// Writes information into the data row, which cannot be stored in the object.
3584 ········/// </summary>
3585 ········/// <param name="cl"></param>
3586 ········/// <param name="pc"></param>
3587 ········/// <param name="row"></param>
3588 ········protected virtual void WriteLostForeignKeysToRow(Class cl, IPersistenceCapable pc, DataRow row)
3589 ········{
3590 ············if (cl.FKColumnNames != null)
3591 ············{
3592 ················//················Debug.WriteLine("WriteLostForeignKeys " + pc.NDOObjectId.Dump());
3593 ················KeyValueList kvl = (KeyValueList)pc.NDOLoadState.LostRowInfo;
3594 ················if (kvl == null)
3595 ····················throw new NDOException(88, "Can't find foreign keys for the relations of the object " + pc.NDOObjectId.Dump());
3596 ················foreach (KeyValuePair pair in kvl)
3597 ····················row[(string) pair.Key] = pair.Value;
3598 ············}
3599 ········}
3600
3601 ········void Row2Object(Class cl, IPersistenceCapable pc, DataRow row)
3602 ········{
3603 ············ReadObject(pc, row, cl.ColumnNames, 0);
3604 ············ReadTimeStamp(cl, pc, row);
3605 ············ReadLostForeignKeysFromRow(cl, pc, row);
3606 ············LoadRelated1To1Objects(pc, row);
3607 ············pc.NDOObjectState = NDOObjectState.Persistent;
3608 ········}
3609
3610
3611 ········/// <summary>
3612 ········/// Convert a data table to objects. Note that the table might only hold objects of the specified type.
3613 ········/// </summary>
3614 ········internal IList DataTableToIList(Type t, ICollection rows, bool hollow)
3615 ········{
3616 ············IList queryResult = GenericListReflector.CreateList(t, rows.Count);
3617 ············if (rows.Count == 0)
3618 ················return queryResult;
3619
3620 ············IList callbackObjects = new ArrayList();
3621 ············Class cl = GetClass(t);
3622 ············if (t.IsGenericTypeDefinition)
3623 ············{
3624 ················if (cl.TypeNameColumn == null)
3625 ····················throw new NDOException(104, "No type name column defined for generic type '" + t.FullName + "'. Check your mapping file.");
3626 ················IEnumerator en = rows.GetEnumerator();
3627 ················en.MoveNext();··// Move to the first element
3628 ················DataRow testrow = (DataRow)en.Current;
3629 ················if (testrow.Table.Columns[cl.TypeNameColumn.Name] == null)
3630 ····················throw new InternalException(3333, "DataTableToIList: TypeNameColumn isn't defined in the schema.");
3631 ············}
3632
3633 ············foreach(DataRow row in rows)
3634 ············{
3635 ················Type concreteType = t;
3636 ················if (t.IsGenericTypeDefinition)··// type information is in the row
3637 ················{
3638 ····················if (row[cl.TypeNameColumn.Name] == DBNull.Value)
3639 ····················{
3640 ························ObjectId tempid = ObjectIdFactory.NewObjectId(t, cl, row, this.typeManager);
3641 ························throw new NDOException(105, "Null entry in the TypeNameColumn of the type '" + t.FullName + "'. Oid = " + tempid.ToString());
3642 ····················}
3643 ····················string typeStr = (string)row[cl.TypeNameColumn.Name];
3644 ····················concreteType = Type.GetType(typeStr);
3645 ····················if (concreteType == null)
3646 ························throw new NDOException(106, "Can't load generic type " + typeStr);
3647 ················}
3648 ················ObjectId id = ObjectIdFactory.NewObjectId(concreteType, cl, row, this.typeManager);
3649 ················IPersistenceCapable pc = cache.GetObject(id);················
3650 ················if(pc == null)
3651 ················{
3652 ····················pc = CreateObject( concreteType );
3653 ····················pc.NDOObjectId = id;
3654 ····················pc.NDOStateManager = sm;
3655 ····················// If the object shouldn't be hollow, this will be overwritten later.
3656 ····················pc.NDOObjectState = NDOObjectState.Hollow;
3657 ················}
3658 ················// If we have found a non hollow object, the time stamp remains the old one.
3659 ················// In every other case we use the new time stamp.
3660 ················// Note, that there could be a hollow object in the cache.
3661 ················// We need the time stamp in hollow objects in order to correctly
3662 ················// delete objects using fake rows.
3663 ················if (pc.NDOObjectState == NDOObjectState.Hollow)
3664 ················{
3665 ····················ReadTimeStamp(cl, pc, row);
3666 ················}
3667 ················if(!hollow && pc.NDOObjectState != NDOObjectState.PersistentDirty)
3668 ················{
3669 ····················Row2Object(cl, pc, row);
3670 ····················if ((pc as IPersistenceNotifiable) != null)
3671 ························callbackObjects.Add(pc);
3672 ················}
3673
3674 ················cache.UpdateCache(pc);
3675 ················queryResult.Add(pc);
3676 ············}
3677 ············// Make shure this is the last statement before returning
3678 ············// to the caller, so the user can recursively use persistent
3679 ············// objects
3680 ············foreach(IPersistenceNotifiable ipn in callbackObjects)
3681 ················ipn.OnLoaded();
3682
3683 ············return queryResult;
3684 ········}
3685
3686 #endregion
3687
3688 #region Cache Management
3689 ········/// <summary>
3690 ········/// Remove all unused entries from the cache.
3691 ········/// </summary>
3692 ········public void CleanupCache()
3693 ········{
3694 ············GC.Collect(GC.MaxGeneration);
3695 ············GC.WaitForPendingFinalizers();
3696 ············cache.Cleanup();
3697 ········}
3698
3699 ········/// <summary>
3700 ········/// Remove all unlocked objects from the cache. Use with care!
3701 ········/// </summary>
3702 ········public void UnloadCache()
3703 ········{
3704 ············cache.Unload();
3705 ········}
3706 #endregion
3707
3708 ········/// <summary>
3709 ········/// Default encryption key for NDO
3710 ········/// </summary>
3711 ········/// <remarks>
3712 ········/// We recommend strongly to use an own encryption key, which can be set with this property.
3713 ········/// </remarks>
3714 ········public virtual byte[] EncryptionKey
3715 ········{
3716 ············get
3717 ············{
3718 ················if (this.encryptionKey == null)
3719 ····················this.encryptionKey = new byte[] { 0x09,0xA2,0x79,0x5C,0x99,0xFF,0xCB,0x8B,0xA3,0x37,0x76,0xC8,0xA6,0x5D,0x6D,0x66,
3720 ······················································0xE2,0x74,0xCF,0xF0,0xF7,0xEA,0xC4,0x82,0x1E,0xD5,0x19,0x4C,0x5A,0xB4,0x89,0x4D };
3721 ················return this.encryptionKey;
3722 ············}
3723 ············set { this.encryptionKey = value; }
3724 ········}
3725
3726 ········/// <summary>
3727 ········/// Hollow mode: If true all objects are made hollow after each transaction.
3728 ········/// </summary>
3729 ········public virtual bool HollowMode
3730 ········{
3731 ············get { return hollowMode; }
3732 ············set { hollowMode = value; }
3733 ········}
3734
3735 ········internal TypeManager TypeManager
3736 ········{
3737 ············get { return typeManager; }
3738 ········}
3739
3740 ········/// <summary>
3741 ········/// Sets or gets transaction mode. Uses TransactionMode enum.
3742 ········/// </summary>
3743 ········/// <remarks>
3744 ········/// Set this value before you start any transactions.
3745 ········/// </remarks>
3746 ········public TransactionMode TransactionMode
3747 ········{
3748 ············get { return TransactionScope.TransactionMode; }
3749 ············set { TransactionScope.TransactionMode = value; }
3750 ········}
3751
3752 ········/// <summary>
3753 ········/// Sets or gets the Isolation Level.
3754 ········/// </summary>
3755 ········/// <remarks>
3756 ········/// Set this value before you start any transactions.
3757 ········/// </remarks>
3758 ········public IsolationLevel IsolationLevel
3759 ········{
3760 ············get { return TransactionScope.IsolationLevel; }
3761 ············set { TransactionScope.IsolationLevel = value; }
3762 ········}
3763
3764 ········internal class MappingTableEntry
3765 ········{
3766 ············private IPersistenceCapable parentObject;
3767 ············private IPersistenceCapable relatedObject;
3768 ············private Relation relation;
3769 ············private bool deleteEntry;
3770 ············
3771 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r) : this(pc, relObj, r, false)
3772 ············{
3773 ············}
3774 ············
3775 ············public MappingTableEntry(IPersistenceCapable pc, IPersistenceCapable relObj, Relation r, bool deleteEntry)
3776 ············{
3777 ················parentObject = pc;
3778 ················relatedObject = relObj;
3779 ················relation = r;
3780 ················this.deleteEntry = deleteEntry;
3781 ············}
3782
3783 ············public bool DeleteEntry
3784 ············{
3785 ················get
3786 ················{
3787 ····················return deleteEntry;
3788 ················}
3789 ············}
3790
3791 ············public IPersistenceCapable ParentObject
3792 ············{
3793 ················get
3794 ················{
3795 ····················return parentObject;
3796 ················}
3797 ············}
3798
3799 ············public IPersistenceCapable RelatedObject
3800 ············{
3801 ················get
3802 ················{
3803 ····················return relatedObject;
3804 ················}
3805 ············}
3806
3807 ············public Relation Relation
3808 ············{
3809 ················get
3810 ················{
3811 ····················return relation;
3812 ················}
3813 ············}
3814 ········}
3815
3816 ········/// <summary>
3817 ········/// Get a DataRow representing the given object.
3818 ········/// </summary>
3819 ········/// <param name="o"></param>
3820 ········/// <returns></returns>
3821 ········public DataRow GetClonedDataRow( object o )
3822 ········{
3823 ············IPersistenceCapable pc = CheckPc( o );
3824
3825 ············if (pc.NDOObjectState == NDOObjectState.Deleted || pc.NDOObjectState == NDOObjectState.Transient)
3826 ················throw new Exception( "GetDataRow: State of the object must not be Deleted or Transient." );
3827
3828 ············DataRow row = cache.GetDataRow( pc );
3829 ············DataTable newTable = row.Table.Clone();
3830 ············newTable.ImportRow( row );
3831 ············row = newTable.Rows[0];
3832
3833 ············Class cls = mappings.FindClass(o.GetType());
3834 ············WriteObject( pc, row, cls.ColumnNames );
3835 ············WriteForeignKeysToRow( pc, row );
3836
3837 ············return row;
3838 ········}
3839
3840 ········/// <summary>
3841 ········/// Gets an object, which shows all changes applied to the given object.
3842 ········/// This function can be used to build an audit system.
3843 ········/// </summary>
3844 ········/// <param name="o"></param>
3845 ········/// <returns>An ExpandoObject</returns>
3846 ········public ChangeLog GetChangeSet( object o )
3847 ········{
3848 ············var changeLog = new ChangeLog(this);
3849 ············changeLog.Initialize( o );
3850 ············return changeLog;
3851 ········}
3852
3853 ········/// <summary>
3854 ········/// Outputs a revision number representing the assembly version.
3855 ········/// </summary>
3856 ········/// <remarks>This can be used for debugging purposes</remarks>
3857 ········public int Revision
3858 ········{
3859 ············get
3860 ············{
3861 ················Version v = new AssemblyName( GetType().Assembly.FullName ).Version;
3862 ················string vstring = String.Format( "{0}{1:D2}{2}{3:D2}", v.Major, v.Minor, v.Build, v.MinorRevision );
3863 ················return int.Parse( vstring );
3864 ············}
3865 ········}
3866 ····}
3867 }
3868