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 |