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