Datei: NDODLL/Mapping/Relation.cs

Last Commit (9de9fa9)
1 //
2 // Copyright (c) 2002-2016 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;
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 ········internal void GetOrdinal()
479 ········{
480 ············System.Diagnostics.Debug.Assert(this.Parent.RelationOrdinalBase > -1, "RelationOrdinalBase for type " + Parent.FullName + " not computed");
481 ············Type t = Type.GetType(this.Parent.FullName + ", " + this.Parent.AssemblyName);
482 IMetaClass mc = Metaclasses. GetInnerClass( t) ;
483 ············Ordinal = this.Parent.RelationOrdinalBase + mc.GetRelationOrdinal(this.FieldName);
484 ············if (Ordinal > 63)
485 ················throw new NDOException(18, "Class " + Parent.FullName + " has too much relations.");
486 ········}
487
488 ········Class RelatedClass
489 ········{
490 ············get
491 ············{
492 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
493 ················if (relatedClass == null)
494 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
495 ················return relatedClass;
496 ············}
497 ········}
498
499 ········void IFieldInitializer.InitFields()
500 ········{
501 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
502
503 ············if (!isEnhancing)
504 ················GetOrdinal();
505
506 ············Class relatedClass = this.RelatedClass;
507
508 ············Type t = Parent.SystemType;
509
510 ············if (t == null)
511 ················throw new InternalException(1155, "Relation.InitFields");
512
513 ············FieldInfo fi = null;
514
515 ············while (fi == null && t != typeof(object))
516 ············{
517 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
518 ················if (fi == null)
519 ····················t = t.BaseType;
520 ············}
521 ············if (fi == null)
522 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
523
524 ············FieldType = fi.FieldType;
525
526 ············System.Attribute a = System.Attribute.GetCustomAttribute(fi, typeof(NDORelationAttribute), false);
527 ············NDORelationAttribute ra = (NDORelationAttribute)a;
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(typeof(NDOPersistentAttribute), false).Length > 0)
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 == null)
558 ····················throw new NDOException(97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}");
559
560 ················if (ra.RelationType == null && fi.FieldType.IsGenericType)
561 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
562 ················else
563 ····················this.referencedType = ra.RelationType;
564 ················if (referencedType == null)
565 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
566 ············}
567 ············else
568 ············{
569 ················this.referencedType = FieldType;
570 ············}
571
572 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
573 ············{
574 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
575 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
576 ············}
577
578 ············this.definingClass = this.Parent;
579 ············Type bt = this.Parent.SystemType.BaseType;
580 ············while (typeof(IPersistenceCapable).IsAssignableFrom(bt))
581 ············{
582 ················Class pcl = this.Parent.Parent.FindClass(bt);
583 ················if (pcl.FindRelation(this.fieldName) != null)
584 ····················this.definingClass = pcl;
585 ················else
586 ····················break;
587 ················bt = bt.BaseType;
588 ············}
589
590 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
591 ············// because that value would be hard coded into the mapping file.
592 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
593 ············if (this.MappingTable != null)
594 ············{
595 ················// r.ForeignKeyColumns points to the own table.
596 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
597 ····················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 + '.');
598 ················int i = 0;
599 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
600 ················{
601 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
602 ····················if (!isEnhancing && fkColumn.Size == 0)
603 ························fkColumn.Size = oidColumn.Size;
604 ····················fkColumn.SystemType = oidColumn.SystemType;
605 ····················i++;
606 ················}
607 ················);
608
609 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
610 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
611 ····················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() + '.');
612 ················i = 0;
613 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
614 ················{
615 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
616 ····················if (!isEnhancing && fkColumn.Size == 0)
617 ························fkColumn.Size = oidColumn.Size;
618 ····················fkColumn.SystemType = oidColumn.SystemType;
619 ····················i++;
620 ················}
621 ················);
622 ············}
623 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
624 ············{
625 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
626 ····················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 + '.');
627 ················int i = 0;
628 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
629 ················{
630 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
631 ····················if (!isEnhancing && fkColumn.Size == 0)
632 ························fkColumn.Size = oidColumn.Size;
633 ····················fkColumn.SystemType = oidColumn.SystemType;
634 ····················i++;
635 ················}
636 ················);
637 ············}
638 ············else··// List relation. The foreign key points to the own table.
639 ············{
640 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
641 ····················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 + '.');
642 ················int i = 0;
643 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
644 ················{
645 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
646 ····················if (!isEnhancing && fkColumn.Size == 0)
647 ························fkColumn.Size = oidColumn.Size;
648 ····················fkColumn.SystemType = oidColumn.SystemType;
649 ····················i++;
650 ················}
651 ················);
652 ············}
653 ········}
654
655 ········/// <summary>
656 ········/// If a relation is bidirectional, this property gets the opposite relation
657 ········/// </summary>
658 ········[Browsable(false)]
659 ········public virtual Relation ForeignRelation
660 ········{
661 ············get
662 ············{
663 ················int status = 0;
664 ················try
665 ················{
666 ····················if (definingClass == null)
667 ························definingClass = this.Parent;
668 ····················status = 1;
669 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
670 ····················{
671 ························foreignRelation = null;
672 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
673 ························status = 2;
674 ························if (null == referencedClass)
675 ························{
676 ····························foreignRelation = null;
677 ························}
678 ························else
679 ························{
680 ····························status = 3;
681 ····························// first check for a relation directing to our class
682 ····························foreach (Relation fr in referencedClass.Relations)
683 ····························{
684 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
685 ································if (null != fr.referencedTypeName
686 ····································&& fr.referencedTypeName == definingClass.FullName
687 ····································&& fr.relationName == this.relationName
688 ····································&& frdefiningClass == this.referencedTypeName)
689 ································{
690 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
691 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
692 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
693 ····································{
694 ········································foreignRelation = fr;
695 ········································break;
696 ····································}
697 ································}
698 ····························}
699 ····························status = 4;
700 ····························// now check, if a relation targets our base class
701 ····························if (foreignRelation == null && definingClass != NodeParent)
702 ····························{
703 ································foreach (Relation fr in referencedClass.Relations)
704 ································{
705 ····································if (null != fr.referencedTypeName
706 ········································&& fr.referencedTypeName == definingClass.FullName
707 ········································&& fr.relationName == this.relationName)
708 ····································{
709 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
710 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
711 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
712 ········································{
713 ············································foreignRelation = fr;
714 ············································break;
715 ········································}
716 ····································}
717 ································}
718 ····························}
719 ························}
720 ························status = 5;
721 ························foreignRelationValid = true;
722 ····················}
723 ····················return foreignRelation;
724 ················}
725 ················catch (Exception ex)
726 ················{
727 ····················throw new InternalException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
728 ················}
729 ············}
730 ········}
731
732 ········/// <summary>
733 ········/// String representation of the relation for debugging and tracing purposes
734 ········/// </summary>
735 ········/// <returns>A string representation of the Relation object</returns>
736 ········public override string ToString()
737 ········{
738 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
739 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
740 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
741 ········}
742
743 ········int hashCode = 0;
744 ········///<inheritdoc/>
745 ········public override int GetHashCode()
746 ········{
747 ············// This is a hack, because data binding to a property grid
748 ············// asks for the hash code. Since the binding occurs in the mapping tool
749 ············// with uninitialized definingClass and SystemType members
750 ············// we just return the hash code of System.Object.
751 ············if (definingClass == null || definingClass.SystemType == null)
752 ················return base.GetHashCode();
753
754 ············if (this.hashCode == 0)
755 ············{
756 ················int v1 = definingClass.SystemType.GetHashCode();
757 ················int v2 = this.referencedType.GetHashCode();
758 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
759 ············}
760 ············return hashCode;
761 ········}
762
763 ········///<inheritdoc/>
764 ········public override bool Equals(object obj)
765 ········{
766 ············if (definingClass == null)
767 ················return base.Equals(obj);
768
769 ············Relation r = obj as Relation;
770 ············if (r == null)
771 ················return false;
772 ············if (r.GetHashCode() == this.GetHashCode()
773 ················&& r.relationName == this.relationName)
774 ············{
775 ················if (r.definingClass == this.definingClass
776 ····················&& r.referencedType == this.referencedType)
777 ····················return true;
778 ················if (this.Bidirectional && r.Bidirectional)
779 ················{
780 ····················if (this.ForeignRelation.definingClass == r.definingClass
781 ························&& this.ForeignRelation.referencedType == r.referencedType)
782 ························return true;
783 ····················if (r.ForeignRelation.definingClass == this.definingClass
784 ························&& r.ForeignRelation.referencedType == this.referencedType)
785 ························return true;
786 ················}
787 ············}
788 ············return false;
789 ········}
790
791
792 ········#region IComparable Member
793
794 ········///<inheritdoc/>
795 ········public int CompareTo(object obj)
796 ········{
797 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
798 ········}
799
800 ········#endregion
801 ····}
802 }
803
New Commit (a99de3b)
1 //
2 // Copyright (c) 2002-2016 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;
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 ········internal void GetOrdinal()
479 ········{
480 ············System.Diagnostics.Debug.Assert(this.Parent.RelationOrdinalBase > -1, "RelationOrdinalBase for type " + Parent.FullName + " not computed");
481 ············Type t = Type.GetType(this.Parent.FullName + ", " + this.Parent.AssemblyName);
482 IMetaClass2 mc = Metaclasses. GetClass( t) ;
483 ············Ordinal = this.Parent.RelationOrdinalBase + mc.GetRelationOrdinal(this.FieldName);
484 ············if (Ordinal > 63)
485 ················throw new NDOException(18, "Class " + Parent.FullName + " has too much relations.");
486 ········}
487
488 ········Class RelatedClass
489 ········{
490 ············get
491 ············{
492 ················Class relatedClass = Parent.Parent.FindClass(this.ReferencedTypeName);
493 ················if (relatedClass == null)
494 ····················throw new NDOException(17, "Can't find mapping information for class " + this.ReferencedTypeName);
495 ················return relatedClass;
496 ············}
497 ········}
498
499 ········void IFieldInitializer.InitFields()
500 ········{
501 ············bool isEnhancing = ((IEnhancerSupport)Parent.Parent).IsEnhancing;
502
503 ············if (!isEnhancing)
504 ················GetOrdinal();
505
506 ············Class relatedClass = this.RelatedClass;
507
508 ············Type t = Parent.SystemType;
509
510 ············if (t == null)
511 ················throw new InternalException(1155, "Relation.InitFields");
512
513 ············FieldInfo fi = null;
514
515 ············while (fi == null && t != typeof(object))
516 ············{
517 ················fi = t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
518 ················if (fi == null)
519 ····················t = t.BaseType;
520 ············}
521 ············if (fi == null)
522 ················throw new NDOException(20, "Can't find field " + Parent.SystemType.Name + "." + FieldName);
523
524 ············FieldType = fi.FieldType;
525
526 ············System.Attribute a = System.Attribute.GetCustomAttribute(fi, typeof(NDORelationAttribute), false);
527 ············NDORelationAttribute ra = (NDORelationAttribute)a;
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(typeof(NDOPersistentAttribute), false).Length > 0)
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 == null)
558 ····················throw new NDOException(97, $"Can't determine relation type for relation {Parent.FullName}.{fi.Name}");
559
560 ················if (ra.RelationType == null && fi.FieldType.IsGenericType)
561 ····················this.referencedType = fi.FieldType.GetGenericArguments()[0];
562 ················else
563 ····················this.referencedType = ra.RelationType;
564 ················if (referencedType == null)
565 ····················throw new NDOException(101, "Can't determine referenced type in relation " + this.Parent.FullName + "." + this.fieldName + ". Provide a type parameter for the [NDORelation] attribute.");
566 ············}
567 ············else
568 ············{
569 ················this.referencedType = FieldType;
570 ············}
571
572 ············if (HasSubclasses && Multiplicity == RelationMultiplicity.List && MappingTable == null)
573 ············{
574 ················//throw new NDOException(21, "Polymorphic 1:n-relation w/o mapping table is not supported");
575 ················Debug.WriteLine("NDO Warning: Polymorphic 1:n-relation " + Parent.FullName + "." + this.FieldName + " w/o mapping table");
576 ············}
577
578 ············this.definingClass = this.Parent;
579 ············Type bt = this.Parent.SystemType.BaseType;
580 ············while (typeof(IPersistenceCapable).IsAssignableFrom(bt))
581 ············{
582 ················Class pcl = this.Parent.Parent.FindClass(bt);
583 ················if (pcl.FindRelation(this.fieldName) != null)
584 ····················this.definingClass = pcl;
585 ················else
586 ····················break;
587 ················bt = bt.BaseType;
588 ············}
589
590 ············// Do not set fkColumn.Size to a value != 0 in during enhancing,
591 ············// because that value would be hard coded into the mapping file.
592 ············// Use (!isEnhancing) to make sure, that the code wasn't called··from the enhancer.
593 ············if (this.MappingTable != null)
594 ············{
595 ················// r.ForeignKeyColumns points to the own table.
596 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
597 ····················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 + '.');
598 ················int i = 0;
599 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
600 ················{
601 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
602 ····················if (!isEnhancing && fkColumn.Size == 0)
603 ························fkColumn.Size = oidColumn.Size;
604 ····················fkColumn.SystemType = oidColumn.SystemType;
605 ····················i++;
606 ················}
607 ················);
608
609 ················// r.MappingTable.ChildForeignKeyColumns points to the table of the related class.
610 ················if (relatedClass.Oid.OidColumns.Count != this.mappingTable.ChildForeignKeyColumns.Count())
611 ····················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() + '.');
612 ················i = 0;
613 ················new ForeignKeyIterator(this.mappingTable).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
614 ················{
615 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
616 ····················if (!isEnhancing && fkColumn.Size == 0)
617 ························fkColumn.Size = oidColumn.Size;
618 ····················fkColumn.SystemType = oidColumn.SystemType;
619 ····················i++;
620 ················}
621 ················);
622 ············}
623 ············else if (this.multiplicity == RelationMultiplicity.Element)··// The foreign key points to the tabel of the related class.
624 ············{
625 ················if (relatedClass.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
626 ····················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 + '.');
627 ················int i = 0;
628 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
629 ················{
630 ····················OidColumn oidColumn = (OidColumn)relatedClass.Oid.OidColumns[i];
631 ····················if (!isEnhancing && fkColumn.Size == 0)
632 ························fkColumn.Size = oidColumn.Size;
633 ····················fkColumn.SystemType = oidColumn.SystemType;
634 ····················i++;
635 ················}
636 ················);
637 ············}
638 ············else··// List relation. The foreign key points to the own table.
639 ············{
640 ················if (Parent.Oid.OidColumns.Count != this.foreignKeyColumns.Count)
641 ····················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 + '.');
642 ················int i = 0;
643 ················new ForeignKeyIterator(this).Iterate(delegate(ForeignKeyColumn fkColumn, bool isLastIndex)
644 ················{
645 ····················OidColumn oidColumn = (OidColumn)Parent.Oid.OidColumns[i];
646 ····················if (!isEnhancing && fkColumn.Size == 0)
647 ························fkColumn.Size = oidColumn.Size;
648 ····················fkColumn.SystemType = oidColumn.SystemType;
649 ····················i++;
650 ················}
651 ················);
652 ············}
653 ········}
654
655 ········/// <summary>
656 ········/// If a relation is bidirectional, this property gets the opposite relation
657 ········/// </summary>
658 ········[Browsable(false)]
659 ········public virtual Relation ForeignRelation
660 ········{
661 ············get
662 ············{
663 ················int status = 0;
664 ················try
665 ················{
666 ····················if (definingClass == null)
667 ························definingClass = this.Parent;
668 ····················status = 1;
669 ····················if (!foreignRelationValid)·· // null is a valid Value for foreignRelation
670 ····················{
671 ························foreignRelation = null;
672 ························Class referencedClass = Parent.Parent.FindClass(this.referencedTypeName);
673 ························status = 2;
674 ························if (null == referencedClass)
675 ························{
676 ····························foreignRelation = null;
677 ························}
678 ························else
679 ························{
680 ····························status = 3;
681 ····························// first check for a relation directing to our class
682 ····························foreach (Relation fr in referencedClass.Relations)
683 ····························{
684 ································string frdefiningClass = fr.definingClass == null ? fr.Parent.FullName : fr.definingClass.FullName;
685 ································if (null != fr.referencedTypeName
686 ····································&& fr.referencedTypeName == definingClass.FullName
687 ····································&& fr.relationName == this.relationName
688 ····································&& frdefiningClass == this.referencedTypeName)
689 ································{
690 ····································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
691 ····································// sonst kommt die gleiche Seite der Beziehung zurück.
692 ····································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
693 ····································{
694 ········································foreignRelation = fr;
695 ········································break;
696 ····································}
697 ································}
698 ····························}
699 ····························status = 4;
700 ····························// now check, if a relation targets our base class
701 ····························if (foreignRelation == null && definingClass != NodeParent)
702 ····························{
703 ································foreach (Relation fr in referencedClass.Relations)
704 ································{
705 ····································if (null != fr.referencedTypeName
706 ········································&& fr.referencedTypeName == definingClass.FullName
707 ········································&& fr.relationName == this.relationName)
708 ····································{
709 ········································// Bei der Selbstbeziehung muss der FieldName unterschiedlich sein
710 ········································// sonst kommt die gleiche Seite der Beziehung zurück.
711 ········································if (referencedClass != definingClass || fr.FieldName != this.FieldName)
712 ········································{
713 ············································foreignRelation = fr;
714 ············································break;
715 ········································}
716 ····································}
717 ································}
718 ····························}
719 ························}
720 ························status = 5;
721 ························foreignRelationValid = true;
722 ····················}
723 ····················return foreignRelation;
724 ················}
725 ················catch (Exception ex)
726 ················{
727 ····················throw new InternalException(1379, "Relation.ForeignRelation:" + ex.Message + " Status: " + status.ToString());
728 ················}
729 ············}
730 ········}
731
732 ········/// <summary>
733 ········/// String representation of the relation for debugging and tracing purposes
734 ········/// </summary>
735 ········/// <returns>A string representation of the Relation object</returns>
736 ········public override string ToString()
737 ········{
738 ············return "Relation " + this.RelationName + " for field " + this.FieldName + " of class " + Parent.FullName + ":\n" +
739 ················"····Type: " + FieldType + " [" + Multiplicity + "] RelationType: " + (Composition ? "Composition" : "Assoziation") +
740 ················", " + (Bidirectional ? "Bidirectional" : "Directed to class " + ReferencedType);
741 ········}
742
743 ········int hashCode = 0;
744 ········///<inheritdoc/>
745 ········public override int GetHashCode()
746 ········{
747 ············// This is a hack, because data binding to a property grid
748 ············// asks for the hash code. Since the binding occurs in the mapping tool
749 ············// with uninitialized definingClass and SystemType members
750 ············// we just return the hash code of System.Object.
751 ············if (definingClass == null || definingClass.SystemType == null)
752 ················return base.GetHashCode();
753
754 ············if (this.hashCode == 0)
755 ············{
756 ················int v1 = definingClass.SystemType.GetHashCode();
757 ················int v2 = this.referencedType.GetHashCode();
758 ················hashCode = (v1 ^ v2) ^ this.relationName.GetHashCode();
759 ············}
760 ············return hashCode;
761 ········}
762
763 ········///<inheritdoc/>
764 ········public override bool Equals(object obj)
765 ········{
766 ············if (definingClass == null)
767 ················return base.Equals(obj);
768
769 ············Relation r = obj as Relation;
770 ············if (r == null)
771 ················return false;
772 ············if (r.GetHashCode() == this.GetHashCode()
773 ················&& r.relationName == this.relationName)
774 ············{
775 ················if (r.definingClass == this.definingClass
776 ····················&& r.referencedType == this.referencedType)
777 ····················return true;
778 ················if (this.Bidirectional && r.Bidirectional)
779 ················{
780 ····················if (this.ForeignRelation.definingClass == r.definingClass
781 ························&& this.ForeignRelation.referencedType == r.referencedType)
782 ························return true;
783 ····················if (r.ForeignRelation.definingClass == this.definingClass
784 ························&& r.ForeignRelation.referencedType == this.referencedType)
785 ························return true;
786 ················}
787 ············}
788 ············return false;
789 ········}
790
791
792 ········#region IComparable Member
793
794 ········///<inheritdoc/>
795 ········public int CompareTo(object obj)
796 ········{
797 ············return this.FieldName.CompareTo(((Relation)obj).FieldName);
798 ········}
799
800 ········#endregion
801 ····}
802 }
803