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

Last Commit (f2128d0)
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, 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
248 ········private bool foreignRelationValid;
249 ········private Relation foreignRelation;
250 ········private Class definingClass;
251
252
253 ········#endregion
254
255 ········/// <summary>
256 ········/// 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.
257 ········/// </summary>
258 ········[Browsable(false)]
259 ········public virtual IEnumerable<Class> ReferencedSubClasses
260 ········{
261 ············get
262 ············{
263 ················List<Class> result = new List<Class>();
264 ················Class cl;
265 ················result.Add(cl = this.RelatedClass);
266 ················result.AddRange(cl.Subclasses);
267 ················return result;
268 ············}
269 ········}
270
271 ········/// <summary>
272 ········/// Checks, if all foreign key mapping entries match the oid columns of the target types
273 ········/// </summary>
274 ········public void RemapForeignKeyColumns(ForeignKeyColumnAttribute[] fkAttributes, ChildForeignKeyColumnAttribute[] childFkAttributes)
275 ········{
276 ············bool remap = false;
277 ············Class cl = this.RelatedClass;
278 ············if (this.mappingTable == null && fkAttributes != null)
279 ············{
280 ················if (cl.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
281 ················{
282 ····················remap = true;
283 ················}
284 ················else
285 ················{
286 ····················int i = 0;
287 ····················foreach(var fkColumn in this.foreignKeyColumns)
288 ····················{
289 ························fkAttributes[i].SetColumnValues( fkColumn );
290 ····················}
291 ················}
292
293 ················if (!remap)
294 ····················return;
295
296 ················this.foreignKeyColumns.Clear();
297 ················foreach(var attr in fkAttributes)
298 ················{
299 ····················attr.CreateColum( this );
300 ················}
301 ············}
302 ············else
303 ············{
304 #warning Hier muss noch der Mapping Table-Fall erzeugt werden.
305 ············}
306 ········}
307
308 ········/// <summary>
309 ········/// Alter the MappingTable, if there are changed attributes in the mappingTableAttribute
310 ········/// </summary>
311 ········/// <param name="ownTypeIsPoly"></param>
312 ········/// <param name="otherTypeIsPoly"></param>
313 ········/// <param name="mappingTableAttribute"></param>
314 ········public void RemapMappingTable(bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
315 ········{
316 ············if (mappingTableAttribute == null && (foreignRelation == null || foreignRelation.mappingTable == null))
317 ················return;
318
319 ············int pos = referencedTypeName.LastIndexOf( '.' );
320 ············string refShortName = this.referencedTypeName.Substring( pos + 1 );
321 ············refShortName = refShortName.Replace( "`", string.Empty );
322 ············string fullName = Parent.FullName;
323 ············pos = fullName.LastIndexOf( '.' );
324 ············string myShortName = fullName.Substring( pos + 1 );
325 ············myShortName = myShortName.Replace( "`", string.Empty );
326
327 ············if (MappingTable == null)
328 ············{
329 ················AddMappingTable( refShortName, myShortName, otherTypeIsPoly, mappingTableAttribute );
330 ············}
331 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
332 ············{
333 ················MappingTable.TableName = mappingTableAttribute.TableName;
334 ············}
335 ············RemapForeignMappingTable( myShortName, refShortName, ownTypeIsPoly, otherTypeIsPoly, mappingTableAttribute );
336 ········}
337
338 ········internal void RemapForeignMappingTable( string myShortName, string refShortName, bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
339 ········{
340 ············if (this.foreignRelation == null)
341 ················return;
342
343 ············if (foreignRelation.MappingTable == null)
344 ············{
345 ················foreignRelation.AddMappingTable( myShortName, refShortName, ownTypeIsPoly, mappingTableAttribute );
346 ················if (otherTypeIsPoly)
347 ················{
348 ····················foreignRelation.ForeignKeyTypeColumnName = "TC" + refShortName;
349 ················}
350 ············}
351 ············string frFkcName = "ID" + refShortName;··// This is going to be the r.ForeignKeyColumnName of the foreign relation
352 ············string frFtcName = null;
353 ············if (ownTypeIsPoly)
354 ················frFtcName = "TC" + myShortName;·· // This is going to be the r.MappingTable.ChildForeignKeyColumnName of the foreign relation
355 ············if (relationName != string.Empty)
356 ············{
357 ················frFkcName += "_" + relationName;
358 ················if (ownTypeIsPoly)
359 ····················frFtcName += "_" + relationName;
360 ············}
361 ············ForeignKeyColumn forFkColumn = foreignRelation.ForeignKeyColumns.FirstOrDefault();
362 ············forFkColumn.Name = frFkcName;
363 ············foreignRelation.MappingTable.ChildForeignKeyTypeColumnName = frFtcName;
364 ········}
365
366 ········internal void AddMappingTable( string typeShortName1, string typeShortName2, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
367 ········{
368 ············this.MappingTable = new MappingTable( this );
369 ············ForeignKeyColumn fkColumn = this.MappingTable.NewForeignKeyColumn();
370 ············fkColumn.Name = "ID" + typeShortName1;
371 ············if (otherTypeIsPoly)
372 ················this.MappingTable.ChildForeignKeyTypeColumnName = "TC" + typeShortName1;
373 ············if (this.RelationName != null && this.RelationName != string.Empty)
374 ············{
375 ················fkColumn.Name += "_" + this.RelationName;
376 ················if (otherTypeIsPoly)
377 ····················this.MappingTable.ChildForeignKeyTypeColumnName += "_" + this.RelationName;
378 ············}
379
380 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
381 ············{
382 ················this.MappingTable.TableName = mappingTableAttribute.TableName;
383 ············}
384 ············else
385 ············{
386 ················if (typeShortName1.CompareTo( typeShortName2 ) < 0)
387 ····················this.MappingTable.TableName = "rel" + typeShortName1 + typeShortName2;
388 ················else
389 ····················this.MappingTable.TableName = "rel" + typeShortName2 + typeShortName1;
390 ············}
391 ············this.MappingTable.ConnectionId = ((Connection) Parent.Parent.Connections.First()).ID;
392 ········}
393
394
395 ········#region Constructors and Save function
396 ········/// <summary>
397 ········/// Constructs a new Relation object
398 ········/// </summary>
399 ········public Relation(Class cl)
400 ············: base(cl)
401 ········{
402 ············// fields initialized with null are optional
403 ············// fields initialized with "" are required
404 ············fieldName = "";
405 ············mappingTable = null;
406 ············referencedTypeName = "";
407 ············relationName = "";
408 ········}
409
410 ········internal Relation(XmlNode relNode, Class parent)
411 ············: base(relNode, parent)
412 ········{
413 ············fieldName = relNode.Attributes["FieldName"].Value;
414 ············referencedTypeName = relNode.Attributes["ReferencedTypeName"].Value;
415 ············if (relNode.Attributes["AccessorName"] != null)
416 ················this.accessorName = relNode.Attributes["AccessorName"].Value;
417
418 ············if (relNode.Attributes["ForeignKeyTypeColumnName"] != null && relNode.Attributes["ForeignKeyTypeColumnName"].Value != string.Empty)
419 ················this.foreignKeyTypeColumnName = relNode.Attributes["ForeignKeyTypeColumnName"].Value;
420
421 ············if (relNode.Attributes["ForeignKeyColumnName"] != null) // Old mapping
422 ············{
423 ················ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
424 ················fkColumn.Name = relNode.Attributes["ForeignKeyColumnName"].Value;
425 ················this.foreignKeyColumns.Add(fkColumn);
426 ············}
427 ············else
428 ············{
429 ················XmlNodeList nl = relNode.SelectNodes(parent.Parent.selectForeignKeyColumns);
430 ················foreach (XmlNode fkcNode in nl)
431 ····················this.foreignKeyColumns.Add(new ForeignKeyColumn(fkcNode, this));
432 ············}
433
434 ············XmlNode mtNode = relNode.SelectSingleNode(Parent.Parent.selectMappingTable, Parent.Parent.nsmgr);
435 ············if (null != mtNode)
436 ················mappingTable = new MappingTable(mtNode, this);
437 ············if (null != relNode.Attributes["RelationName"])
438 ················relationName = relNode.Attributes["RelationName"].Value;
439 ············else
440 ················relationName = string.Empty;
441 ········}
442
443 ········internal void Save(XmlNode parentNode)
444 ········{
445 ············XmlElement relNode = parentNode.OwnerDocument.CreateElement("Relation");
446 ············parentNode.AppendChild(relNode);
447 ············base.SaveProperties(relNode);
448 ············relNode.SetAttribute("FieldName", this.fieldName);
449 ············if (!String.IsNullOrEmpty( this.accessorName ))
450 ················relNode.SetAttribute("AccessorName", this.accessorName);
451
452 ············if (!string.IsNullOrEmpty(foreignKeyTypeColumnName))
453 ················relNode.SetAttribute("ForeignKeyTypeColumnName", foreignKeyTypeColumnName);
454
455 #if nix
456 ············relNode.SetAttribute("ForeignKeyColumnName", foreignKeyColumnName);
457 #endif
458 ············foreach (ForeignKeyColumn fkColumn in this.foreignKeyColumns)
459 ················fkColumn.Save(relNode);
460
461 ············relNode.SetAttribute("ReferencedTypeName", referencedTypeName);
462 ············relNode.SetAttribute("RelationName", relationName);
463
464 ············if (null != mappingTable)
465 ················mappingTable.Save(relNode);
466 ········}
467
468 ········#endregion
469
470 ········/// <summary>
471 ········/// Determines, if a relation is bidirectional
472 ········/// </summary>
473 ········public virtual bool Bidirectional
474 ········{
475 ············get { return ForeignRelation != null; }
476 ········}
477
478 ········Class RelatedClass
479 ········{
480 ············get
481 ············{
482 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
483 ················if (relatedClass == null)
484 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
485 ················return relatedClass;
486 ············}
487 ········}
488
489 ········void IFieldInitializer.SetOrdinal( int ordinal )
490 ········{
491 ············Ordinal = ordinal;
492 ········}
493
494 ········void IFieldInitializer.InitFields()
495 ········{
496 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
497
498 ············Class relatedClass = this.RelatedClass;
499
500 ············Type t = Parent.SystemType;
501
502 ············if (t == null)
503 ················throw new MappingException(1155, "Relation.InitFields");
504
505 ············FieldInfo fi = null;
506
507 ············while (fi == null && t != typeof(object))
508 ············{
509 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
510 ················if (fi == null)
511 ····················t = t.BaseType;
512 ············}
513 ············if (fi == null)
514 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
515
516 ············FieldType = fi.FieldType;
517
518 ············System.Attribute a = System.Attribute.GetCustomAttribute(fi, typeof(NDORelationAttribute), false);
519 ············NDORelationAttribute ra = (NDORelationAttribute)a;
520
521 ············this.composition = ra != null && (ra.Info & RelationInfo.Composite) != 0;
522
523 ············if (fi.FieldType == typeof(System.Collections.IList) || fi.FieldType.GetInterface("IList") != null || fi.FieldType.FullName.StartsWith("System.Collections.Generic.IList`1"))
524 ············{
525 ················this.multiplicity = RelationMultiplicity.List;
526 ············}
527 ············else if (fi.FieldType.GetCustomAttributes(typeof(NDOPersistentAttribute), false).Length > 0)
528 ············{
529 ················this.multiplicity = RelationMultiplicity.Element;
530 ············}
531 ············else
532 ············{
533 ················throw new NDOException(111, "Invalid field type for relation " + t.FullName + "." + FieldName + ": Type = " + fi.FieldType.Name);
534 ············}
535
536
537 ············// This could be easier, if we hadn't the choice whether to use
538 ············// polymorphy or not.
539 ············bool cond1 = this.Multiplicity == RelationMultiplicity.Element
540 ················&& this.ForeignKeyTypeColumnName != null;
541 ············bool cond2 = this.Multiplicity == RelationMultiplicity.List
542 ················&& this.MappingTable != null && this.MappingTable.ChildForeignKeyTypeColumnName != null;
543 ············hasSubclasses = (relatedClass.HasSubclasses)
544 ················&& (cond1 || cond2);
545
546
547 ············if (this.multiplicity == RelationMultiplicity.List)
548 ············{
549 ················if (ra == null)
550 ····················throw new NDOException(97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}");
551
552 ················if (ra.RelationType == null && fi.FieldType.IsGenericType)
553 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
554 ················else
555 ····················this.referencedType = ra.RelationType;
556 ················if (referencedType == null)
557 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
558 ············}
559 ············else
560 ············{
561 ················this.referencedType = FieldType;
562 ············}
563
564 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
565 ············{
566 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
567 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
568 ············}
569
570 ············this.definingClass = this.Parent;
571 ············Type bt = this.Parent.SystemType.BaseType;
572 ············
573 ············while ( bt != null && bt.GetInterfaces().Any( i => i.FullName == "NDO.IPersistenceCapable" ) )
574 ············{
575 ················Class pcl = this.Parent.Parent.FindClass(bt);
576 ················if (pcl.FindRelation(this.fieldName) != null)
577 ····················this.definingClass = pcl;
578 ················else
579 ····················break;
580 ················bt = bt.BaseType;
581 ············}
582
583 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
584 ············// because that value would be hard coded into the mapping file.
585 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
586 ············if (this.MappingTable != null)
587 ············{
588 ················// r.ForeignKeyColumns points to the own table.
589 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
590 ····················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 + '.');
591 ················int i = 0;
592 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
593 ················{
594 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
595 ····················if (!isEnhancing && fkColumn.Size == 0)
596 ························fkColumn.Size = oidColumn.Size;
597 ····················fkColumn.SystemType = oidColumn.SystemType;
598 ····················i++;
599 ················}
600 ················);
601
602 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
603 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
604 ····················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() + '.');
605 ················i = 0;
606 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
607 ················{
608 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
609 ····················if (!isEnhancing && fkColumn.Size == 0)
610 ························fkColumn.Size = oidColumn.Size;
611 ····················fkColumn.SystemType = oidColumn.SystemType;
612 ····················i++;
613 ················}
614 ················);
615 ············}
616 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
617 ············{
618 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
619 ····················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 + '.');
620 ················int i = 0;
621 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
622 ················{
623 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
624 ····················if (!isEnhancing && fkColumn.Size == 0)
625 ························fkColumn.Size = oidColumn.Size;
626 ····················fkColumn.SystemType = oidColumn.SystemType;
627 ····················i++;
628 ················}
629 ················);
630 ············}
631 ············else··// List relation. The foreign key points to the own table.
632 ············{
633 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
634 ····················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 + '.');
635 ················int i = 0;
636 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
637 ················{
638 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
639 ····················if (!isEnhancing && fkColumn.Size == 0)
640 ························fkColumn.Size = oidColumn.Size;
641 ····················fkColumn.SystemType = oidColumn.SystemType;
642 ····················i++;
643 ················}
644 ················);
645 ············}
646 ········}
647
648 ········/// <summary>
649 ········/// If a relation is bidirectional, this property gets the opposite relation
650 ········/// </summary>
651 ········[Browsable(false)]
652 ········public virtual Relation ForeignRelation
653 ········{
654 ············get
655 ············{
656 ················int status = 0;
657 ················try
658 ················{
659 ····················if (definingClass == null)
660 ························definingClass = this.Parent;
661 ····················status = 1;
662 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
663 ····················{
664 ························foreignRelation = null;
665 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
666 ························status = 2;
667 ························if (null == referencedClass)
668 ························{
669 ····························foreignRelation = null;
670 ························}
671 ························else
672 ························{
673 ····························status = 3;
674 ····························// first check for a relation directing to our class
675 ····························foreach (Relation fr in referencedClass.Relations)
676 ····························{
677 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
678 ································if (null != fr.referencedTypeName
679 ····································&& fr.referencedTypeName == definingClass.FullName
680 ····································&& fr.relationName == this.relationName
681 ····································&& frdefiningClass == this.referencedTypeName)
682 ································{
683 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
684 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
685 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
686 ····································{
687 ········································foreignRelation = fr;
688 ········································break;
689 ····································}
690 ································}
691 ····························}
692 ····························status = 4;
693 ····························// now check, if a relation targets our base class
694 ····························if (foreignRelation == null && definingClass != NodeParent)
695 ····························{
696 ································foreach (Relation fr in referencedClass.Relations)
697 ································{
698 ····································if (null != fr.referencedTypeName
699 ········································&& fr.referencedTypeName == definingClass.FullName
700 ········································&& fr.relationName == this.relationName)
701 ····································{
702 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
703 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
704 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
705 ········································{
706 ············································foreignRelation = fr;
707 ············································break;
708 ········································}
709 ····································}
710 ································}
711 ····························}
712 ························}
713 ························status = 5;
714 ························foreignRelationValid = true;
715 ····················}
716 ····················return foreignRelation;
717 ················}
718 ················catch (Exception ex)
719 ················{
720 ····················throw new MappingException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
721 ················}
722 ············}
723 ········}
724
725 ········/// <summary>
726 ········/// String representation of the relation for debugging and tracing purposes
727 ········/// </summary>
728 ········/// <returns>A string representation of the Relation object</returns>
729 ········public override string ToString()
730 ········{
731 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
732 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
733 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
734 ········}
735
736 ········int hashCode = 0;
737 ········///<inheritdoc/>
738 ········public override int GetHashCode()
739 ········{
740 ············// This is a hack, because data binding to a property grid
741 ············// asks for the hash code. Since the binding occurs in the mapping tool
742 ············// with uninitialized definingClass and SystemType members
743 ············// we just return the hash code of System.Object.
744 ············if (definingClass == null || definingClass.SystemType == null)
745 ················return base.GetHashCode();
746
747 ············if (this.hashCode == 0)
748 ············{
749 ················int v1 = definingClass.SystemType.GetHashCode();
750 ················int v2 = this.referencedType.GetHashCode();
751 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
752 ············}
753 ············return hashCode;
754 ········}
755
756 ········///<inheritdoc/>
757 ········public override bool Equals(object obj)
758 ········{
759 ············if (definingClass == null)
760 ················return base.Equals(obj);
761
762 ············Relation r = obj as Relation;
763 ············if (r == null)
764 ················return false;
765 ············if (r.GetHashCode() == this.GetHashCode()
766 ················&& r.relationName == this.relationName)
767 ············{
768 ················if (r.definingClass == this.definingClass
769 ····················&& r.referencedType == this.referencedType)
770 ····················return true;
771 ················if (this.Bidirectional && r.Bidirectional)
772 ················{
773 ····················if (this.ForeignRelation.definingClass == r.definingClass
774 ························&& this.ForeignRelation.referencedType == r.referencedType)
775 ························return true;
776 ····················if (r.ForeignRelation.definingClass == this.definingClass
777 ························&& r.ForeignRelation.referencedType == this.referencedType)
778 ························return true;
779 ················}
780 ············}
781 ············return false;
782 ········}
783
784
785 ········#region IComparable Member
786
787 ········///<inheritdoc/>
788 ········public int CompareTo(object obj)
789 ········{
790 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
791 ········}
792
793 ········#endregion
794 ····}
795 }
796
New Commit (6626c8f)
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, 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
248 ········private bool foreignRelationValid;
249 ········private Relation foreignRelation;
250 ········private Class definingClass;
251
252
253 ········#endregion
254
255 ········/// <summary>
256 ········/// 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.
257 ········/// </summary>
258 ········[Browsable(false)]
259 ········public virtual IEnumerable<Class> ReferencedSubClasses
260 ········{
261 ············get
262 ············{
263 ················List<Class> result = new List<Class>();
264 ················Class cl;
265 ················result.Add(cl = this.RelatedClass);
266 ················result.AddRange(cl.Subclasses);
267 ················return result;
268 ············}
269 ········}
270
271 ········/// <summary>
272 ········/// Checks, if all foreign key mapping entries match the oid columns of the target types
273 ········/// </summary>
274 ········public void RemapForeignKeyColumns(ForeignKeyColumnAttribute[] fkAttributes, ChildForeignKeyColumnAttribute[] childFkAttributes)
275 ········{
276 ············bool remap = false;
277 ············Class cl = this.RelatedClass;
278 ············if (this.mappingTable == null && fkAttributes != null)
279 ············{
280 ················if (cl.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
281 ················{
282 ····················remap = true;
283 ················}
284 ················else
285 ················{
286 ····················int i = 0;
287 ····················foreach(var fkColumn in this.foreignKeyColumns)
288 ····················{
289 ························fkAttributes[i].SetColumnValues( fkColumn );
290 ····················}
291 ················}
292
293 ················if (!remap)
294 ····················return;
295
296 ················this.foreignKeyColumns.Clear();
297 ················foreach(var attr in fkAttributes)
298 ················{
299 ····················attr.CreateColum( this );
300 ················}
301 ············}
302 ············else
303 ············{
304 //Hier muss noch der Mapping Table-Fall erzeugt werden.
305 ············}
306 ········}
307
308 ········/// <summary>
309 ········/// Alter the MappingTable, if there are changed attributes in the mappingTableAttribute
310 ········/// </summary>
311 ········/// <param name="ownTypeIsPoly"></param>
312 ········/// <param name="otherTypeIsPoly"></param>
313 ········/// <param name="mappingTableAttribute"></param>
314 ········public void RemapMappingTable(bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
315 ········{
316 ············if (mappingTableAttribute == null && (foreignRelation == null || foreignRelation.mappingTable == null))
317 ················return;
318
319 ············int pos = referencedTypeName.LastIndexOf( '.' );
320 ············string refShortName = this.referencedTypeName.Substring( pos + 1 );
321 ············refShortName = refShortName.Replace( "`", string.Empty );
322 ············string fullName = Parent.FullName;
323 ············pos = fullName.LastIndexOf( '.' );
324 ············string myShortName = fullName.Substring( pos + 1 );
325 ············myShortName = myShortName.Replace( "`", string.Empty );
326
327 ············if (MappingTable == null)
328 ············{
329 ················AddMappingTable( refShortName, myShortName, otherTypeIsPoly, mappingTableAttribute );
330 ············}
331 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
332 ············{
333 ················MappingTable.TableName = mappingTableAttribute.TableName;
334 ············}
335 ············RemapForeignMappingTable( myShortName, refShortName, ownTypeIsPoly, otherTypeIsPoly, mappingTableAttribute );
336 ········}
337
338 ········internal void RemapForeignMappingTable( string myShortName, string refShortName, bool ownTypeIsPoly, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
339 ········{
340 ············if (this.foreignRelation == null)
341 ················return;
342
343 ············if (foreignRelation.MappingTable == null)
344 ············{
345 ················foreignRelation.AddMappingTable( myShortName, refShortName, ownTypeIsPoly, mappingTableAttribute );
346 ················if (otherTypeIsPoly)
347 ················{
348 ····················foreignRelation.ForeignKeyTypeColumnName = "TC" + refShortName;
349 ················}
350 ············}
351 ············string frFkcName = "ID" + refShortName;··// This is going to be the r.ForeignKeyColumnName of the foreign relation
352 ············string frFtcName = null;
353 ············if (ownTypeIsPoly)
354 ················frFtcName = "TC" + myShortName;·· // This is going to be the r.MappingTable.ChildForeignKeyColumnName of the foreign relation
355 ············if (relationName != string.Empty)
356 ············{
357 ················frFkcName += "_" + relationName;
358 ················if (ownTypeIsPoly)
359 ····················frFtcName += "_" + relationName;
360 ············}
361 ············ForeignKeyColumn forFkColumn = foreignRelation.ForeignKeyColumns.FirstOrDefault();
362 ············forFkColumn.Name = frFkcName;
363 ············foreignRelation.MappingTable.ChildForeignKeyTypeColumnName = frFtcName;
364 ········}
365
366 ········internal void AddMappingTable( string typeShortName1, string typeShortName2, bool otherTypeIsPoly, MappingTableAttribute mappingTableAttribute )
367 ········{
368 ············this.MappingTable = new MappingTable( this );
369 ············ForeignKeyColumn fkColumn = this.MappingTable.NewForeignKeyColumn();
370 ············fkColumn.Name = "ID" + typeShortName1;
371 ············if (otherTypeIsPoly)
372 ················this.MappingTable.ChildForeignKeyTypeColumnName = "TC" + typeShortName1;
373 ············if (this.RelationName != null && this.RelationName != string.Empty)
374 ············{
375 ················fkColumn.Name += "_" + this.RelationName;
376 ················if (otherTypeIsPoly)
377 ····················this.MappingTable.ChildForeignKeyTypeColumnName += "_" + this.RelationName;
378 ············}
379
380 ············if (mappingTableAttribute != null && mappingTableAttribute.TableName != null)
381 ············{
382 ················this.MappingTable.TableName = mappingTableAttribute.TableName;
383 ············}
384 ············else
385 ············{
386 ················if (typeShortName1.CompareTo( typeShortName2 ) < 0)
387 ····················this.MappingTable.TableName = "rel" + typeShortName1 + typeShortName2;
388 ················else
389 ····················this.MappingTable.TableName = "rel" + typeShortName2 + typeShortName1;
390 ············}
391 ············this.MappingTable.ConnectionId = ((Connection) Parent.Parent.Connections.First()).ID;
392 ········}
393
394
395 ········#region Constructors and Save function
396 ········/// <summary>
397 ········/// Constructs a new Relation object
398 ········/// </summary>
399 ········public Relation(Class cl)
400 ············: base(cl)
401 ········{
402 ············// fields initialized with null are optional
403 ············// fields initialized with "" are required
404 ············fieldName = "";
405 ············mappingTable = null;
406 ············referencedTypeName = "";
407 ············relationName = "";
408 ········}
409
410 ········internal Relation(XmlNode relNode, Class parent)
411 ············: base(relNode, parent)
412 ········{
413 ············fieldName = relNode.Attributes["FieldName"].Value;
414 ············referencedTypeName = relNode.Attributes["ReferencedTypeName"].Value;
415 ············if (relNode.Attributes["AccessorName"] != null)
416 ················this.accessorName = relNode.Attributes["AccessorName"].Value;
417
418 ············if (relNode.Attributes["ForeignKeyTypeColumnName"] != null && relNode.Attributes["ForeignKeyTypeColumnName"].Value != string.Empty)
419 ················this.foreignKeyTypeColumnName = relNode.Attributes["ForeignKeyTypeColumnName"].Value;
420
421 ············if (relNode.Attributes["ForeignKeyColumnName"] != null) // Old mapping
422 ············{
423 ················ForeignKeyColumn fkColumn = new ForeignKeyColumn(this);
424 ················fkColumn.Name = relNode.Attributes["ForeignKeyColumnName"].Value;
425 ················this.foreignKeyColumns.Add(fkColumn);
426 ············}
427 ············else
428 ············{
429 ················XmlNodeList nl = relNode.SelectNodes(parent.Parent.selectForeignKeyColumns);
430 ················foreach (XmlNode fkcNode in nl)
431 ····················this.foreignKeyColumns.Add(new ForeignKeyColumn(fkcNode, this));
432 ············}
433
434 ············XmlNode mtNode = relNode.SelectSingleNode(Parent.Parent.selectMappingTable, Parent.Parent.nsmgr);
435 ············if (null != mtNode)
436 ················mappingTable = new MappingTable(mtNode, this);
437 ············if (null != relNode.Attributes["RelationName"])
438 ················relationName = relNode.Attributes["RelationName"].Value;
439 ············else
440 ················relationName = string.Empty;
441 ········}
442
443 ········internal void Save(XmlNode parentNode)
444 ········{
445 ············XmlElement relNode = parentNode.OwnerDocument.CreateElement("Relation");
446 ············parentNode.AppendChild(relNode);
447 ············base.SaveProperties(relNode);
448 ············relNode.SetAttribute("FieldName", this.fieldName);
449 ············if (!String.IsNullOrEmpty( this.accessorName ))
450 ················relNode.SetAttribute("AccessorName", this.accessorName);
451
452 ············if (!string.IsNullOrEmpty(foreignKeyTypeColumnName))
453 ················relNode.SetAttribute("ForeignKeyTypeColumnName", foreignKeyTypeColumnName);
454
455 #if nix
456 ············relNode.SetAttribute("ForeignKeyColumnName", foreignKeyColumnName);
457 #endif
458 ············foreach (ForeignKeyColumn fkColumn in this.foreignKeyColumns)
459 ················fkColumn.Save(relNode);
460
461 ············relNode.SetAttribute("ReferencedTypeName", referencedTypeName);
462 ············relNode.SetAttribute("RelationName", relationName);
463
464 ············if (null != mappingTable)
465 ················mappingTable.Save(relNode);
466 ········}
467
468 ········#endregion
469
470 ········/// <summary>
471 ········/// Determines, if a relation is bidirectional
472 ········/// </summary>
473 ········public virtual bool Bidirectional
474 ········{
475 ············get { return ForeignRelation != null; }
476 ········}
477
478 ········Class RelatedClass
479 ········{
480 ············get
481 ············{
482 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
483 ················if (relatedClass == null)
484 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
485 ················return relatedClass;
486 ············}
487 ········}
488
489 ········void IFieldInitializer.SetOrdinal( int ordinal )
490 ········{
491 ············Ordinal = ordinal;
492 ········}
493
494 ········void IFieldInitializer.InitFields()
495 ········{
496 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
497
498 ············Class relatedClass = this.RelatedClass;
499
500 ············Type t = Parent.SystemType;
501
502 ············if (t == null)
503 ················throw new MappingException(1155, "Relation.InitFields");
504
505 ············FieldInfo fi = null;
506
507 ············while (fi == null && t != typeof(object))
508 ············{
509 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
510 ················if (fi == null)
511 ····················t = t.BaseType;
512 ············}
513 ············if (fi == null)
514 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
515
516 ············FieldType = fi.FieldType;
517
518 ············System.Attribute a = System.Attribute.GetCustomAttribute(fi, typeof(NDORelationAttribute), false);
519 ············NDORelationAttribute ra = (NDORelationAttribute)a;
520
521 ············this.composition = ra != null && (ra.Info & RelationInfo.Composite) != 0;
522
523 ············if (fi.FieldType == typeof(System.Collections.IList) || fi.FieldType.GetInterface("IList") != null || fi.FieldType.FullName.StartsWith("System.Collections.Generic.IList`1"))
524 ············{
525 ················this.multiplicity = RelationMultiplicity.List;
526 ············}
527 ············else if (fi.FieldType.GetCustomAttributes(typeof(NDOPersistentAttribute), false).Length > 0)
528 ············{
529 ················this.multiplicity = RelationMultiplicity.Element;
530 ············}
531 ············else
532 ············{
533 ················throw new NDOException(111, "Invalid field type for relation " + t.FullName + "." + FieldName + ": Type = " + fi.FieldType.Name);
534 ············}
535
536
537 ············// This could be easier, if we hadn't the choice whether to use
538 ············// polymorphy or not.
539 ············bool cond1 = this.Multiplicity == RelationMultiplicity.Element
540 ················&& this.ForeignKeyTypeColumnName != null;
541 ············bool cond2 = this.Multiplicity == RelationMultiplicity.List
542 ················&& this.MappingTable != null && this.MappingTable.ChildForeignKeyTypeColumnName != null;
543 ············hasSubclasses = (relatedClass.HasSubclasses)
544 ················&& (cond1 || cond2);
545
546
547 ············if (this.multiplicity == RelationMultiplicity.List)
548 ············{
549 ················if (ra == null)
550 ····················throw new NDOException(97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}");
551
552 ················if (ra.RelationType == null && fi.FieldType.IsGenericType)
553 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
554 ················else
555 ····················this.referencedType = ra.RelationType;
556 ················if (referencedType == null)
557 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
558 ············}
559 ············else
560 ············{
561 ················this.referencedType = FieldType;
562 ············}
563
564 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
565 ············{
566 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
567 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
568 ············}
569
570 ············this.definingClass = this.Parent;
571 ············Type bt = this.Parent.SystemType.BaseType;
572 ············
573 ············while ( bt != null && bt.GetInterfaces().Any( i => i.FullName == "NDO.IPersistenceCapable" ) )
574 ············{
575 ················Class pcl = this.Parent.Parent.FindClass(bt);
576 ················if (pcl.FindRelation(this.fieldName) != null)
577 ····················this.definingClass = pcl;
578 ················else
579 ····················break;
580 ················bt = bt.BaseType;
581 ············}
582
583 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
584 ············// because that value would be hard coded into the mapping file.
585 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
586 ············if (this.MappingTable != null)
587 ············{
588 ················// r.ForeignKeyColumns points to the own table.
589 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
590 ····················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 + '.');
591 ················int i = 0;
592 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
593 ················{
594 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
595 ····················if (!isEnhancing && fkColumn.Size == 0)
596 ························fkColumn.Size = oidColumn.Size;
597 ····················fkColumn.SystemType = oidColumn.SystemType;
598 ····················i++;
599 ················}
600 ················);
601
602 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
603 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
604 ····················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() + '.');
605 ················i = 0;
606 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
607 ················{
608 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
609 ····················if (!isEnhancing && fkColumn.Size == 0)
610 ························fkColumn.Size = oidColumn.Size;
611 ····················fkColumn.SystemType = oidColumn.SystemType;
612 ····················i++;
613 ················}
614 ················);
615 ············}
616 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
617 ············{
618 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
619 ····················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 + '.');
620 ················int i = 0;
621 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
622 ················{
623 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
624 ····················if (!isEnhancing && fkColumn.Size == 0)
625 ························fkColumn.Size = oidColumn.Size;
626 ····················fkColumn.SystemType = oidColumn.SystemType;
627 ····················i++;
628 ················}
629 ················);
630 ············}
631 ············else··// List relation. The foreign key points to the own table.
632 ············{
633 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
634 ····················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 + '.');
635 ················int i = 0;
636 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
637 ················{
638 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
639 ····················if (!isEnhancing && fkColumn.Size == 0)
640 ························fkColumn.Size = oidColumn.Size;
641 ····················fkColumn.SystemType = oidColumn.SystemType;
642 ····················i++;
643 ················}
644 ················);
645 ············}
646 ········}
647
648 ········/// <summary>
649 ········/// If a relation is bidirectional, this property gets the opposite relation
650 ········/// </summary>
651 ········[Browsable(false)]
652 ········public virtual Relation ForeignRelation
653 ········{
654 ············get
655 ············{
656 ················int status = 0;
657 ················try
658 ················{
659 ····················if (definingClass == null)
660 ························definingClass = this.Parent;
661 ····················status = 1;
662 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
663 ····················{
664 ························foreignRelation = null;
665 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
666 ························status = 2;
667 ························if (null == referencedClass)
668 ························{
669 ····························foreignRelation = null;
670 ························}
671 ························else
672 ························{
673 ····························status = 3;
674 ····························// first check for a relation directing to our class
675 ····························foreach (Relation fr in referencedClass.Relations)
676 ····························{
677 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
678 ································if (null != fr.referencedTypeName
679 ····································&& fr.referencedTypeName == definingClass.FullName
680 ····································&& fr.relationName == this.relationName
681 ····································&& frdefiningClass == this.referencedTypeName)
682 ································{
683 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
684 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
685 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
686 ····································{
687 ········································foreignRelation = fr;
688 ········································break;
689 ····································}
690 ································}
691 ····························}
692 ····························status = 4;
693 ····························// now check, if a relation targets our base class
694 ····························if (foreignRelation == null && definingClass != NodeParent)
695 ····························{
696 ································foreach (Relation fr in referencedClass.Relations)
697 ································{
698 ····································if (null != fr.referencedTypeName
699 ········································&& fr.referencedTypeName == definingClass.FullName
700 ········································&& fr.relationName == this.relationName)
701 ····································{
702 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
703 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
704 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
705 ········································{
706 ············································foreignRelation = fr;
707 ············································break;
708 ········································}
709 ····································}
710 ································}
711 ····························}
712 ························}
713 ························status = 5;
714 ························foreignRelationValid = true;
715 ····················}
716 ····················return foreignRelation;
717 ················}
718 ················catch (Exception ex)
719 ················{
720 ····················throw new MappingException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
721 ················}
722 ············}
723 ········}
724
725 ········/// <summary>
726 ········/// String representation of the relation for debugging and tracing purposes
727 ········/// </summary>
728 ········/// <returns>A string representation of the Relation object</returns>
729 ········public override string ToString()
730 ········{
731 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
732 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
733 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
734 ········}
735
736 ········int hashCode = 0;
737 ········///<inheritdoc/>
738 ········public override int GetHashCode()
739 ········{
740 ············// This is a hack, because data binding to a property grid
741 ············// asks for the hash code. Since the binding occurs in the mapping tool
742 ············// with uninitialized definingClass and SystemType members
743 ············// we just return the hash code of System.Object.
744 ············if (definingClass == null || definingClass.SystemType == null)
745 ················return base.GetHashCode();
746
747 ············if (this.hashCode == 0)
748 ············{
749 ················int v1 = definingClass.SystemType.GetHashCode();
750 ················int v2 = this.referencedType.GetHashCode();
751 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
752 ············}
753 ············return hashCode;
754 ········}
755
756 ········///<inheritdoc/>
757 ········public override bool Equals(object obj)
758 ········{
759 ············if (definingClass == null)
760 ················return base.Equals(obj);
761
762 ············Relation r = obj as Relation;
763 ············if (r == null)
764 ················return false;
765 ············if (r.GetHashCode() == this.GetHashCode()
766 ················&& r.relationName == this.relationName)
767 ············{
768 ················if (r.definingClass == this.definingClass
769 ····················&& r.referencedType == this.referencedType)
770 ····················return true;
771 ················if (this.Bidirectional && r.Bidirectional)
772 ················{
773 ····················if (this.ForeignRelation.definingClass == r.definingClass
774 ························&& this.ForeignRelation.referencedType == r.referencedType)
775 ························return true;
776 ····················if (r.ForeignRelation.definingClass == this.definingClass
777 ························&& r.ForeignRelation.referencedType == this.referencedType)
778 ························return true;
779 ················}
780 ············}
781 ············return false;
782 ········}
783
784
785 ········#region IComparable Member
786
787 ········///<inheritdoc/>
788 ········public int CompareTo(object obj)
789 ········{
790 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
791 ········}
792
793 ········#endregion
794 ····}
795 }
796