Datei: NDO.Mapping/NDO.Mapping/Relation.cs

Last Commit (a5a7f46)
1 //
2 // Copyright (c) 2002-2025 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 NDO.Mapping.Attributes;
 
24 using System;
25 using System.Linq;
26 using System.Collections.Generic;
27 using System.ComponentModel;
28 using System.Diagnostics;
29 using System.Reflection;
30 using System.Xml;
31
32 namespace NDO.Mapping
33 {
34 ····/// <summary>
35 ····/// This class encapsulates a relation between classes
36 ····/// </summary>
37 ····/// <remarks>This class is equivalent to the Relation element of the mapping file schema.</remarks>
38 ····public class Relation : MappingNode, IFieldInitializer, ILoadStateSupport, IComparable
39 ····{
40 ········#region State variables and accessors
41 ········/// <summary>
42 ········/// Parent class of relation
43 ········/// </summary>
44 ········[Browsable(false)]
45 ········public Class Parent
46 ········{
47 ············get { return NodeParent as Class; }
48 ········}
49
50 ········/// <summary>
51 ········/// Removes the Relation object from the Relation object list of the parent object.
52 ········/// </summary>
53 ········public override void Remove()
54 ········{
55 ············Parent.RemoveRelation(this);
56 ········}
57
58 ········/// <summary>
59 ········/// Field name of relation.
60 ········/// </summary>
61 ········[ReadOnly(true), Description("Field name of relation.")]
62 ········public string FieldName
63 ········{
64 ············get { return fieldName; }
65 ············set { fieldName = value; this.Changed = true; }
66 ········}
67 ········private string fieldName;
68
69 ········/// <summary>
70 ········/// Field name of relation.
71 ········/// </summary>
72 ········[Description("Field name of relation.")]
73 ········public string AccessorName
74 ········{
75 ············get { return accessorName; }
76 ············set { accessorName = value; this.Changed = true; }
77 ········}
78 ········private string accessorName;
79
80 ········/// <summary>
81 ········/// Field type of relation.
82 ········/// </summary>
83 ········[Browsable(false)]
84 ········public Type FieldType
85 ········{
86 ············get { return fieldType; }
87 ············set { fieldType = value; }
88 ········}
89 ········private Type fieldType;
90
91 ········/// <summary>
92 ········/// Name of the referenced type.
93 ········/// </summary>
94 ········[ReadOnly(true), Description("Name of the referenced type.")]
95 ········public string ReferencedTypeName
96 ········{
97 ············get { return referencedTypeName; }
98 ············set { referencedTypeName = value; this.Changed = true; }
99 ········}
100 ········private string referencedTypeName;
101
102 ········/// <summary>
103 ········/// Type of referenced class. For 1:1 relations is the same type as the field type.
104 ········/// This field is initialized by NDO while constructing the PersistenceManger.
105 ········/// </summary>
106 ········[Browsable(false)]
107 ········public Type ReferencedType
108 ········{
109 ············get { return referencedType; }
110 ············set { referencedType = value; }
111 ········}
112 ········private Type referencedType;
113
114 ········List<ForeignKeyColumn> foreignKeyColumns = new List<ForeignKeyColumn>();
115 ········/// <summary>
116 ········/// The foreign key columns of the relation.
117 ········/// </summary>
118 ········/// <remarks>
119 ········/// Under normal circumstances this collection contains one Column
120 ········/// definition. But if the related class has a MultiColumn oid the
121 ········/// relation needs more than one column to store the information needed to
122 ········/// denote the related column. The order of the foreignKeyColums must match
123 ········/// the order of the according Oid colums in the related class.
124 ········/// </remarks>
125 ········[Browsable(false)]
126 ········public IEnumerable<ForeignKeyColumn> ForeignKeyColumns
127 ········{
128 ············get { return this.foreignKeyColumns; }
129 ········}
130
131 ········/// <summary>
132 ········/// Adds a new ForeignKeyColumn to the relation.
133 ········/// </summary>
134 ········/// <returns></returns>
135 ········/// <remarks>
136 ········/// Used by the enhancer and the mapping tool.
137 ········/// </remarks>
138 ········public ForeignKeyColumn NewForeignKeyColumn()
139 ········{
140 ············ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
141 ············this.foreignKeyColumns.Add(fkColumn);
142 ············return fkColumn;
143 ········}
144
145 ········/// <summary>
146 ········/// Removes a foreign key column from the relation.
147 ········/// </summary>
148 ········/// <param name="fkc"></param>
149 ········public void RemoveForeignKeyColumn(ForeignKeyColumn fkc)
150 ········{
151 ············this.foreignKeyColumns.Remove(fkc);
152 ········}
153
154 ········/// <summary>
155 ········/// Name of the foreign key type column in data row. The type of this column is always "int".
156 ········/// </summary>
157 ········[Description("Name of the foreign key type column.")]
158 ········public string ForeignKeyTypeColumnName
159 ········{
160 ············get { return foreignKeyTypeColumnName; }
161 ············set { foreignKeyTypeColumnName = value; this.Changed = true; }
162 ········}
163 ········private string foreignKeyTypeColumnName;
164
165
166 #if nix
167 ········/// <summary>
168 ········/// Name of the foreign key column.
169 ········/// </summary>
170 ········/// <remarks>
171 ········/// In 1:1 relations the column resides in datarows representing objects of the own class, pointing to rows, representing the related class.
172 ········/// In 1:n relations the column resides in datarows representing objects of the related class, pointing to rows, representing the own class.
173 ········/// If a mapping table is used, the column resides in the mapping table, pointing to rows representing the own class.
174 ········/// </remarks>
175 ········[Description("Name of the foreign key column.")]
176 ········public string ForeignKeyColumnName
177 ········{
178 ············get { return foreignKeyColumnName; }
179 ············set { foreignKeyColumnName = value; this.Changed = true; }
180 ········}
181 ········private string foreignKeyColumnName;
182
183 #endif
184 ········/// <summary>
185 ········/// Optional name of relation. Used to distinguish between several relations to the same type.
186 ········/// </summary>
187 ········[Description("Used to distinguish between several relations to the same type.")]
188 ········public string RelationName
189 ········{
190 ············get { return relationName; }
191 ············set { relationName = value; this.Changed = true; }
192 ········}
193 ········private string relationName;
194
195 ········/// <summary>
196 ········/// Optional mapping table
197 ········/// </summary>
198 ········[Browsable(false)]
199 ········public MappingTable MappingTable
200 ········{
201 ············get { return mappingTable; }
202 ············set { mappingTable = value; this.Changed = true; }
203 ········}
204 ········private MappingTable mappingTable;
205
206 ········/// <summary>
207 ········/// Relation type: Assoziation == false, Composition == true
208 ········/// This field is only initialized, if used by the NDO framework.
209 ········/// </summary>
210 ········[Browsable(false)]
211 ········public bool Composition
212 ········{
213 ············get { return composition; }
214 ············set { composition = value; }
215 ········}
216 ········private bool composition;
217
218 ········/// <summary>
219 ········/// True, if we have a polymorphic relation
220 ········/// This field is only initialized, if used by the NDO framework.
221 ········/// </summary>
222 ········[Browsable(false)]
223 ········public bool HasSubclasses
224 ········{
225 ············get { return hasSubclasses; }
226 ············set { hasSubclasses = value; }
227 ········}
228 ········private bool hasSubclasses;
229
230
231 ········/// <summary>
232 ········/// Relation type: field or element.
233 ········/// This field is only initialized, if used by the NDO framework.
234 ········/// </summary>
235 ········[Browsable(false)]
236 ········public RelationMultiplicity Multiplicity
237 ········{
238 ············get { return multiplicity; }
239 ············set { multiplicity = value; }
240 ········}
241 ········private RelationMultiplicity multiplicity;
242
243 ········/// <summary>
244 ········/// For accessing the relation load state in the objects
245 ········/// </summary>
246 ········internal int Ordinal { get; set; }
247 ········int ILoadStateSupport.Ordinal => Ordinal;
248
249 ········private bool foreignRelationValid;
250 ········private Relation foreignRelation;
251 ········private Class definingClass;
252
253
254 ········#endregion
255
256 ········/// <summary>
257 ········/// Returns a list of the target class and all subclasses of the target class of the relation. This field is initialized by the NDO Framework.
258 ········/// </summary>
259 ········[Browsable(false)]
260 ········public virtual IEnumerable<Class> ReferencedSubClasses
261 ········{
262 ············get
263 ············{
264 ················List<Class> result = new List<Class>();
265 ················Class cl;
266 ················result.Add(cl = this.RelatedClass);
267 ················result.AddRange(cl.Subclasses);
268 ················return result;
269 ············}
270 ········}
271
272 ········/// <summary>
273 ········/// Checks, if all foreign key mapping entries match the oid columns of the target types
274 ········/// </summary>
275 ········public void RemapForeignKeyColumns(ForeignKeyColumnAttribute[] fkAttributes, ChildForeignKeyColumnAttribute[] childFkAttributes)
276 ········{
277 ············bool remap = false;
278 ············Class cl = this.RelatedClass;
279 ············if (this.mappingTable == null && fkAttributes != null)
280 ············{
281 ················if (cl.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
282 ················{
283 ····················remap = true;
284 ················}
285 ················else
286 ················{
287 ····················int i = 0;
288 ····················foreach(var fkColumn in this.foreignKeyColumns)
289 ····················{
290 ························fkAttributes[i].SetColumnValues( fkColumn );
291 ····················}
292 ················}
293
294 ················if (!remap)
295 ····················return;
296
297 ················this.foreignKeyColumns.Clear();
298 ················foreach(var attr in fkAttributes)
299 ················{
300 ····················attr.CreateColum( this );
301 ················}
302 ············}
303 ············else
304 ············{
305 ················//Hier muss noch der Mapping Table-Fall erzeugt werden.
306 ············}
307 ········}
308
309 ········/// <summary>
310 ········/// Alter the MappingTable, if there are changed attributes in the mappingTableAttribute
311 ········/// </summary>
312 ········/// <param name="ownTypeIsPoly"></param>
313 ········/// <param name="otherTypeIsPoly"></param>
314 ········/// <param name="mappingTableAttribute"></param>
315 ········public void RemapMappingTable(bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
316 ········{
317 ············if (mappingTableAttribute == null && (foreignRelation == null || foreignRelation.mappingTable == null))
318 ················return;
319
320 ············int pos = referencedTypeName.LastIndexOf( '.' );
321 ············string refShortName = this.referencedTypeName.Substring( pos + 1 );
322 ············refShortName = refShortName.Replace( "`", string.Empty );
323 ············string fullName = Parent.FullName;
324 ············pos = fullName.LastIndexOf( '.' );
325 ············string myShortName = fullName.Substring( pos + 1 );
326 ············myShortName = myShortName.Replace( "`", string.Empty );
327
328 ············if (MappingTable == null)
329 ············{
330 ················AddMappingTable( refShortName, myShortName, otherTypeIsPoly, mappingTableAttribute );
331 ············}
332 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
333 ············{
334 ················MappingTable.TableName = mappingTableAttribute.TableName;
335 ············}
336 ············RemapForeignMappingTable( myShortName, refShortName, ownTypeIsPoly, otherTypeIsPoly, mappingTableAttribute );
337 ········}
338
339 ········internal void RemapForeignMappingTable( string myShortName, string refShortName, bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
340 ········{
341 ············if (this.foreignRelation == null)
342 ················return;
343
344 ············if (foreignRelation.MappingTable == null)
345 ············{
346 ················foreignRelation.AddMappingTable( myShortName, refShortName, ownTypeIsPoly, mappingTableAttribute );
347 ················if (otherTypeIsPoly)
348 ················{
349 ····················foreignRelation.ForeignKeyTypeColumnName = "TC" + refShortName;
350 ················}
351 ············}
352 ············string frFkcName = "ID" + refShortName;··// This is going to be the r.ForeignKeyColumnName of the foreign relation
353 ············string frFtcName = null;
354 ············if (ownTypeIsPoly)
355 ················frFtcName = "TC" + myShortName;·· // This is going to be the r.MappingTable.ChildForeignKeyColumnName of the foreign relation
356 ············if (relationName != string.Empty)
357 ············{
358 ················frFkcName += "_" + relationName;
359 ················if (ownTypeIsPoly)
360 ····················frFtcName += "_" + relationName;
361 ············}
362 ············ForeignKeyColumn forFkColumn = foreignRelation.ForeignKeyColumns.FirstOrDefault();
363 ············forFkColumn.Name = frFkcName;
364 ············foreignRelation.MappingTable.ChildForeignKeyTypeColumnName = frFtcName;
365 ········}
366
367 ········internal void AddMappingTable( string typeShortName1, string typeShortName2, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
368 ········{
369 ············this.MappingTable = new MappingTable( this );
370 ············ForeignKeyColumn fkColumn = this.MappingTable.NewForeignKeyColumn();
371 ············fkColumn.Name = "ID" + typeShortName1;
372 ············if (otherTypeIsPoly)
373 ················this.MappingTable.ChildForeignKeyTypeColumnName = "TC" + typeShortName1;
374 ············if (this.RelationName != null && this.RelationName != string.Empty)
375 ············{
376 ················fkColumn.Name += "_" + this.RelationName;
377 ················if (otherTypeIsPoly)
378 ····················this.MappingTable.ChildForeignKeyTypeColumnName += "_" + this.RelationName;
379 ············}
380
381 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
382 ············{
383 ················this.MappingTable.TableName = mappingTableAttribute.TableName;
384 ············}
385 ············else
386 ············{
387 ················if (typeShortName1.CompareTo( typeShortName2 ) < 0)
388 ····················this.MappingTable.TableName = "rel" + typeShortName1 + typeShortName2;
389 ················else
390 ····················this.MappingTable.TableName = "rel" + typeShortName2 + typeShortName1;
391 ············}
392 ············this.MappingTable.ConnectionId = ((Connection) Parent.Parent.Connections.First()).ID;
393 ········}
394
395
396 ········#region Constructors and Save function
397 ········/// <summary>
398 ········/// Constructs a new Relation object
399 ········/// </summary>
400 ········public Relation(Class cl)
401 ············: base(cl)
402 ········{
403 ············// fields initialized with null are optional
404 ············// fields initialized with "" are required
405 ············fieldName = "";
406 ············mappingTable = null;
407 ············referencedTypeName = "";
408 ············relationName = "";
409 ········}
410
411 ········internal Relation(XmlNode relNode, Class parent)
412 ············: base(relNode, parent)
413 ········{
414 ············fieldName = relNode.Attributes["FieldName"].Value;
415 ············referencedTypeName = relNode.Attributes["ReferencedTypeName"].Value;
416 ············if (relNode.Attributes["AccessorName"] != null)
417 ················this.accessorName = relNode.Attributes["AccessorName"].Value;
418
419 ············if (relNode.Attributes["ForeignKeyTypeColumnName"] != null && relNode.Attributes["ForeignKeyTypeColumnName"].Value != string.Empty)
420 ················this.foreignKeyTypeColumnName = relNode.Attributes["ForeignKeyTypeColumnName"].Value;
421
422 ············if (relNode.Attributes["ForeignKeyColumnName"] != null) // Old mapping
423 ············{
424 ················ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
425 ················fkColumn.Name = relNode.Attributes["ForeignKeyColumnName"].Value;
426 ················this.foreignKeyColumns.Add(fkColumn);
427 ············}
428 ············else
429 ············{
430 ················XmlNodeList nl = relNode.SelectNodes(parent.Parent.selectForeignKeyColumns);
431 ················foreach (XmlNode fkcNode in nl)
432 ····················this.foreignKeyColumns.Add(new ForeignKeyColumn(fkcNode, this));
433 ············}
434
435 ············XmlNode mtNode = relNode.SelectSingleNode(Parent.Parent.selectMappingTable, Parent.Parent.nsmgr);
436 ············if (null != mtNode)
437 ················mappingTable = new MappingTable(mtNode, this);
438 ············if (null != relNode.Attributes["RelationName"])
439 ················relationName = relNode.Attributes["RelationName"].Value;
440 ············else
441 ················relationName = string.Empty;
442 ········}
443
444 ········internal void Save(XmlNode parentNode)
445 ········{
446 ············XmlElement relNode = parentNode.OwnerDocument.CreateElement("Relation");
447 ············parentNode.AppendChild(relNode);
448 ············base.SaveProperties(relNode);
449 ············relNode.SetAttribute("FieldName", this.fieldName);
450 ············if (!String.IsNullOrEmpty( this.accessorName ))
451 ················relNode.SetAttribute("AccessorName", this.accessorName);
452
453 ············if (!string.IsNullOrEmpty(foreignKeyTypeColumnName))
454 ················relNode.SetAttribute("ForeignKeyTypeColumnName", foreignKeyTypeColumnName);
455
456 #if nix
457 ············relNode.SetAttribute("ForeignKeyColumnName", foreignKeyColumnName);
458 #endif
459 ············foreach (ForeignKeyColumn fkColumn in this.foreignKeyColumns)
460 ················fkColumn.Save(relNode);
461
462 ············relNode.SetAttribute("ReferencedTypeName", referencedTypeName);
463 ············relNode.SetAttribute("RelationName", relationName);
464
465 ············if (null != mappingTable)
466 ················mappingTable.Save(relNode);
467 ········}
468
469 ········#endregion
470
471 ········/// <summary>
472 ········/// Determines, if a relation is bidirectional
473 ········/// </summary>
474 ········public virtual bool Bidirectional
475 ········{
476 ············get { return ForeignRelation != null; }
477 ········}
478
479 ········Class RelatedClass
480 ········{
481 ············get
482 ············{
483 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
484 ················if (relatedClass == null)
485 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
486 ················return relatedClass;
487 ············}
488 ········}
489
490 ········void IFieldInitializer.SetOrdinal( int ordinal )
491 ········{
492 ············Ordinal = ordinal;
493 ········}
494
495 ········void IFieldInitializer.InitFields()
496 ········{
497 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
498
499 ············Class relatedClass = this.RelatedClass;
500
501 ············Type t = Parent.SystemType;
502
503 ············if (t == null)
504 ················throw new MappingException(1155, "Relation.InitFields");
505
506 ············FieldInfo fi = null;
507
508 ············while (fi == null && t != typeof(object))
509 ············{
510 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
511 ················if (fi == null)
512 ····················t = t.BaseType;
513 ············}
514 ············if (fi == null)
515 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
516
517 ············FieldType = fi.FieldType;
518 ············var assys = FieldType.Assembly.GetReferencedAssemblies();
519
520 ············NDORelationAttribute ra = null;
521 ············var rax = fi.GetCustomAttributes(false).FirstOrDefault(ca => ca.GetType().Name == "NDORelationAttribute");
522 ············if (rax != null)
523 ············{
524 ················ra = rax as NDORelationAttribute;
525 ················if (ra == null)
526 throw new Exception( "Cant load type NDORelationAttribute" ) ;
 
 
 
 
 
527 ············};
528
529 ············this.composition = ra != null && (ra.Info & RelationInfo.Composite) != 0;
530
531 ············if (fi.FieldType == typeof(System.Collections.IList) || fi.FieldType.GetInterface("IList") != null || fi.FieldType.FullName.StartsWith("System.Collections.Generic.IList`1"))
532 ············{
533 ················this.multiplicity = RelationMultiplicity.List;
534 ············}
535 ············else if (fi.FieldType.GetCustomAttributes(false).Any(ca => ca.GetType().Name == "NDOPersistentAttribute"))
536 ············{
537 ················this.multiplicity = RelationMultiplicity.Element;
538 ············}
539 ············else
540 ············{
541 ················throw new NDOException(111, "Invalid field type for relation " + t.FullName + "." + FieldName + ": Type = " + fi.FieldType.Name);
542 ············}
543
544
545 ············// This could be easier, if we hadn't the choice whether to use
546 ············// polymorphy or not.
547 ············bool cond1 = this.Multiplicity == RelationMultiplicity.Element
548 ················&& this.ForeignKeyTypeColumnName != null;
549 ············bool cond2 = this.Multiplicity == RelationMultiplicity.List
550 ················&& this.MappingTable != null && this.MappingTable.ChildForeignKeyTypeColumnName != null;
551 ············hasSubclasses = (relatedClass.HasSubclasses)
552 ················&& (cond1 || cond2);
553
554
555 ············if (this.multiplicity == RelationMultiplicity.List)
556 ············{
557 ················if (ra?.RelationType == null && fi.FieldType.IsGenericType)
558 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
559 ················else
560 ················{
561 ····················if (ra == null)
562 ························throw new NDOException( 97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}" );
563 ····················this.referencedType = ra.RelationType;
564 ················}
565
566 ················if (referencedType == null)
567 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
568 ············}
569 ············else
570 ············{
571 ················this.referencedType = FieldType;
572 ············}
573
574 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
575 ············{
576 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
577 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
578 ············}
579
580 ············this.definingClass = this.Parent;
581 ············Type bt = this.Parent.SystemType.BaseType;
582 ············
583 ············while ( bt != null && bt.GetInterfaces().Any( i => i.FullName == "NDO.IPersistenceCapable" ) )
584 ············{
585 ················Class pcl = this.Parent.Parent.FindClass(bt);
586 ················if (pcl.FindRelation(this.fieldName) != null)
587 ····················this.definingClass = pcl;
588 ················else
589 ····················break;
590 ················bt = bt.BaseType;
591 ············}
592
593 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
594 ············// because that value would be hard coded into the mapping file.
595 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
596 ············if (this.MappingTable != null)
597 ············{
598 ················// r.ForeignKeyColumns points to the own table.
599 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
600 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + Parent.FullName + " has an oid column count of " + Parent.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
601 ················int i = 0;
602 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
603 ················{
604 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
605 ····················if (!isEnhancing && fkColumn.Size == 0)
606 ························fkColumn.Size = oidColumn.Size;
607 ····················fkColumn.SystemType = oidColumn.SystemType;
608 ····················i++;
609 ················}
610 ················);
611
612 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
613 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
614 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + relatedClass.FullName + " has an oid column count of " + relatedClass.Oid.OidColumns.Count + ". The Relation " + this.Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.mappingTable.ChildForeignKeyColumns.Count() + '.');
615 ················i = 0;
616 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
617 ················{
618 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
619 ····················if (!isEnhancing && fkColumn.Size == 0)
620 ························fkColumn.Size = oidColumn.Size;
621 ····················fkColumn.SystemType = oidColumn.SystemType;
622 ····················i++;
623 ················}
624 ················);
625 ············}
626 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
627 ············{
628 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
629 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + relatedClass.FullName + " has an oid column count of " + relatedClass.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
630 ················int i = 0;
631 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
632 ················{
633 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
634 ····················if (!isEnhancing && fkColumn.Size == 0)
635 ························fkColumn.Size = oidColumn.Size;
636 ····················fkColumn.SystemType = oidColumn.SystemType;
637 ····················i++;
638 ················}
639 ················);
640 ············}
641 ············else··// List relation. The foreign key points to the own table.
642 ············{
643 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
644 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + Parent.FullName + " has an oid column count of " + Parent.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
645 ················int i = 0;
646 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
647 ················{
648 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
649 ····················if (!isEnhancing && fkColumn.Size == 0)
650 ························fkColumn.Size = oidColumn.Size;
651 ····················fkColumn.SystemType = oidColumn.SystemType;
652 ····················i++;
653 ················}
654 ················);
655 ············}
656 ········}
657
658 ········/// <summary>
659 ········/// If a relation is bidirectional, this property gets the opposite relation
660 ········/// </summary>
661 ········[Browsable(false)]
662 ········public virtual Relation ForeignRelation
663 ········{
664 ············get
665 ············{
666 ················int status = 0;
667 ················try
668 ················{
669 ····················if (definingClass == null)
670 ························definingClass = this.Parent;
671 ····················status = 1;
672 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
673 ····················{
674 ························foreignRelation = null;
675 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
676 ························status = 2;
677 ························if (null == referencedClass)
678 ························{
679 ····························foreignRelation = null;
680 ························}
681 ························else
682 ························{
683 ····························status = 3;
684 ····························// first check for a relation directing to our class
685 ····························foreach (Relation fr in referencedClass.Relations)
686 ····························{
687 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
688 ································if (null != fr.referencedTypeName
689 ····································&& fr.referencedTypeName == definingClass.FullName
690 ····································&& fr.relationName == this.relationName
691 ····································&& frdefiningClass == this.referencedTypeName)
692 ································{
693 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
694 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
695 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
696 ····································{
697 ········································foreignRelation = fr;
698 ········································break;
699 ····································}
700 ································}
701 ····························}
702 ····························status = 4;
703 ····························// now check, if a relation targets our base class
704 ····························if (foreignRelation == null && definingClass != NodeParent)
705 ····························{
706 ································foreach (Relation fr in referencedClass.Relations)
707 ································{
708 ····································if (null != fr.referencedTypeName
709 ········································&& fr.referencedTypeName == definingClass.FullName
710 ········································&& fr.relationName == this.relationName)
711 ····································{
712 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
713 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
714 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
715 ········································{
716 ············································foreignRelation = fr;
717 ············································break;
718 ········································}
719 ····································}
720 ································}
721 ····························}
722 ························}
723 ························status = 5;
724 ························foreignRelationValid = true;
725 ····················}
726 ····················return foreignRelation;
727 ················}
728 ················catch (Exception ex)
729 ················{
730 ····················throw new MappingException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
731 ················}
732 ············}
733 ········}
734
735 ········/// <summary>
736 ········/// String representation of the relation for debugging and tracing purposes
737 ········/// </summary>
738 ········/// <returns>A string representation of the Relation object</returns>
739 ········public override string ToString()
740 ········{
741 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
742 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
743 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
744 ········}
745
746 ········int hashCode = 0;
747 ········///<inheritdoc/>
748 ········public override int GetHashCode()
749 ········{
750 ············// This is a hack, because data binding to a property grid
751 ············// asks for the hash code. Since the binding occurs in the mapping tool
752 ············// with uninitialized definingClass and SystemType members
753 ············// we just return the hash code of System.Object.
754 ············if (definingClass == null || definingClass.SystemType == null)
755 ················return base.GetHashCode();
756
757 ············if (this.hashCode == 0)
758 ············{
759 ················int v1 = definingClass.SystemType.GetHashCode();
760 ················int v2 = this.referencedType.GetHashCode();
761 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
762 ············}
763 ············return hashCode;
764 ········}
765
766 ········///<inheritdoc/>
767 ········public override bool Equals(object obj)
768 ········{
769 ············if (definingClass == null)
770 ················return base.Equals(obj);
771
772 ············Relation r = obj as Relation;
773 ············if (r == null)
774 ················return false;
775 ············if (r.GetHashCode() == this.GetHashCode()
776 ················&& r.relationName == this.relationName)
777 ············{
778 ················if (r.definingClass == this.definingClass
779 ····················&& r.referencedType == this.referencedType)
780 ····················return true;
781 ················if (this.Bidirectional && r.Bidirectional)
782 ················{
783 ····················if (this.ForeignRelation.definingClass == r.definingClass
784 ························&& this.ForeignRelation.referencedType == r.referencedType)
785 ························return true;
786 ····················if (r.ForeignRelation.definingClass == this.definingClass
787 ························&& r.ForeignRelation.referencedType == this.referencedType)
788 ························return true;
789 ················}
790 ············}
791 ············return false;
792 ········}
793
794
795 ········#region IComparable Member
796
797 ········///<inheritdoc/>
798 ········public int CompareTo(object obj)
799 ········{
800 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
801 ········}
802
803 ········#endregion
804 ····}
805 }
806
New Commit (b74c314)
1 //
2 // Copyright (c) 2002-2025 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 NDO.Mapping.Attributes;
24 using NDO.Mapping.Serialization;
25 using System;
26 using System.Linq;
27 using System.Collections.Generic;
28 using System.ComponentModel;
29 using System.Diagnostics;
30 using System.Reflection;
31 using System.Xml;
32
33 namespace NDO.Mapping
34 {
35 ····/// <summary>
36 ····/// This class encapsulates a relation between classes
37 ····/// </summary>
38 ····/// <remarks>This class is equivalent to the Relation element of the mapping file schema.</remarks>
39 ····public class Relation : MappingNode, IFieldInitializer, ILoadStateSupport, IComparable
40 ····{
41 ········#region State variables and accessors
42 ········/// <summary>
43 ········/// Parent class of relation
44 ········/// </summary>
45 ········[Browsable(false)]
46 ········public Class Parent
47 ········{
48 ············get { return NodeParent as Class; }
49 ········}
50
51 ········/// <summary>
52 ········/// Removes the Relation object from the Relation object list of the parent object.
53 ········/// </summary>
54 ········public override void Remove()
55 ········{
56 ············Parent.RemoveRelation(this);
57 ········}
58
59 ········/// <summary>
60 ········/// Field name of relation.
61 ········/// </summary>
62 ········[ReadOnly(true), Description("Field name of relation.")]
63 ········public string FieldName
64 ········{
65 ············get { return fieldName; }
66 ············set { fieldName = value; this.Changed = true; }
67 ········}
68 ········private string fieldName;
69
70 ········/// <summary>
71 ········/// Field name of relation.
72 ········/// </summary>
73 ········[Description("Field name of relation.")]
74 ········public string AccessorName
75 ········{
76 ············get { return accessorName; }
77 ············set { accessorName = value; this.Changed = true; }
78 ········}
79 ········private string accessorName;
80
81 ········/// <summary>
82 ········/// Field type of relation.
83 ········/// </summary>
84 ········[Browsable(false)]
85 ········public Type FieldType
86 ········{
87 ············get { return fieldType; }
88 ············set { fieldType = value; }
89 ········}
90 ········private Type fieldType;
91
92 ········/// <summary>
93 ········/// Name of the referenced type.
94 ········/// </summary>
95 ········[ReadOnly(true), Description("Name of the referenced type.")]
96 ········public string ReferencedTypeName
97 ········{
98 ············get { return referencedTypeName; }
99 ············set { referencedTypeName = value; this.Changed = true; }
100 ········}
101 ········private string referencedTypeName;
102
103 ········/// <summary>
104 ········/// Type of referenced class. For 1:1 relations is the same type as the field type.
105 ········/// This field is initialized by NDO while constructing the PersistenceManger.
106 ········/// </summary>
107 ········[Browsable(false)]
108 ········public Type ReferencedType
109 ········{
110 ············get { return referencedType; }
111 ············set { referencedType = value; }
112 ········}
113 ········private Type referencedType;
114
115 ········List<ForeignKeyColumn> foreignKeyColumns = new List<ForeignKeyColumn>();
116 ········/// <summary>
117 ········/// The foreign key columns of the relation.
118 ········/// </summary>
119 ········/// <remarks>
120 ········/// Under normal circumstances this collection contains one Column
121 ········/// definition. But if the related class has a MultiColumn oid the
122 ········/// relation needs more than one column to store the information needed to
123 ········/// denote the related column. The order of the foreignKeyColums must match
124 ········/// the order of the according Oid colums in the related class.
125 ········/// </remarks>
126 ········[Browsable(false)]
127 ········public IEnumerable<ForeignKeyColumn> ForeignKeyColumns
128 ········{
129 ············get { return this.foreignKeyColumns; }
130 ········}
131
132 ········/// <summary>
133 ········/// Adds a new ForeignKeyColumn to the relation.
134 ········/// </summary>
135 ········/// <returns></returns>
136 ········/// <remarks>
137 ········/// Used by the enhancer and the mapping tool.
138 ········/// </remarks>
139 ········public ForeignKeyColumn NewForeignKeyColumn()
140 ········{
141 ············ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
142 ············this.foreignKeyColumns.Add(fkColumn);
143 ············return fkColumn;
144 ········}
145
146 ········/// <summary>
147 ········/// Removes a foreign key column from the relation.
148 ········/// </summary>
149 ········/// <param name="fkc"></param>
150 ········public void RemoveForeignKeyColumn(ForeignKeyColumn fkc)
151 ········{
152 ············this.foreignKeyColumns.Remove(fkc);
153 ········}
154
155 ········/// <summary>
156 ········/// Name of the foreign key type column in data row. The type of this column is always "int".
157 ········/// </summary>
158 ········[Description("Name of the foreign key type column.")]
159 ········public string ForeignKeyTypeColumnName
160 ········{
161 ············get { return foreignKeyTypeColumnName; }
162 ············set { foreignKeyTypeColumnName = value; this.Changed = true; }
163 ········}
164 ········private string foreignKeyTypeColumnName;
165
166
167 #if nix
168 ········/// <summary>
169 ········/// Name of the foreign key column.
170 ········/// </summary>
171 ········/// <remarks>
172 ········/// In 1:1 relations the column resides in datarows representing objects of the own class, pointing to rows, representing the related class.
173 ········/// In 1:n relations the column resides in datarows representing objects of the related class, pointing to rows, representing the own class.
174 ········/// If a mapping table is used, the column resides in the mapping table, pointing to rows representing the own class.
175 ········/// </remarks>
176 ········[Description("Name of the foreign key column.")]
177 ········public string ForeignKeyColumnName
178 ········{
179 ············get { return foreignKeyColumnName; }
180 ············set { foreignKeyColumnName = value; this.Changed = true; }
181 ········}
182 ········private string foreignKeyColumnName;
183
184 #endif
185 ········/// <summary>
186 ········/// Optional name of relation. Used to distinguish between several relations to the same type.
187 ········/// </summary>
188 ········[Description("Used to distinguish between several relations to the same type.")]
189 ········public string RelationName
190 ········{
191 ············get { return relationName; }
192 ············set { relationName = value; this.Changed = true; }
193 ········}
194 ········private string relationName;
195
196 ········/// <summary>
197 ········/// Optional mapping table
198 ········/// </summary>
199 ········[Browsable(false)]
200 ········public MappingTable MappingTable
201 ········{
202 ············get { return mappingTable; }
203 ············set { mappingTable = value; this.Changed = true; }
204 ········}
205 ········private MappingTable mappingTable;
206
207 ········/// <summary>
208 ········/// Relation type: Assoziation == false, Composition == true
209 ········/// This field is only initialized, if used by the NDO framework.
210 ········/// </summary>
211 ········[Browsable(false)]
212 ········public bool Composition
213 ········{
214 ············get { return composition; }
215 ············set { composition = value; }
216 ········}
217 ········private bool composition;
218
219 ········/// <summary>
220 ········/// True, if we have a polymorphic relation
221 ········/// This field is only initialized, if used by the NDO framework.
222 ········/// </summary>
223 ········[Browsable(false)]
224 ········public bool HasSubclasses
225 ········{
226 ············get { return hasSubclasses; }
227 ············set { hasSubclasses = value; }
228 ········}
229 ········private bool hasSubclasses;
230
231
232 ········/// <summary>
233 ········/// Relation type: field or element.
234 ········/// This field is only initialized, if used by the NDO framework.
235 ········/// </summary>
236 ········[Browsable(false)]
237 ········public RelationMultiplicity Multiplicity
238 ········{
239 ············get { return multiplicity; }
240 ············set { multiplicity = value; }
241 ········}
242 ········private RelationMultiplicity multiplicity;
243
244 ········/// <summary>
245 ········/// For accessing the relation load state in the objects
246 ········/// </summary>
247 ········internal int Ordinal { get; set; }
248 ········int ILoadStateSupport.Ordinal => Ordinal;
249
250 ········private bool foreignRelationValid;
251 ········private Relation foreignRelation;
252 ········private Class definingClass;
253
254
255 ········#endregion
256
257 ········/// <summary>
258 ········/// Returns a list of the target class and all subclasses of the target class of the relation. This field is initialized by the NDO Framework.
259 ········/// </summary>
260 ········[Browsable(false)]
261 ········public virtual IEnumerable<Class> ReferencedSubClasses
262 ········{
263 ············get
264 ············{
265 ················List<Class> result = new List<Class>();
266 ················Class cl;
267 ················result.Add(cl = this.RelatedClass);
268 ················result.AddRange(cl.Subclasses);
269 ················return result;
270 ············}
271 ········}
272
273 ········/// <summary>
274 ········/// Checks, if all foreign key mapping entries match the oid columns of the target types
275 ········/// </summary>
276 ········public void RemapForeignKeyColumns(ForeignKeyColumnAttribute[] fkAttributes, ChildForeignKeyColumnAttribute[] childFkAttributes)
277 ········{
278 ············bool remap = false;
279 ············Class cl = this.RelatedClass;
280 ············if (this.mappingTable == null && fkAttributes != null)
281 ············{
282 ················if (cl.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
283 ················{
284 ····················remap = true;
285 ················}
286 ················else
287 ················{
288 ····················int i = 0;
289 ····················foreach(var fkColumn in this.foreignKeyColumns)
290 ····················{
291 ························fkAttributes[i].SetColumnValues( fkColumn );
292 ····················}
293 ················}
294
295 ················if (!remap)
296 ····················return;
297
298 ················this.foreignKeyColumns.Clear();
299 ················foreach(var attr in fkAttributes)
300 ················{
301 ····················attr.CreateColum( this );
302 ················}
303 ············}
304 ············else
305 ············{
306 ················//Hier muss noch der Mapping Table-Fall erzeugt werden.
307 ············}
308 ········}
309
310 ········/// <summary>
311 ········/// Alter the MappingTable, if there are changed attributes in the mappingTableAttribute
312 ········/// </summary>
313 ········/// <param name="ownTypeIsPoly"></param>
314 ········/// <param name="otherTypeIsPoly"></param>
315 ········/// <param name="mappingTableAttribute"></param>
316 ········public void RemapMappingTable(bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
317 ········{
318 ············if (mappingTableAttribute == null && (foreignRelation == null || foreignRelation.mappingTable == null))
319 ················return;
320
321 ············int pos = referencedTypeName.LastIndexOf( '.' );
322 ············string refShortName = this.referencedTypeName.Substring( pos + 1 );
323 ············refShortName = refShortName.Replace( "`", string.Empty );
324 ············string fullName = Parent.FullName;
325 ············pos = fullName.LastIndexOf( '.' );
326 ············string myShortName = fullName.Substring( pos + 1 );
327 ············myShortName = myShortName.Replace( "`", string.Empty );
328
329 ············if (MappingTable == null)
330 ············{
331 ················AddMappingTable( refShortName, myShortName, otherTypeIsPoly, mappingTableAttribute );
332 ············}
333 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
334 ············{
335 ················MappingTable.TableName = mappingTableAttribute.TableName;
336 ············}
337 ············RemapForeignMappingTable( myShortName, refShortName, ownTypeIsPoly, otherTypeIsPoly, mappingTableAttribute );
338 ········}
339
340 ········internal void RemapForeignMappingTable( string myShortName, string refShortName, bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
341 ········{
342 ············if (this.foreignRelation == null)
343 ················return;
344
345 ············if (foreignRelation.MappingTable == null)
346 ············{
347 ················foreignRelation.AddMappingTable( myShortName, refShortName, ownTypeIsPoly, mappingTableAttribute );
348 ················if (otherTypeIsPoly)
349 ················{
350 ····················foreignRelation.ForeignKeyTypeColumnName = "TC" + refShortName;
351 ················}
352 ············}
353 ············string frFkcName = "ID" + refShortName;··// This is going to be the r.ForeignKeyColumnName of the foreign relation
354 ············string frFtcName = null;
355 ············if (ownTypeIsPoly)
356 ················frFtcName = "TC" + myShortName;·· // This is going to be the r.MappingTable.ChildForeignKeyColumnName of the foreign relation
357 ············if (relationName != string.Empty)
358 ············{
359 ················frFkcName += "_" + relationName;
360 ················if (ownTypeIsPoly)
361 ····················frFtcName += "_" + relationName;
362 ············}
363 ············ForeignKeyColumn forFkColumn = foreignRelation.ForeignKeyColumns.FirstOrDefault();
364 ············forFkColumn.Name = frFkcName;
365 ············foreignRelation.MappingTable.ChildForeignKeyTypeColumnName = frFtcName;
366 ········}
367
368 ········internal void AddMappingTable( string typeShortName1, string typeShortName2, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
369 ········{
370 ············this.MappingTable = new MappingTable( this );
371 ············ForeignKeyColumn fkColumn = this.MappingTable.NewForeignKeyColumn();
372 ············fkColumn.Name = "ID" + typeShortName1;
373 ············if (otherTypeIsPoly)
374 ················this.MappingTable.ChildForeignKeyTypeColumnName = "TC" + typeShortName1;
375 ············if (this.RelationName != null && this.RelationName != string.Empty)
376 ············{
377 ················fkColumn.Name += "_" + this.RelationName;
378 ················if (otherTypeIsPoly)
379 ····················this.MappingTable.ChildForeignKeyTypeColumnName += "_" + this.RelationName;
380 ············}
381
382 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
383 ············{
384 ················this.MappingTable.TableName = mappingTableAttribute.TableName;
385 ············}
386 ············else
387 ············{
388 ················if (typeShortName1.CompareTo( typeShortName2 ) < 0)
389 ····················this.MappingTable.TableName = "rel" + typeShortName1 + typeShortName2;
390 ················else
391 ····················this.MappingTable.TableName = "rel" + typeShortName2 + typeShortName1;
392 ············}
393 ············this.MappingTable.ConnectionId = ((Connection) Parent.Parent.Connections.First()).ID;
394 ········}
395
396
397 ········#region Constructors and Save function
398 ········/// <summary>
399 ········/// Constructs a new Relation object
400 ········/// </summary>
401 ········public Relation(Class cl)
402 ············: base(cl)
403 ········{
404 ············// fields initialized with null are optional
405 ············// fields initialized with "" are required
406 ············fieldName = "";
407 ············mappingTable = null;
408 ············referencedTypeName = "";
409 ············relationName = "";
410 ········}
411
412 ········internal Relation(XmlNode relNode, Class parent)
413 ············: base(relNode, parent)
414 ········{
415 ············fieldName = relNode.Attributes["FieldName"].Value;
416 ············referencedTypeName = relNode.Attributes["ReferencedTypeName"].Value;
417 ············if (relNode.Attributes["AccessorName"] != null)
418 ················this.accessorName = relNode.Attributes["AccessorName"].Value;
419
420 ············if (relNode.Attributes["ForeignKeyTypeColumnName"] != null && relNode.Attributes["ForeignKeyTypeColumnName"].Value != string.Empty)
421 ················this.foreignKeyTypeColumnName = relNode.Attributes["ForeignKeyTypeColumnName"].Value;
422
423 ············if (relNode.Attributes["ForeignKeyColumnName"] != null) // Old mapping
424 ············{
425 ················ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
426 ················fkColumn.Name = relNode.Attributes["ForeignKeyColumnName"].Value;
427 ················this.foreignKeyColumns.Add(fkColumn);
428 ············}
429 ············else
430 ············{
431 ················XmlNodeList nl = relNode.SelectNodes(parent.Parent.selectForeignKeyColumns);
432 ················foreach (XmlNode fkcNode in nl)
433 ····················this.foreignKeyColumns.Add(new ForeignKeyColumn(fkcNode, this));
434 ············}
435
436 ············XmlNode mtNode = relNode.SelectSingleNode(Parent.Parent.selectMappingTable, Parent.Parent.nsmgr);
437 ············if (null != mtNode)
438 ················mappingTable = new MappingTable(mtNode, this);
439 ············if (null != relNode.Attributes["RelationName"])
440 ················relationName = relNode.Attributes["RelationName"].Value;
441 ············else
442 ················relationName = string.Empty;
443 ········}
444
445 ········internal void Save(XmlNode parentNode)
446 ········{
447 ············XmlElement relNode = parentNode.OwnerDocument.CreateElement("Relation");
448 ············parentNode.AppendChild(relNode);
449 ············base.SaveProperties(relNode);
450 ············relNode.SetAttribute("FieldName", this.fieldName);
451 ············if (!String.IsNullOrEmpty( this.accessorName ))
452 ················relNode.SetAttribute("AccessorName", this.accessorName);
453
454 ············if (!string.IsNullOrEmpty(foreignKeyTypeColumnName))
455 ················relNode.SetAttribute("ForeignKeyTypeColumnName", foreignKeyTypeColumnName);
456
457 #if nix
458 ············relNode.SetAttribute("ForeignKeyColumnName", foreignKeyColumnName);
459 #endif
460 ············foreach (ForeignKeyColumn fkColumn in this.foreignKeyColumns)
461 ················fkColumn.Save(relNode);
462
463 ············relNode.SetAttribute("ReferencedTypeName", referencedTypeName);
464 ············relNode.SetAttribute("RelationName", relationName);
465
466 ············if (null != mappingTable)
467 ················mappingTable.Save(relNode);
468 ········}
469
470 ········#endregion
471
472 ········/// <summary>
473 ········/// Determines, if a relation is bidirectional
474 ········/// </summary>
475 ········public virtual bool Bidirectional
476 ········{
477 ············get { return ForeignRelation != null; }
478 ········}
479
480 ········Class RelatedClass
481 ········{
482 ············get
483 ············{
484 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
485 ················if (relatedClass == null)
486 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
487 ················return relatedClass;
488 ············}
489 ········}
490
491 ········void IFieldInitializer.SetOrdinal( int ordinal )
492 ········{
493 ············Ordinal = ordinal;
494 ········}
495
496 ········void IFieldInitializer.InitFields()
497 ········{
498 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
499
500 ············Class relatedClass = this.RelatedClass;
501
502 ············Type t = Parent.SystemType;
503
504 ············if (t == null)
505 ················throw new MappingException(1155, "Relation.InitFields");
506
507 ············FieldInfo fi = null;
508
509 ············while (fi == null && t != typeof(object))
510 ············{
511 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
512 ················if (fi == null)
513 ····················t = t.BaseType;
514 ············}
515 ············if (fi == null)
516 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
517
518 ············FieldType = fi.FieldType;
519 ············var assys = FieldType.Assembly.GetReferencedAssemblies();
520
521 ············NDORelationAttribute ra = null;
522 ············var rax = fi.GetCustomAttributes(false).FirstOrDefault(ca => ca.GetType().Name == "NDORelationAttribute");
523 ············if (rax != null)
524 ············{
525 ················ra = rax as NDORelationAttribute;
526 ················if (ra == null)
527 {
528 ····················// The NDORelationAttribute is from an assembly with another version.
529 ····················// This can happen in the context of the NDOEnhancer.
530 ····················// Get the attribute with Serialization/Deserialization
531 ····················ra = rax.ConvertToNdoRelation();
532 ················}
533 ············};
534
535 ············this.composition = ra != null && (ra.Info & RelationInfo.Composite) != 0;
536
537 ············if (fi.FieldType == typeof(System.Collections.IList) || fi.FieldType.GetInterface("IList") != null || fi.FieldType.FullName.StartsWith("System.Collections.Generic.IList`1"))
538 ············{
539 ················this.multiplicity = RelationMultiplicity.List;
540 ············}
541 ············else if (fi.FieldType.GetCustomAttributes(false).Any(ca => ca.GetType().Name == "NDOPersistentAttribute"))
542 ············{
543 ················this.multiplicity = RelationMultiplicity.Element;
544 ············}
545 ············else
546 ············{
547 ················throw new NDOException(111, "Invalid field type for relation " + t.FullName + "." + FieldName + ": Type = " + fi.FieldType.Name);
548 ············}
549
550
551 ············// This could be easier, if we hadn't the choice whether to use
552 ············// polymorphy or not.
553 ············bool cond1 = this.Multiplicity == RelationMultiplicity.Element
554 ················&& this.ForeignKeyTypeColumnName != null;
555 ············bool cond2 = this.Multiplicity == RelationMultiplicity.List
556 ················&& this.MappingTable != null && this.MappingTable.ChildForeignKeyTypeColumnName != null;
557 ············hasSubclasses = (relatedClass.HasSubclasses)
558 ················&& (cond1 || cond2);
559
560
561 ············if (this.multiplicity == RelationMultiplicity.List)
562 ············{
563 ················if (ra?.RelationType == null && fi.FieldType.IsGenericType)
564 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
565 ················else
566 ················{
567 ····················if (ra == null)
568 ························throw new NDOException( 97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}" );
569 ····················this.referencedType = ra.RelationType;
570 ················}
571
572 ················if (referencedType == null)
573 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
574 ············}
575 ············else
576 ············{
577 ················this.referencedType = FieldType;
578 ············}
579
580 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
581 ············{
582 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
583 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
584 ············}
585
586 ············this.definingClass = this.Parent;
587 ············Type bt = this.Parent.SystemType.BaseType;
588 ············
589 ············while ( bt != null && bt.GetInterfaces().Any( i => i.FullName == "NDO.IPersistenceCapable" ) )
590 ············{
591 ················Class pcl = this.Parent.Parent.FindClass(bt);
592 ················if (pcl.FindRelation(this.fieldName) != null)
593 ····················this.definingClass = pcl;
594 ················else
595 ····················break;
596 ················bt = bt.BaseType;
597 ············}
598
599 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
600 ············// because that value would be hard coded into the mapping file.
601 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
602 ············if (this.MappingTable != null)
603 ············{
604 ················// r.ForeignKeyColumns points to the own table.
605 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
606 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + Parent.FullName + " has an oid column count of " + Parent.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
607 ················int i = 0;
608 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
609 ················{
610 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
611 ····················if (!isEnhancing && fkColumn.Size == 0)
612 ························fkColumn.Size = oidColumn.Size;
613 ····················fkColumn.SystemType = oidColumn.SystemType;
614 ····················i++;
615 ················}
616 ················);
617
618 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
619 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
620 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + relatedClass.FullName + " has an oid column count of " + relatedClass.Oid.OidColumns.Count + ". The Relation " + this.Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.mappingTable.ChildForeignKeyColumns.Count() + '.');
621 ················i = 0;
622 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
623 ················{
624 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
625 ····················if (!isEnhancing && fkColumn.Size == 0)
626 ························fkColumn.Size = oidColumn.Size;
627 ····················fkColumn.SystemType = oidColumn.SystemType;
628 ····················i++;
629 ················}
630 ················);
631 ············}
632 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
633 ············{
634 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
635 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + relatedClass.FullName + " has an oid column count of " + relatedClass.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
636 ················int i = 0;
637 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
638 ················{
639 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
640 ····················if (!isEnhancing && fkColumn.Size == 0)
641 ························fkColumn.Size = oidColumn.Size;
642 ····················fkColumn.SystemType = oidColumn.SystemType;
643 ····················i++;
644 ················}
645 ················);
646 ············}
647 ············else··// List relation. The foreign key points to the own table.
648 ············{
649 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
650 ····················throw new NDOException(115, "Column count between relation and Oid doesn't match. Type " + Parent.FullName + " has an oid column count of " + Parent.Oid.OidColumns.Count + ". The Relation " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
651 ················int i = 0;
652 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
653 ················{
654 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
655 ····················if (!isEnhancing && fkColumn.Size == 0)
656 ························fkColumn.Size = oidColumn.Size;
657 ····················fkColumn.SystemType = oidColumn.SystemType;
658 ····················i++;
659 ················}
660 ················);
661 ············}
662 ········}
663
664 ········/// <summary>
665 ········/// If a relation is bidirectional, this property gets the opposite relation
666 ········/// </summary>
667 ········[Browsable(false)]
668 ········public virtual Relation ForeignRelation
669 ········{
670 ············get
671 ············{
672 ················int status = 0;
673 ················try
674 ················{
675 ····················if (definingClass == null)
676 ························definingClass = this.Parent;
677 ····················status = 1;
678 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
679 ····················{
680 ························foreignRelation = null;
681 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
682 ························status = 2;
683 ························if (null == referencedClass)
684 ························{
685 ····························foreignRelation = null;
686 ························}
687 ························else
688 ························{
689 ····························status = 3;
690 ····························// first check for a relation directing to our class
691 ····························foreach (Relation fr in referencedClass.Relations)
692 ····························{
693 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
694 ································if (null != fr.referencedTypeName
695 ····································&& fr.referencedTypeName == definingClass.FullName
696 ····································&& fr.relationName == this.relationName
697 ····································&& frdefiningClass == this.referencedTypeName)
698 ································{
699 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
700 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
701 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
702 ····································{
703 ········································foreignRelation = fr;
704 ········································break;
705 ····································}
706 ································}
707 ····························}
708 ····························status = 4;
709 ····························// now check, if a relation targets our base class
710 ····························if (foreignRelation == null && definingClass != NodeParent)
711 ····························{
712 ································foreach (Relation fr in referencedClass.Relations)
713 ································{
714 ····································if (null != fr.referencedTypeName
715 ········································&& fr.referencedTypeName == definingClass.FullName
716 ········································&& fr.relationName == this.relationName)
717 ····································{
718 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
719 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
720 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
721 ········································{
722 ············································foreignRelation = fr;
723 ············································break;
724 ········································}
725 ····································}
726 ································}
727 ····························}
728 ························}
729 ························status = 5;
730 ························foreignRelationValid = true;
731 ····················}
732 ····················return foreignRelation;
733 ················}
734 ················catch (Exception ex)
735 ················{
736 ····················throw new MappingException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
737 ················}
738 ············}
739 ········}
740
741 ········/// <summary>
742 ········/// String representation of the relation for debugging and tracing purposes
743 ········/// </summary>
744 ········/// <returns>A string representation of the Relation object</returns>
745 ········public override string ToString()
746 ········{
747 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
748 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
749 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
750 ········}
751
752 ········int hashCode = 0;
753 ········///<inheritdoc/>
754 ········public override int GetHashCode()
755 ········{
756 ············// This is a hack, because data binding to a property grid
757 ············// asks for the hash code. Since the binding occurs in the mapping tool
758 ············// with uninitialized definingClass and SystemType members
759 ············// we just return the hash code of System.Object.
760 ············if (definingClass == null || definingClass.SystemType == null)
761 ················return base.GetHashCode();
762
763 ············if (this.hashCode == 0)
764 ············{
765 ················int v1 = definingClass.SystemType.GetHashCode();
766 ················int v2 = this.referencedType.GetHashCode();
767 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
768 ············}
769 ············return hashCode;
770 ········}
771
772 ········///<inheritdoc/>
773 ········public override bool Equals(object obj)
774 ········{
775 ············if (definingClass == null)
776 ················return base.Equals(obj);
777
778 ············Relation r = obj as Relation;
779 ············if (r == null)
780 ················return false;
781 ············if (r.GetHashCode() == this.GetHashCode()
782 ················&& r.relationName == this.relationName)
783 ············{
784 ················if (r.definingClass == this.definingClass
785 ····················&& r.referencedType == this.referencedType)
786 ····················return true;
787 ················if (this.Bidirectional && r.Bidirectional)
788 ················{
789 ····················if (this.ForeignRelation.definingClass == r.definingClass
790 ························&& this.ForeignRelation.referencedType == r.referencedType)
791 ························return true;
792 ····················if (r.ForeignRelation.definingClass == this.definingClass
793 ························&& r.ForeignRelation.referencedType == this.referencedType)
794 ························return true;
795 ················}
796 ············}
797 ············return false;
798 ········}
799
800
801 ········#region IComparable Member
802
803 ········///<inheritdoc/>
804 ········public int CompareTo(object obj)
805 ········{
806 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
807 ········}
808
809 ········#endregion
810 ····}
811 }
812