2010-03-12 Jb Evain <jbevain@novell.com>
[mono.git] / mcs / class / System.Data.Linq / src / DbMetal / Generator / Implementation / CodeTextGenerator / CodeGenerator.Class.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System.Collections.Generic;\r
28 using System.Data.Linq.Mapping;\r
29 using System.Diagnostics;\r
30 using System.Linq;\r
31 using DbLinq.Schema.Dbml;\r
32 using DbLinq.Schema.Dbml.Adapter;\r
33 using DbLinq.Util;\r
34 using DbMetal.Generator.EntityInterface;\r
35 \r
36 #if MONO_STRICT\r
37 using System.Data.Linq;\r
38 #else\r
39 using DbLinq.Data.Linq;\r
40 #endif\r
41 \r
42 namespace DbMetal.Generator.Implementation.CodeTextGenerator\r
43 {\r
44 #if !MONO_STRICT\r
45     public\r
46 #endif\r
47     partial class CodeGenerator\r
48     {\r
49         protected virtual void WriteClasses(CodeWriter writer, Database schema, GenerationContext context)\r
50         {\r
51             IEnumerable<Table> tables = schema.Tables;\r
52 \r
53             var types = context.Parameters.GenerateTypes;\r
54             if (types.Count > 0)\r
55                 tables = tables.Where(t => types.Contains(t.Type.Name));\r
56 \r
57             foreach (var table in tables)\r
58                 WriteClass(writer, table, schema, context);\r
59         }\r
60 \r
61         protected virtual void WriteClass(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
62         {\r
63             writer.WriteLine();\r
64 \r
65             string entityBase = context.Parameters.EntityBase;\r
66             if (string.IsNullOrEmpty(entityBase))\r
67                 entityBase = schema.EntityBase;\r
68 \r
69             var specifications = SpecificationDefinition.Partial;\r
70             if (table.Type.AccessModifierSpecified)\r
71                 specifications |= GetSpecificationDefinition(table.Type.AccessModifier);\r
72             else\r
73                 specifications |= SpecificationDefinition.Public;\r
74             if (table.Type.ModifierSpecified)\r
75                 specifications |= GetSpecificationDefinition(table.Type.Modifier);\r
76 \r
77             var tableAttribute = NewAttributeDefinition<TableAttribute>();\r
78             tableAttribute["Name"] = table.Name;\r
79             //using (WriteAttributes(writer, context.Parameters.EntityExposedAttributes))\r
80             using (WriteAttributes(writer, GetAttributeNames(context, context.Parameters.EntityExposedAttributes)))\r
81             using (writer.WriteAttribute(tableAttribute))\r
82             using (writer.WriteClass(specifications,\r
83                                      table.Type.Name, entityBase, context.Parameters.EntityInterfaces))\r
84             {\r
85                 WriteClassHeader(writer, table, context);\r
86                 WriteCustomTypes(writer, table, schema, context);\r
87                 WriteClassExtensibilityDeclarations(writer, table, context);\r
88                 WriteClassProperties(writer, table, context);\r
89                 if (context.Parameters.GenerateEqualsAndHash)\r
90                     WriteClassEqualsAndHash(writer, table, context);\r
91                 WriteClassChildren(writer, table, schema, context);\r
92                 WriteClassParents(writer, table, schema, context);\r
93                 WriteClassChildrenAttachment(writer, table, schema, context);\r
94                 WriteClassCtor(writer, table, schema, context);\r
95             }\r
96         }\r
97 \r
98         protected virtual void WriteClassEqualsAndHash(CodeWriter writer, Table table, GenerationContext context)\r
99         {\r
100             List<DbLinq.Schema.Dbml.Column> primaryKeys = table.Type.Columns.Where(c => c.IsPrimaryKey).ToList();\r
101             if (primaryKeys.Count == 0)\r
102             {\r
103                 writer.WriteLine("#warning L189 table {0} has no primary key. Multiple C# objects will refer to the same row.",\r
104                                  table.Name);\r
105                 return;\r
106             }\r
107 \r
108             using (writer.WriteRegion(string.Format("GetHashCode(), Equals() - uses column {0} to look up objects in liveObjectMap",\r
109                                                     string.Join(", ", primaryKeys.Select(pk => pk.Member).ToList().ToArray()))))\r
110             {\r
111                 // GetHashCode\r
112                 using (writer.WriteMethod(SpecificationDefinition.Public | SpecificationDefinition.Override,\r
113                                           "GetHashCode", typeof(int)))\r
114                 {\r
115                     string hashCode = null;\r
116 \r
117                     foreach (var primaryKey in primaryKeys)\r
118                     {\r
119                         var member = writer.GetVariableExpression(primaryKey.Storage);\r
120                         string primaryKeyHashCode = writer.GetMethodCallExpression(writer.GetMemberExpression(member, "GetHashCode"));\r
121                         if (primaryKey.CanBeNull\r
122                         || primaryKey.ExtendedType == null\r
123                         || GetType(primaryKey.Type, false).IsClass) // this patch to ensure that even if DB does not allow nulls,\r
124                         // our in-memory object won't generate a fault\r
125                         {\r
126                             var isNullExpression = writer.GetEqualExpression(member, writer.GetNullExpression());\r
127                             var nullExpression = writer.GetLiteralValue(0);\r
128                             primaryKeyHashCode = writer.GetTernaryExpression(isNullExpression, nullExpression, primaryKeyHashCode);\r
129                         }\r
130                         if (string.IsNullOrEmpty(hashCode))\r
131                             hashCode = primaryKeyHashCode;\r
132                         else\r
133                             hashCode = writer.GetXOrExpression(hashCode, primaryKeyHashCode);\r
134                     }\r
135                     writer.WriteLine(writer.GetReturnStatement(hashCode));\r
136                 }\r
137                 writer.WriteLine();\r
138 \r
139                 // Equals\r
140                 string otherAsObject = "o";\r
141                 using (writer.WriteMethod(SpecificationDefinition.Public | SpecificationDefinition.Override,\r
142                                           "Equals", typeof(bool), new ParameterDefinition { Type = typeof(object), Name = otherAsObject }))\r
143                 {\r
144                     string other = "other";\r
145                     writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(\r
146                                                              writer.GetDeclarationExpression(other, table.Type.Name),\r
147                                                              writer.GetCastExpression(otherAsObject, table.Type.Name,\r
148                                                                                       false))));\r
149                     using (writer.WriteIf(writer.GetEqualExpression(other, writer.GetNullExpression())))\r
150                     {\r
151                         writer.WriteLine(writer.GetReturnStatement(writer.GetLiteralValue(false)));\r
152                     }\r
153                     string andExpression = null;\r
154                     foreach (var primaryKey in primaryKeys)\r
155                     {\r
156                         var member = writer.GetVariableExpression(primaryKey.Storage);\r
157                         string primaryKeyTest = writer.GetMethodCallExpression(writer.GetMemberExpression(writer.GetLiteralType(typeof(object)), "Equals"),\r
158                                                                                member,\r
159                                                                                writer.GetMemberExpression(other, member));\r
160                         if (string.IsNullOrEmpty(andExpression))\r
161                             andExpression = primaryKeyTest;\r
162                         else\r
163                             andExpression = writer.GetAndExpression(andExpression, primaryKeyTest);\r
164                     }\r
165                     writer.WriteLine(writer.GetReturnStatement(andExpression));\r
166                 }\r
167             }\r
168         }\r
169 \r
170         /// <summary>\r
171         /// Class headers are written at top of class\r
172         /// They consist in specific headers writen by interface implementors\r
173         /// </summary>\r
174         /// <param name="writer"></param>\r
175         /// <param name="table"></param>\r
176         /// <param name="context"></param>\r
177         private void WriteClassHeader(CodeWriter writer, Table table, GenerationContext context)\r
178         {\r
179             foreach (IImplementation implementation in context.Implementations())\r
180                 implementation.WriteClassHeader(writer, table, context);\r
181         }\r
182 \r
183         protected virtual void WriteClassExtensibilityDeclarations(CodeWriter writer, Table table, GenerationContext context)\r
184         {\r
185             using (writer.WriteRegion("Extensibility Method Definitions"))\r
186             {\r
187                 writer.WriteLine("partial void OnCreated();");\r
188                 foreach (var c in table.Type.Columns)\r
189                 {\r
190                     writer.WriteLine("partial void On{0}Changed();", c.Member);\r
191                     writer.WriteLine("partial void On{0}Changing({1} value);", c.Member, GetTypeOrExtendedType(writer, c));\r
192                 }\r
193             }\r
194         }\r
195 \r
196         /// <summary>\r
197         /// Writes all properties, depending on the use (simple property or FK)\r
198         /// </summary>\r
199         /// <param name="writer"></param>\r
200         /// <param name="table"></param>\r
201         /// <param name="context"></param>\r
202         protected virtual void WriteClassProperties(CodeWriter writer, Table table, GenerationContext context)\r
203         {\r
204             foreach (var property in table.Type.Columns)\r
205             {\r
206                 var property1 = property;\r
207                 var relatedAssociations = from a in table.Type.Associations\r
208                                           where a.IsForeignKey\r
209                                           && a.TheseKeys.Contains(property1.Name)\r
210                                           select a;\r
211                 WriteClassProperty(writer, property, relatedAssociations, context);\r
212             }\r
213         }\r
214 \r
215         protected virtual string GetTypeOrExtendedType(CodeWriter writer, Column property)\r
216         {\r
217             object extendedType = property.ExtendedType;\r
218             var enumType = extendedType as EnumType;\r
219             if (enumType != null)\r
220                 return writer.GetEnumType(enumType.Name);\r
221             return writer.GetLiteralType(GetType(property.Type, property.CanBeNull));\r
222         }\r
223 \r
224         /// <summary>\r
225         /// Writes class property\r
226         /// </summary>\r
227         /// <param name="writer"></param>\r
228         /// <param name="property"></param>\r
229         /// <param name="relatedAssociations">non null if property is a FK</param>\r
230         /// <param name="context"></param>\r
231         protected virtual void WriteClassProperty(CodeWriter writer, Column property, IEnumerable<Association> relatedAssociations, GenerationContext context)\r
232         {\r
233             using (writer.WriteRegion(string.Format("{0} {1}", GetTypeOrExtendedType(writer, property), property.Member)))\r
234             {\r
235                 WriteClassPropertyBackingField(writer, property, context);\r
236                 WriteClassPropertyAccessors(writer, property, relatedAssociations, context);\r
237             }\r
238         }\r
239 \r
240         protected virtual void WriteClassPropertyBackingField(CodeWriter writer, Column property, GenerationContext context)\r
241         {\r
242             //AttributeDefinition autoGenAttribute = null;\r
243             //if (property.IsDbGenerated)\r
244             //    autoGenAttribute = NewAttributeDefinition<AutoGenIdAttribute>();\r
245             //using (writer.WriteAttribute(autoGenAttribute))\r
246             // for auto-properties, we just won't generate a private field\r
247             if (property.Storage != null)\r
248                 writer.WriteField(SpecificationDefinition.Private, property.Storage, GetTypeOrExtendedType(writer, property));\r
249         }\r
250 \r
251         /// <summary>\r
252         /// Returns a name from a given fullname\r
253         /// </summary>\r
254         /// <param name="fullName"></param>\r
255         /// <returns></returns>\r
256         protected virtual string GetName(string fullName)\r
257         {\r
258             var namePartIndex = fullName.LastIndexOf('.');\r
259             // if we have a dot, we have a namespace\r
260             if (namePartIndex > 0)\r
261                 return fullName.Substring(namePartIndex + 1);\r
262             // otherwise, it's just a name, that we keep as is\r
263             return fullName;\r
264         }\r
265 \r
266         /// <summary>\r
267         /// Returns name for given list of attributes\r
268         /// </summary>\r
269         /// <param name="context"></param>\r
270         /// <param name="attributes"></param>\r
271         /// <returns></returns>\r
272         protected virtual string[] GetAttributeNames(GenerationContext context, string[] attributes)\r
273         {\r
274             return (from a in attributes select GetName(a)).ToArray();\r
275         }\r
276 \r
277         private class EnumFullname\r
278         {\r
279             private string _EnumName;\r
280             private object _EnumValue;\r
281 \r
282             public EnumFullname(string enumName, object enumValue)\r
283             {\r
284                 _EnumName = enumName;\r
285                 _EnumValue = enumValue;\r
286             }\r
287 \r
288             public override string ToString()\r
289             {\r
290                 return string.Format("{0}.{1}", _EnumName, _EnumValue.ToString());\r
291             }\r
292         }\r
293 \r
294         /// <summary>\r
295         /// Writes property accessor\r
296         /// </summary>\r
297         /// <param name="writer"></param>\r
298         /// <param name="property"></param>\r
299         /// <param name="relatedAssociations"></param>\r
300         /// <param name="context"></param>\r
301         protected virtual void WriteClassPropertyAccessors(CodeWriter writer, Column property, IEnumerable<Association> relatedAssociations, GenerationContext context)\r
302         {\r
303             //generate [Column(...)] attribute\r
304             var column = NewAttributeDefinition<ColumnAttribute>();\r
305             column["Storage"] = property.Storage;\r
306             column["Name"] = property.Name;\r
307             column["DbType"] = property.DbType;\r
308             // be smart: we only write attributes when they differ from the default values\r
309             var columnAttribute = new ColumnAttribute();\r
310             if (property.IsPrimaryKey != columnAttribute.IsPrimaryKey)\r
311                 column["IsPrimaryKey"] = property.IsPrimaryKey;\r
312             if (property.IsDbGenerated != columnAttribute.IsDbGenerated)\r
313                 column["IsDbGenerated"] = property.IsDbGenerated;\r
314             if (property.AutoSync != DbLinq.Schema.Dbml.AutoSync.Default)\r
315                 column["AutoSync"] = new EnumFullname("AutoSync", property.AutoSync);\r
316             if (property.CanBeNull != columnAttribute.CanBeNull)\r
317                 column["CanBeNull"] = property.CanBeNull;\r
318             if (property.Expression != null)\r
319                 column["Expression"] = property.Expression;\r
320 \r
321             var specifications = property.AccessModifierSpecified\r
322                                      ? GetSpecificationDefinition(property.AccessModifier)\r
323                                      : SpecificationDefinition.Public;\r
324             if (property.ModifierSpecified)\r
325                 specifications |= GetSpecificationDefinition(property.Modifier);\r
326 \r
327             //using (WriteAttributes(writer, context.Parameters.MemberExposedAttributes))\r
328             using (WriteAttributes(writer, GetAttributeNames(context, context.Parameters.MemberExposedAttributes)))\r
329             using (writer.WriteAttribute(NewAttributeDefinition<DebuggerNonUserCodeAttribute>()))\r
330             using (writer.WriteAttribute(column))\r
331             using (writer.WriteProperty(specifications, property.Member, GetTypeOrExtendedType(writer, property)))\r
332             {\r
333                 // on auto storage, we're just lazy\r
334                 if (property.Storage == null)\r
335                     writer.WriteAutomaticPropertyGetSet();\r
336                 else\r
337                 {\r
338                     using (writer.WritePropertyGet())\r
339                     {\r
340                         writer.WriteLine(writer.GetReturnStatement(writer.GetVariableExpression(property.Storage)));\r
341                     }\r
342                     using (writer.WritePropertySet())\r
343                     {\r
344                         WriteClassPropertyAccessorSet(writer, property, relatedAssociations, context);\r
345                     }\r
346                 }\r
347             }\r
348         }\r
349 \r
350         /// <summary>\r
351         /// Writes property setter, for FK properties\r
352         /// </summary>\r
353         /// <param name="writer"></param>\r
354         /// <param name="property"></param>\r
355         /// <param name="relatedAssociations"></param>\r
356         /// <param name="context"></param>\r
357         private void WriteClassPropertyAccessorSet(CodeWriter writer, Column property, IEnumerable<Association> relatedAssociations, GenerationContext context)\r
358         {\r
359             // if new value if different from old one\r
360             using (writer.WriteIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(),\r
361                                                                 writer.GetVariableExpression(property.Storage))))\r
362             {\r
363                 // if the property is used as a FK, we ensure that it hasn't been already loaded or assigned\r
364                 foreach (var relatedAssociation in relatedAssociations)\r
365                 {\r
366                     // first thing to check: ensure the association backend isn't already affected.\r
367                     // if it is the case, then the property must not be manually set\r
368 \r
369                     // R# considers the following as an error, but the csc doesn't\r
370                     //var memberName = ReflectionUtility.GetMemberInfo<DbLinq.Data.Linq.EntityRef<object>>(e => e.HasLoadedOrAssignedValue).Name;\r
371                     var memberName = "HasLoadedOrAssignedValue";\r
372                     using (writer.WriteIf(writer.GetMemberExpression(relatedAssociation.Storage, memberName)))\r
373                     {\r
374                         writer.WriteLine(writer.GetThrowStatement(writer.GetNewExpression(\r
375                                                                       writer.GetMethodCallExpression(\r
376                                                                           writer.GetLiteralFullType(\r
377                                                                               typeof(\r
378                                                                                   System.Data.Linq.\r
379                                                                                   ForeignKeyReferenceAlreadyHasValueException\r
380                                                                                   )))\r
381                                                                       )));\r
382                     }\r
383                 }\r
384                 // the before and after are used by extensions related to interfaces\r
385                 // for example INotifyPropertyChanged\r
386                 // here the code before the change\r
387                 foreach (IImplementation implementation in context.Implementations())\r
388                     implementation.WritePropertyBeforeSet(writer, property, context);\r
389                 // property assignment\r
390                 writer.WriteLine(\r
391                     writer.GetStatement(\r
392                         writer.GetAssignmentExpression(writer.GetVariableExpression(property.Storage),\r
393                                                        writer.GetPropertySetValueExpression())));\r
394                 // here the code after change\r
395                 foreach (IImplementation implementation in context.Implementations())\r
396                     implementation.WritePropertyAfterSet(writer, property, context);\r
397             }\r
398         }\r
399 \r
400         /// <summary>\r
401         /// Returns all children (ie members of EntitySet)\r
402         /// </summary>\r
403         /// <param name="table"></param>\r
404         /// <returns></returns>\r
405         protected virtual IEnumerable<Association> GetClassChildren(Table table)\r
406         {\r
407             return table.Type.Associations.Where(a => !a.IsForeignKey);\r
408         }\r
409 \r
410         /// <summary>\r
411         /// Returns all parents (ie member referenced as EntityRef)\r
412         /// </summary>\r
413         /// <param name="table"></param>\r
414         /// <returns></returns>\r
415         protected virtual IEnumerable<Association> GetClassParents(Table table)\r
416         {\r
417             return table.Type.Associations.Where(a => a.IsForeignKey);\r
418         }\r
419 \r
420         protected virtual void WriteClassChildren(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
421         {\r
422             var children = GetClassChildren(table).ToList();\r
423             if (children.Count > 0)\r
424             {\r
425                 using (writer.WriteRegion("Children"))\r
426                 {\r
427                     foreach (var child in children)\r
428                     {\r
429                         bool hasDuplicates = (from c in children where c.Member == child.Member select c).Count() > 1;\r
430                         WriteClassChild(writer, child, hasDuplicates, schema, context);\r
431                     }\r
432                 }\r
433             }\r
434         }\r
435 \r
436         private void WriteClassChild(CodeWriter writer, Association child, bool hasDuplicates, Database schema, GenerationContext context)\r
437         {\r
438             // the following is apparently useless\r
439             DbLinq.Schema.Dbml.Table targetTable = schema.Tables.FirstOrDefault(t => t.Type.Name == child.Type);\r
440             if (targetTable == null)\r
441             {\r
442                 //Logger.Write(Level.Error, "ERROR L143 target table class not found:" + child.Type);\r
443                 return;\r
444             }\r
445 \r
446             var storageAttribute = NewAttributeDefinition<AssociationAttribute>();\r
447             storageAttribute["Storage"] = child.Storage;\r
448             storageAttribute["OtherKey"] = child.OtherKey;\r
449             storageAttribute["ThisKey"] = child.ThisKey;\r
450             storageAttribute["Name"] = child.Name;\r
451 \r
452             SpecificationDefinition specifications;\r
453             if (child.AccessModifierSpecified)\r
454                 specifications = GetSpecificationDefinition(child.AccessModifier);\r
455             else\r
456                 specifications = SpecificationDefinition.Public;\r
457             if (child.ModifierSpecified)\r
458                 specifications |= GetSpecificationDefinition(child.Modifier);\r
459 \r
460             var propertyName = hasDuplicates\r
461                                    ? child.Member + "_" + string.Join("", child.OtherKeys.ToArray())\r
462                                    : child.Member;\r
463 \r
464             var propertyType = writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type);\r
465 \r
466             if (child.Storage != null)\r
467                 writer.WriteField(SpecificationDefinition.Private, child.Storage, propertyType);\r
468 \r
469             using (writer.WriteAttribute(storageAttribute))\r
470             using (writer.WriteAttribute(NewAttributeDefinition<DebuggerNonUserCodeAttribute>()))\r
471             using (writer.WriteProperty(specifications, propertyName,\r
472                                         writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type)))\r
473             {\r
474                 // if we have a backing field, use it\r
475                 if (child.Storage != null)\r
476                 {\r
477                     // the getter returns the field\r
478                     using (writer.WritePropertyGet())\r
479                     {\r
480                         writer.WriteLine(writer.GetReturnStatement(\r
481                             child.Storage\r
482                             ));\r
483                     }\r
484                     // the setter assigns the field\r
485                     using (writer.WritePropertySet())\r
486                     {\r
487                         writer.WriteLine(writer.GetStatement(\r
488                             writer.GetAssignmentExpression(\r
489                             child.Storage,\r
490                             writer.GetPropertySetValueExpression())\r
491                             ));\r
492                     }\r
493                 }\r
494                 // otherwise, use automatic property\r
495                 else\r
496                     writer.WriteAutomaticPropertyGetSet();\r
497             }\r
498             writer.WriteLine();\r
499         }\r
500 \r
501         protected virtual void WriteClassParents(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
502         {\r
503             var parents = GetClassParents(table).ToList();\r
504             if (parents.Count > 0)\r
505             {\r
506                 using (writer.WriteRegion("Parents"))\r
507                 {\r
508                     foreach (var parent in parents)\r
509                     {\r
510                         bool hasDuplicates = (from p in parents where p.Member == parent.Member select p).Count() > 1;\r
511                         WriteClassParent(writer, parent, hasDuplicates, schema, context);\r
512                     }\r
513                 }\r
514             }\r
515         }\r
516 \r
517         protected virtual void WriteClassParent(CodeWriter writer, Association parent, bool hasDuplicates, Database schema, GenerationContext context)\r
518         {\r
519             // the following is apparently useless\r
520             DbLinq.Schema.Dbml.Table targetTable = schema.Tables.FirstOrDefault(t => t.Type.Name == parent.Type);\r
521             if (targetTable == null)\r
522             {\r
523                 //Logger.Write(Level.Error, "ERROR L191 target table type not found: " + parent.Type + "  (processing " + parent.Name + ")");\r
524                 return;\r
525             }\r
526 \r
527             string member = parent.Member;\r
528             string storageField = parent.Storage;\r
529             // TODO: remove this\r
530             if (member == parent.ThisKey)\r
531             {\r
532                 member = parent.ThisKey + targetTable.Type.Name; //repeat name to prevent collision (same as Linq)\r
533                 storageField = "_x_" + parent.Member;\r
534             }\r
535 \r
536             writer.WriteField(SpecificationDefinition.Private, storageField,\r
537                               writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)),\r
538                                                     targetTable.Type.Name));\r
539 \r
540             var storageAttribute = NewAttributeDefinition<AssociationAttribute>();\r
541                         storageAttribute["Storage"] = storageField;\r
542                         storageAttribute["OtherKey"] = parent.OtherKey;\r
543             storageAttribute["ThisKey"] = parent.ThisKey;\r
544             storageAttribute["Name"] = parent.Name;\r
545             storageAttribute["IsForeignKey"] = parent.IsForeignKey;\r
546 \r
547             SpecificationDefinition specifications;\r
548             if (parent.AccessModifierSpecified)\r
549                 specifications = GetSpecificationDefinition(parent.AccessModifier);\r
550             else\r
551                 specifications = SpecificationDefinition.Public;\r
552             if (parent.ModifierSpecified)\r
553                 specifications |= GetSpecificationDefinition(parent.Modifier);\r
554 \r
555             var propertyName = hasDuplicates\r
556                                    ? member + "_" + string.Join("", parent.TheseKeys.ToArray())\r
557                                    : member;\r
558 \r
559             using (writer.WriteAttribute(storageAttribute))\r
560             using (writer.WriteAttribute(NewAttributeDefinition<DebuggerNonUserCodeAttribute>()))\r
561             using (writer.WriteProperty(specifications, propertyName, targetTable.Type.Name))\r
562             {\r
563                 string storage = writer.GetMemberExpression(storageField, "Entity");\r
564                 using (writer.WritePropertyGet())\r
565                 {\r
566                     writer.WriteLine(writer.GetReturnStatement(storage));\r
567                 }\r
568                 using (writer.WritePropertySet())\r
569                 {\r
570                     // algorithm is:\r
571                     // 1.1. must be different than previous value\r
572                     // 1.2. or HasLoadedOrAssignedValue is false (but why?)\r
573                     // 2. implementations before change\r
574                     // 3. if previous value not null\r
575                     // 3.1. place parent in temp variable\r
576                     // 3.2. set [Storage].Entity to null\r
577                     // 3.3. remove it from parent list\r
578                     // 4. assign value to [Storage].Entity\r
579                     // 5. if value is not null\r
580                     // 5.1. add it to parent list\r
581                     // 5.2. set FK members with entity keys\r
582                     // 6. else\r
583                     // 6.1. set FK members to defaults (null or 0)\r
584                     // 7. implementationas after change\r
585 \r
586                     //writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(storage, writer.GetPropertySetValueExpression())));\r
587                     var entityMember = writer.GetMemberExpression(parent.Storage, "Entity");\r
588                     // 1.1\r
589                     using (writer.WriteIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(),\r
590                                                                         entityMember)))\r
591                     {\r
592                         var otherAssociation = schema.GetReverseAssociation(parent);\r
593                         // 2. code before the change\r
594                         // TODO change interface to require a member instead of a column\r
595                         //foreach (IImplementation implementation in context.Implementations())\r
596                         //    implementation.WritePropertyBeforeSet(writer, ???, context);\r
597                         // 3.\r
598                         using (writer.WriteIf(writer.GetDifferentExpression(entityMember, writer.GetNullExpression())))\r
599                         {\r
600                             var previousEntityRefName = "previous" + parent.Type;\r
601                             // 3.1.\r
602                             writer.WriteLine(writer.GetStatement(\r
603                                 writer.GetVariableDeclarationInitialization(parent.Type, previousEntityRefName, entityMember)\r
604                                 ));\r
605                             // 3.2.\r
606                             writer.WriteLine(writer.GetStatement(\r
607                                 writer.GetAssignmentExpression(entityMember, writer.GetNullExpression())\r
608                                 ));\r
609                             // 3.3.\r
610                             writer.WriteLine(writer.GetStatement(\r
611                                 writer.GetMethodCallExpression(\r
612                                     writer.GetMemberExpression(writer.GetMemberExpression(previousEntityRefName, otherAssociation.Member), "Remove"),\r
613                                     writer.GetThisExpression())\r
614                                 ));\r
615                         }\r
616                         // 4.\r
617                         writer.WriteLine(writer.GetStatement(\r
618                             writer.GetAssignmentExpression(entityMember, writer.GetPropertySetValueExpression())\r
619                             ));\r
620 \r
621                         // 5. if value is null or not\r
622                         writer.WriteRawIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(), writer.GetNullExpression()));\r
623                         // 5.1.\r
624                         writer.WriteLine(writer.GetStatement(\r
625                             writer.GetMethodCallExpression(\r
626                                 writer.GetMemberExpression(writer.GetMemberExpression(writer.GetPropertySetValueExpression(), otherAssociation.Member), "Add"),\r
627                                 writer.GetThisExpression())\r
628                             ));\r
629 \r
630                         // 5.2\r
631                         var table = schema.Tables.Single(t => t.Type.Associations.Contains(parent));\r
632                         var childKeys = parent.TheseKeys.ToArray();\r
633                         var childColumns = (from ck in childKeys select table.Type.Columns.Single(c => c.Member == ck))\r
634                                             .ToArray();\r
635                         var parentKeys = parent.OtherKeys.ToArray();\r
636 \r
637                         for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)\r
638                         {\r
639                             writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(\r
640                                 childColumns[keyIndex].Storage ?? childColumns[keyIndex].Member,\r
641                                 writer.GetMemberExpression(writer.GetPropertySetValueExpression(), parentKeys[keyIndex])\r
642                                 )));\r
643                         }\r
644 \r
645                         // 6.\r
646                         writer.WriteRawElse();\r
647 \r
648                         // 6.1.\r
649                         for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)\r
650                         {\r
651                             var column = table.Type.Columns.Single(c => c.Member == childKeys[keyIndex]);\r
652                             var columnType = System.Type.GetType(column.Type);\r
653                             var columnLiteralType = columnType != null ? writer.GetLiteralType(columnType) : column.Type;\r
654                             writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(\r
655                                 childColumns[keyIndex].Storage ?? childColumns[keyIndex].Member,\r
656                                 column.CanBeNull ? writer.GetNullExpression() : writer.GetNullValueExpression(columnLiteralType)\r
657                                 )));\r
658                         }\r
659 \r
660                         writer.WriteRawEndif();\r
661 \r
662                         // 7. code after change\r
663                         // TODO change interface to require a member instead of a column\r
664                         //foreach (IImplementation implementation in context.Implementations())\r
665                         //    implementation.WritePropertyAfterSet(writer, ???, context);\r
666 \r
667                     }\r
668                 }\r
669             }\r
670             writer.WriteLine();\r
671         }\r
672 \r
673         /// <summary>\r
674         /// Returns event method name related to a child\r
675         /// </summary>\r
676         /// <param name="child"></param>\r
677         /// <param name="method"></param>\r
678         /// <returns></returns>\r
679         protected virtual string GetChildMethodName(Association child, string method)\r
680         {\r
681             return string.Format("{0}_{1}", child.Member, method);\r
682         }\r
683 \r
684         /// <summary>\r
685         /// Returns child attach method name\r
686         /// </summary>\r
687         /// <param name="child"></param>\r
688         /// <returns></returns>\r
689         protected virtual string GetChildAttachMethodName(Association child)\r
690         {\r
691             return GetChildMethodName(child, "Attach");\r
692         }\r
693 \r
694         /// <summary>\r
695         /// Returns child detach method name\r
696         /// </summary>\r
697         /// <param name="child"></param>\r
698         /// <returns></returns>\r
699         protected virtual string GetChildDetachMethodName(Association child)\r
700         {\r
701             return GetChildMethodName(child, "Detach");\r
702         }\r
703 \r
704         /// <summary>\r
705         /// Writes attach/detach method\r
706         /// </summary>\r
707         /// <param name="writer"></param>\r
708         /// <param name="table"></param>\r
709         /// <param name="schema"></param>\r
710         /// <param name="context"></param>\r
711         protected virtual void WriteClassChildrenAttachment(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
712         {\r
713             var children = GetClassChildren(table).ToList();\r
714             if (children.Count > 0)\r
715             {\r
716                 using (writer.WriteRegion("Attachement handlers"))\r
717                 {\r
718                     foreach (var child in children)\r
719                     {\r
720                         // the reverse child is the association seen from the child\r
721                         // we're going to use it...\r
722                         var reverseChild = schema.GetReverseAssociation(child);\r
723                         // ... to get the parent name\r
724                         var memberName = reverseChild.Member;\r
725                         var entityParameter = new ParameterDefinition { Name = "entity", LiteralType = child.Type };\r
726                         // the Attach event handler sets the child entity parent to "this"\r
727                         using (writer.WriteMethod(SpecificationDefinition.Private, GetChildAttachMethodName(child),\r
728                                                   null, entityParameter))\r
729                         {\r
730                             writer.WriteLine(\r
731                                 writer.GetStatement(\r
732                                     writer.GetAssignmentExpression(\r
733                                         writer.GetMemberExpression(entityParameter.Name, memberName),\r
734                                         writer.GetThisExpression())));\r
735                         }\r
736                         writer.WriteLine();\r
737                         // the Detach event handler sets the child entity parent to null\r
738                         using (writer.WriteMethod(SpecificationDefinition.Private, GetChildDetachMethodName(child),\r
739                                                   null, entityParameter))\r
740                         {\r
741                             writer.WriteLine(\r
742                                 writer.GetStatement(\r
743                                     writer.GetAssignmentExpression(\r
744                                         writer.GetMemberExpression(entityParameter.Name, memberName),\r
745                                         writer.GetNullExpression())));\r
746                         }\r
747                         writer.WriteLine();\r
748                     }\r
749                 }\r
750             }\r
751         }\r
752 \r
753         /// <summary>\r
754         /// Writes class ctor.\r
755         /// EntitySet initializations\r
756         /// </summary>\r
757         /// <param name="writer"></param>\r
758         /// <param name="table"></param>\r
759         /// <param name="schema"></param>\r
760         /// <param name="context"></param>\r
761         protected virtual void WriteClassCtor(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
762         {\r
763             using (writer.WriteRegion("ctor"))\r
764             using (writer.WriteCtor(SpecificationDefinition.Public, table.Type.Name, new ParameterDefinition[0], null))\r
765             {\r
766                 // children are EntitySet\r
767                 foreach (var child in GetClassChildren(table))\r
768                 {\r
769                     // if the association has a storage, we use it. Otherwise, we use the property name\r
770                     var entitySetMember = child.Storage ?? child.Member;\r
771                     writer.WriteLine(writer.GetStatement(\r
772                         writer.GetAssignmentExpression(\r
773                             entitySetMember,\r
774                             writer.GetNewExpression(writer.GetMethodCallExpression(\r
775                                 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type),\r
776                                 GetChildAttachMethodName(child),\r
777                                 GetChildDetachMethodName(child)\r
778                             ))\r
779                         )\r
780                         ));\r
781                 }\r
782                 // the parents are the entities referenced by a FK. So a "parent" is an EntityRef\r
783                 foreach (var parent in GetClassParents(table))\r
784                 {\r
785                     var entityRefMember = parent.Storage;\r
786                     writer.WriteLine(writer.GetStatement(\r
787                         writer.GetAssignmentExpression(\r
788                             entityRefMember,\r
789                             writer.GetNewExpression(writer.GetMethodCallExpression(\r
790                             writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)), parent.Type)\r
791                             ))\r
792                         )\r
793                     ));\r
794                 }\r
795                 writer.WriteLine(writer.GetStatement(writer.GetMethodCallExpression("OnCreated")));\r
796             }\r
797         }\r
798     }\r
799 }\r