PageRenderTime 38ms CodeModel.GetById 2ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 1ms

/mcs/class/System.Data.Linq/src/DbMetal/Generator/Implementation/CodeTextGenerator/CodeGenerator.Class.cs

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