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

Last Commit (6f29331)
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 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
519 System. Attribute a = System. Attribute. GetCustomAttribute( fi, typeof( NDORelationAttribute) , false) ;
520 NDORelationAttribute ra = ( NDORelationAttribute) a;
 
 
 
 
 
 
521
522 ············this.composition = ra != null && (ra.Info & RelationInfo.Composite) != 0;
523
524 ············if (fi.FieldType == typeof(System.Collections.IList) || fi.FieldType.GetInterface("IList") != null || fi.FieldType.FullName.StartsWith("System.Collections.Generic.IList`1"))
525 ············{
526 ················this.multiplicity = RelationMultiplicity.List;
527 ············}
528 else if ( fi. FieldType. GetCustomAttributes( typeof( NDOPersistentAttribute) , false) . Length > 0)
529 ············{
530 ················this.multiplicity = RelationMultiplicity.Element;
531 ············}
532 ············else
533 ············{
534 ················throw new NDOException(111, "Invalid field type for relation " + t.FullName + "." + FieldName + ": Type = " + fi.FieldType.Name);
535 ············}
536
537
538 ············// This could be easier, if we hadn't the choice whether to use
539 ············// polymorphy or not.
540 ············bool cond1 = this.Multiplicity == RelationMultiplicity.Element
541 ················&& this.ForeignKeyTypeColumnName != null;
542 ············bool cond2 = this.Multiplicity == RelationMultiplicity.List
543 ················&& this.MappingTable != null && this.MappingTable.ChildForeignKeyTypeColumnName != null;
544 ············hasSubclasses = (relatedClass.HasSubclasses)
545 ················&& (cond1 || cond2);
546
547
548 ············if (this.multiplicity == RelationMultiplicity.List)
549 ············{
550 if ( ra == null)
551 ····················throw new NDOException(97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}");
552
553 ················if (ra.RelationType == null && fi.FieldType.IsGenericType)
554 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
555 ················else
 
 
 
556 ····················this.referencedType = ra.RelationType;
 
 
557 ················if (referencedType == null)
558 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
559 ············}
560 ············else
561 ············{
562 ················this.referencedType = FieldType;
563 ············}
564
565 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
566 ············{
567 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
568 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
569 ············}
570
571 ············this.definingClass = this.Parent;
572 ············Type bt = this.Parent.SystemType.BaseType;
573 ············
574 ············while ( bt != null && bt.GetInterfaces().Any( i => i.FullName == "NDO.IPersistenceCapable" ) )
575 ············{
576 ················Class pcl = this.Parent.Parent.FindClass(bt);
577 ················if (pcl.FindRelation(this.fieldName) != null)
578 ····················this.definingClass = pcl;
579 ················else
580 ····················break;
581 ················bt = bt.BaseType;
582 ············}
583
584 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
585 ············// because that value would be hard coded into the mapping file.
586 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
587 ············if (this.MappingTable != null)
588 ············{
589 ················// r.ForeignKeyColumns points to the own table.
590 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
591 ····················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 + '.');
592 ················int i = 0;
593 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
594 ················{
595 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
596 ····················if (!isEnhancing && fkColumn.Size == 0)
597 ························fkColumn.Size = oidColumn.Size;
598 ····················fkColumn.SystemType = oidColumn.SystemType;
599 ····················i++;
600 ················}
601 ················);
602
603 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
604 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
605 ····················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() + '.');
606 ················i = 0;
607 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
608 ················{
609 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
610 ····················if (!isEnhancing && fkColumn.Size == 0)
611 ························fkColumn.Size = oidColumn.Size;
612 ····················fkColumn.SystemType = oidColumn.SystemType;
613 ····················i++;
614 ················}
615 ················);
616 ············}
617 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
618 ············{
619 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.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 " + Parent.FullName + "." + this.fieldName + " has a foreign key column count of " + this.foreignKeyColumns.Count + '.');
621 ················int i = 0;
622 ················new ForeignKeyIterator(this).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··// List relation. The foreign key points to the own table.
633 ············{
634 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
635 ····················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 + '.');
636 ················int i = 0;
637 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
638 ················{
639 ····················OidColumn oidColumn = (OidColumn)Parent.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 ········}
648
649 ········/// <summary>
650 ········/// If a relation is bidirectional, this property gets the opposite relation
651 ········/// </summary>
652 ········[Browsable(false)]
653 ········public virtual Relation ForeignRelation
654 ········{
655 ············get
656 ············{
657 ················int status = 0;
658 ················try
659 ················{
660 ····················if (definingClass == null)
661 ························definingClass = this.Parent;
662 ····················status = 1;
663 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
664 ····················{
665 ························foreignRelation = null;
666 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
667 ························status = 2;
668 ························if (null == referencedClass)
669 ························{
670 ····························foreignRelation = null;
671 ························}
672 ························else
673 ························{
674 ····························status = 3;
675 ····························// first check for a relation directing to our class
676 ····························foreach (Relation fr in referencedClass.Relations)
677 ····························{
678 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
679 ································if (null != fr.referencedTypeName
680 ····································&& fr.referencedTypeName == definingClass.FullName
681 ····································&& fr.relationName == this.relationName
682 ····································&& frdefiningClass == this.referencedTypeName)
683 ································{
684 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
685 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
686 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
687 ····································{
688 ········································foreignRelation = fr;
689 ········································break;
690 ····································}
691 ································}
692 ····························}
693 ····························status = 4;
694 ····························// now check, if a relation targets our base class
695 ····························if (foreignRelation == null && definingClass != NodeParent)
696 ····························{
697 ································foreach (Relation fr in referencedClass.Relations)
698 ································{
699 ····································if (null != fr.referencedTypeName
700 ········································&& fr.referencedTypeName == definingClass.FullName
701 ········································&& fr.relationName == this.relationName)
702 ····································{
703 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
704 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
705 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
706 ········································{
707 ············································foreignRelation = fr;
708 ············································break;
709 ········································}
710 ····································}
711 ································}
712 ····························}
713 ························}
714 ························status = 5;
715 ························foreignRelationValid = true;
716 ····················}
717 ····················return foreignRelation;
718 ················}
719 ················catch (Exception ex)
720 ················{
721 ····················throw new MappingException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
722 ················}
723 ············}
724 ········}
725
726 ········/// <summary>
727 ········/// String representation of the relation for debugging and tracing purposes
728 ········/// </summary>
729 ········/// <returns>A string representation of the Relation object</returns>
730 ········public override string ToString()
731 ········{
732 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
733 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
734 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
735 ········}
736
737 ········int hashCode = 0;
738 ········///<inheritdoc/>
739 ········public override int GetHashCode()
740 ········{
741 ············// This is a hack, because data binding to a property grid
742 ············// asks for the hash code. Since the binding occurs in the mapping tool
743 ············// with uninitialized definingClass and SystemType members
744 ············// we just return the hash code of System.Object.
745 ············if (definingClass == null || definingClass.SystemType == null)
746 ················return base.GetHashCode();
747
748 ············if (this.hashCode == 0)
749 ············{
750 ················int v1 = definingClass.SystemType.GetHashCode();
751 ················int v2 = this.referencedType.GetHashCode();
752 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
753 ············}
754 ············return hashCode;
755 ········}
756
757 ········///<inheritdoc/>
758 ········public override bool Equals(object obj)
759 ········{
760 ············if (definingClass == null)
761 ················return base.Equals(obj);
762
763 ············Relation r = obj as Relation;
764 ············if (r == null)
765 ················return false;
766 ············if (r.GetHashCode() == this.GetHashCode()
767 ················&& r.relationName == this.relationName)
768 ············{
769 ················if (r.definingClass == this.definingClass
770 ····················&& r.referencedType == this.referencedType)
771 ····················return true;
772 ················if (this.Bidirectional && r.Bidirectional)
773 ················{
774 ····················if (this.ForeignRelation.definingClass == r.definingClass
775 ························&& this.ForeignRelation.referencedType == r.referencedType)
776 ························return true;
777 ····················if (r.ForeignRelation.definingClass == this.definingClass
778 ························&& r.ForeignRelation.referencedType == this.referencedType)
779 ························return true;
780 ················}
781 ············}
782 ············return false;
783 ········}
784
785
786 ········#region IComparable Member
787
788 ········///<inheritdoc/>
789 ········public int CompareTo(object obj)
790 ········{
791 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
792 ········}
793
794 ········#endregion
795 ····}
796 }
797
New Commit (4666cb7)
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