5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\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
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\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
27 using System.Collections.Generic;
\r
28 using System.Data.Linq.Mapping;
\r
29 using System.Diagnostics;
\r
31 using DbLinq.Schema.Dbml;
\r
32 using DbLinq.Schema.Dbml.Adapter;
\r
34 using DbMetal.Generator.EntityInterface;
\r
37 using System.Data.Linq;
\r
39 using DbLinq.Data.Linq;
\r
42 namespace DbMetal.Generator.Implementation.CodeTextGenerator
\r
47 partial class CodeGenerator
\r
49 protected virtual void WriteClasses(CodeWriter writer, Database schema, GenerationContext context)
\r
51 IEnumerable<Table> tables = schema.Tables;
\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
57 foreach (var table in tables)
\r
58 WriteClass(writer, table, schema, context);
\r
61 protected virtual void WriteClass(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
65 string entityBase = context.Parameters.EntityBase;
\r
66 if (string.IsNullOrEmpty(entityBase))
\r
67 entityBase = schema.EntityBase;
\r
69 var specifications = SpecificationDefinition.Partial;
\r
70 if (table.Type.AccessModifierSpecified)
\r
71 specifications |= GetSpecificationDefinition(table.Type.AccessModifier);
\r
73 specifications |= SpecificationDefinition.Public;
\r
74 if (table.Type.ModifierSpecified)
\r
75 specifications |= GetSpecificationDefinition(table.Type.Modifier);
\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
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
98 protected virtual void WriteClassEqualsAndHash(CodeWriter writer, Table table, GenerationContext context)
\r
100 List<DbLinq.Schema.Dbml.Column> primaryKeys = table.Type.Columns.Where(c => c.IsPrimaryKey).ToList();
\r
101 if (primaryKeys.Count == 0)
\r
103 writer.WriteLine("#warning L189 table {0} has no primary key. Multiple C# objects will refer to the same row.",
\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
112 using (writer.WriteMethod(SpecificationDefinition.Public | SpecificationDefinition.Override,
\r
113 "GetHashCode", typeof(int)))
\r
115 string hashCode = null;
\r
117 foreach (var primaryKey in primaryKeys)
\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
126 var isNullExpression = writer.GetEqualExpression(member, writer.GetNullExpression());
\r
127 var nullExpression = writer.GetLiteralValue(0);
\r
128 primaryKeyHashCode = writer.GetTernaryExpression(isNullExpression, nullExpression, primaryKeyHashCode);
\r
130 if (string.IsNullOrEmpty(hashCode))
\r
131 hashCode = primaryKeyHashCode;
\r
133 hashCode = writer.GetXOrExpression(hashCode, primaryKeyHashCode);
\r
135 writer.WriteLine(writer.GetReturnStatement(hashCode));
\r
137 writer.WriteLine();
\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
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
149 using (writer.WriteIf(writer.GetEqualExpression(other, writer.GetNullExpression())))
\r
151 writer.WriteLine(writer.GetReturnStatement(writer.GetLiteralValue(false)));
\r
153 string andExpression = null;
\r
154 foreach (var primaryKey in primaryKeys)
\r
156 var member = writer.GetVariableExpression(primaryKey.Storage);
\r
157 string primaryKeyTest = writer.GetMethodCallExpression(writer.GetMemberExpression(writer.GetLiteralType(typeof(object)), "Equals"),
\r
159 writer.GetMemberExpression(other, member));
\r
160 if (string.IsNullOrEmpty(andExpression))
\r
161 andExpression = primaryKeyTest;
\r
163 andExpression = writer.GetAndExpression(andExpression, primaryKeyTest);
\r
165 writer.WriteLine(writer.GetReturnStatement(andExpression));
\r
171 /// Class headers are written at top of class
\r
172 /// They consist in specific headers writen by interface implementors
\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
179 foreach (IImplementation implementation in context.Implementations())
\r
180 implementation.WriteClassHeader(writer, table, context);
\r
183 protected virtual void WriteClassExtensibilityDeclarations(CodeWriter writer, Table table, GenerationContext context)
\r
185 using (writer.WriteRegion("Extensibility Method Definitions"))
\r
187 writer.WriteLine("partial void OnCreated();");
\r
188 foreach (var c in table.Type.Columns)
\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
197 /// Writes all properties, depending on the use (simple property or FK)
\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
204 foreach (var property in table.Type.Columns)
\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
211 WriteClassProperty(writer, property, relatedAssociations, context);
\r
215 protected virtual string GetTypeOrExtendedType(CodeWriter writer, Column property)
\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
225 /// Writes class property
\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
233 using (writer.WriteRegion(string.Format("{0} {1}", GetTypeOrExtendedType(writer, property), property.Member)))
\r
235 WriteClassPropertyBackingField(writer, property, context);
\r
236 WriteClassPropertyAccessors(writer, property, relatedAssociations, context);
\r
240 protected virtual void WriteClassPropertyBackingField(CodeWriter writer, Column property, GenerationContext context)
\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
252 /// Returns a name from a given fullname
\r
254 /// <param name="fullName"></param>
\r
255 /// <returns></returns>
\r
256 protected virtual string GetName(string fullName)
\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
267 /// Returns name for given list of attributes
\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
274 return (from a in attributes select GetName(a)).ToArray();
\r
277 private class EnumFullname
\r
279 private string _EnumName;
\r
280 private object _EnumValue;
\r
282 public EnumFullname(string enumName, object enumValue)
\r
284 _EnumName = enumName;
\r
285 _EnumValue = enumValue;
\r
288 public override string ToString()
\r
290 return string.Format("{0}.{1}", _EnumName, _EnumValue.ToString());
\r
295 /// Writes property accessor
\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
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
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
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
333 // on auto storage, we're just lazy
\r
334 if (property.Storage == null)
\r
335 writer.WriteAutomaticPropertyGetSet();
\r
338 using (writer.WritePropertyGet())
\r
340 writer.WriteLine(writer.GetReturnStatement(writer.GetVariableExpression(property.Storage)));
\r
342 using (writer.WritePropertySet())
\r
344 WriteClassPropertyAccessorSet(writer, property, relatedAssociations, context);
\r
351 /// Writes property setter, for FK properties
\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
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
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
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
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
374 writer.WriteLine(writer.GetThrowStatement(writer.GetNewExpression(
\r
375 writer.GetMethodCallExpression(
\r
376 writer.GetLiteralFullType(
\r
379 ForeignKeyReferenceAlreadyHasValueException
\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
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
401 /// Returns all children (ie members of EntitySet)
\r
403 /// <param name="table"></param>
\r
404 /// <returns></returns>
\r
405 protected virtual IEnumerable<Association> GetClassChildren(Table table)
\r
407 return table.Type.Associations.Where(a => !a.IsForeignKey);
\r
411 /// Returns all parents (ie member referenced as EntityRef)
\r
413 /// <param name="table"></param>
\r
414 /// <returns></returns>
\r
415 protected virtual IEnumerable<Association> GetClassParents(Table table)
\r
417 return table.Type.Associations.Where(a => a.IsForeignKey);
\r
420 protected virtual void WriteClassChildren(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
422 var children = GetClassChildren(table).ToList();
\r
423 if (children.Count > 0)
\r
425 using (writer.WriteRegion("Children"))
\r
427 foreach (var child in children)
\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
436 private void WriteClassChild(CodeWriter writer, Association child, bool hasDuplicates, Database schema, GenerationContext context)
\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
442 //Logger.Write(Level.Error, "ERROR L143 target table class not found:" + child.Type);
\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
452 SpecificationDefinition specifications;
\r
453 if (child.AccessModifierSpecified)
\r
454 specifications = GetSpecificationDefinition(child.AccessModifier);
\r
456 specifications = SpecificationDefinition.Public;
\r
457 if (child.ModifierSpecified)
\r
458 specifications |= GetSpecificationDefinition(child.Modifier);
\r
460 var propertyName = hasDuplicates
\r
461 ? child.Member + "_" + string.Join("", child.OtherKeys.ToArray())
\r
464 var propertyType = writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type);
\r
466 if (child.Storage != null)
\r
467 writer.WriteField(SpecificationDefinition.Private, child.Storage, propertyType);
\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
474 // if we have a backing field, use it
\r
475 if (child.Storage != null)
\r
477 // the getter returns the field
\r
478 using (writer.WritePropertyGet())
\r
480 writer.WriteLine(writer.GetReturnStatement(
\r
484 // the setter assigns the field
\r
485 using (writer.WritePropertySet())
\r
487 writer.WriteLine(writer.GetStatement(
\r
488 writer.GetAssignmentExpression(
\r
490 writer.GetPropertySetValueExpression())
\r
494 // otherwise, use automatic property
\r
496 writer.WriteAutomaticPropertyGetSet();
\r
498 writer.WriteLine();
\r
501 protected virtual void WriteClassParents(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
503 var parents = GetClassParents(table).ToList();
\r
504 if (parents.Count > 0)
\r
506 using (writer.WriteRegion("Parents"))
\r
508 foreach (var parent in parents)
\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
517 protected virtual void WriteClassParent(CodeWriter writer, Association parent, bool hasDuplicates, Database schema, GenerationContext context)
\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
523 //Logger.Write(Level.Error, "ERROR L191 target table type not found: " + parent.Type + " (processing " + parent.Name + ")");
\r
527 string member = parent.Member;
\r
528 string storageField = parent.Storage;
\r
529 // TODO: remove this
\r
530 if (member == parent.ThisKey)
\r
532 member = parent.ThisKey + targetTable.Type.Name; //repeat name to prevent collision (same as Linq)
\r
533 storageField = "_x_" + parent.Member;
\r
536 writer.WriteField(SpecificationDefinition.Private, storageField,
\r
537 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)),
\r
538 targetTable.Type.Name));
\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
547 SpecificationDefinition specifications;
\r
548 if (parent.AccessModifierSpecified)
\r
549 specifications = GetSpecificationDefinition(parent.AccessModifier);
\r
551 specifications = SpecificationDefinition.Public;
\r
552 if (parent.ModifierSpecified)
\r
553 specifications |= GetSpecificationDefinition(parent.Modifier);
\r
555 var propertyName = hasDuplicates
\r
556 ? member + "_" + string.Join("", parent.TheseKeys.ToArray())
\r
559 using (writer.WriteAttribute(storageAttribute))
\r
560 using (writer.WriteAttribute(NewAttributeDefinition<DebuggerNonUserCodeAttribute>()))
\r
561 using (writer.WriteProperty(specifications, propertyName, targetTable.Type.Name))
\r
563 string storage = writer.GetMemberExpression(storageField, "Entity");
\r
564 using (writer.WritePropertyGet())
\r
566 writer.WriteLine(writer.GetReturnStatement(storage));
\r
568 using (writer.WritePropertySet())
\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
583 // 6.1. set FK members to defaults (null or 0)
\r
584 // 7. implementationas after change
\r
586 //writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(storage, writer.GetPropertySetValueExpression())));
\r
587 var entityMember = writer.GetMemberExpression(parent.Storage, "Entity");
\r
589 using (writer.WriteIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(),
\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
598 using (writer.WriteIf(writer.GetDifferentExpression(entityMember, writer.GetNullExpression())))
\r
600 var previousEntityRefName = "previous" + parent.Type;
\r
602 writer.WriteLine(writer.GetStatement(
\r
603 writer.GetVariableDeclarationInitialization(parent.Type, previousEntityRefName, entityMember)
\r
606 writer.WriteLine(writer.GetStatement(
\r
607 writer.GetAssignmentExpression(entityMember, writer.GetNullExpression())
\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
617 writer.WriteLine(writer.GetStatement(
\r
618 writer.GetAssignmentExpression(entityMember, writer.GetPropertySetValueExpression())
\r
621 // 5. if value is null or not
\r
622 writer.WriteRawIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(), writer.GetNullExpression()));
\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
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
635 var parentKeys = parent.OtherKeys.ToArray();
\r
637 for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)
\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
646 writer.WriteRawElse();
\r
649 for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)
\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
660 writer.WriteRawEndif();
\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
670 writer.WriteLine();
\r
674 /// Returns event method name related to a child
\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
681 return string.Format("{0}_{1}", child.Member, method);
\r
685 /// Returns child attach method name
\r
687 /// <param name="child"></param>
\r
688 /// <returns></returns>
\r
689 protected virtual string GetChildAttachMethodName(Association child)
\r
691 return GetChildMethodName(child, "Attach");
\r
695 /// Returns child detach method name
\r
697 /// <param name="child"></param>
\r
698 /// <returns></returns>
\r
699 protected virtual string GetChildDetachMethodName(Association child)
\r
701 return GetChildMethodName(child, "Detach");
\r
705 /// Writes attach/detach method
\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
713 var children = GetClassChildren(table).ToList();
\r
714 if (children.Count > 0)
\r
716 using (writer.WriteRegion("Attachement handlers"))
\r
718 foreach (var child in children)
\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
731 writer.GetStatement(
\r
732 writer.GetAssignmentExpression(
\r
733 writer.GetMemberExpression(entityParameter.Name, memberName),
\r
734 writer.GetThisExpression())));
\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
742 writer.GetStatement(
\r
743 writer.GetAssignmentExpression(
\r
744 writer.GetMemberExpression(entityParameter.Name, memberName),
\r
745 writer.GetNullExpression())));
\r
747 writer.WriteLine();
\r
754 /// Writes class ctor.
\r
755 /// EntitySet initializations
\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
763 using (writer.WriteRegion("ctor"))
\r
764 using (writer.WriteCtor(SpecificationDefinition.Public, table.Type.Name, new ParameterDefinition[0], null))
\r
766 // children are EntitySet
\r
767 foreach (var child in GetClassChildren(table))
\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
774 writer.GetNewExpression(writer.GetMethodCallExpression(
\r
775 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type),
\r
776 GetChildAttachMethodName(child),
\r
777 GetChildDetachMethodName(child)
\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
785 var entityRefMember = parent.Storage;
\r
786 writer.WriteLine(writer.GetStatement(
\r
787 writer.GetAssignmentExpression(
\r
789 writer.GetNewExpression(writer.GetMethodCallExpression(
\r
790 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)), parent.Type)
\r
795 writer.WriteLine(writer.GetStatement(writer.GetMethodCallExpression("OnCreated")));
\r