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 foreach (var table in schema.Tables)
\r
52 WriteClass(writer, table, schema, context);
\r
55 protected virtual void WriteClass(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
59 string entityBase = context.Parameters.EntityBase;
\r
60 if (string.IsNullOrEmpty(entityBase))
\r
61 entityBase = schema.EntityBase;
\r
63 var specifications = SpecificationDefinition.Partial;
\r
64 if (table.Type.AccessModifierSpecified)
\r
65 specifications |= GetSpecificationDefinition(table.Type.AccessModifier);
\r
67 specifications |= SpecificationDefinition.Public;
\r
68 if (table.Type.ModifierSpecified)
\r
69 specifications |= GetSpecificationDefinition(table.Type.Modifier);
\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
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
91 protected virtual void WriteClassEqualsAndHash(CodeWriter writer, Table table, GenerationContext context)
\r
93 List<DbLinq.Schema.Dbml.Column> primaryKeys = table.Type.Columns.Where(c => c.IsPrimaryKey).ToList();
\r
94 if (primaryKeys.Count == 0)
\r
96 writer.WriteLine("#warning L189 table {0} has no primary key. Multiple C# objects will refer to the same row.",
\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
105 using (writer.WriteMethod(SpecificationDefinition.Public | SpecificationDefinition.Override,
\r
106 "GetHashCode", typeof(int)))
\r
108 string hashCode = null;
\r
110 foreach (var primaryKey in primaryKeys)
\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
119 var isNullExpression = writer.GetEqualExpression(member, writer.GetNullExpression());
\r
120 var nullExpression = writer.GetLiteralValue(0);
\r
121 primaryKeyHashCode = writer.GetTernaryExpression(isNullExpression, nullExpression, primaryKeyHashCode);
\r
123 if (string.IsNullOrEmpty(hashCode))
\r
124 hashCode = primaryKeyHashCode;
\r
126 hashCode = writer.GetXOrExpression(hashCode, primaryKeyHashCode);
\r
128 writer.WriteLine(writer.GetReturnStatement(hashCode));
\r
130 writer.WriteLine();
\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
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
142 using (writer.WriteIf(writer.GetEqualExpression(other, writer.GetNullExpression())))
\r
144 writer.WriteLine(writer.GetReturnStatement(writer.GetLiteralValue(false)));
\r
146 string andExpression = null;
\r
147 foreach (var primaryKey in primaryKeys)
\r
149 var member = writer.GetVariableExpression(primaryKey.Storage);
\r
150 string primaryKeyTest = writer.GetMethodCallExpression(writer.GetMemberExpression(writer.GetLiteralType(typeof(object)), "Equals"),
\r
152 writer.GetMemberExpression(other, member));
\r
153 if (string.IsNullOrEmpty(andExpression))
\r
154 andExpression = primaryKeyTest;
\r
156 andExpression = writer.GetAndExpression(andExpression, primaryKeyTest);
\r
158 writer.WriteLine(writer.GetReturnStatement(andExpression));
\r
164 /// Class headers are written at top of class
\r
165 /// They consist in specific headers writen by interface implementors
\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
172 foreach (IImplementation implementation in context.Implementations())
\r
173 implementation.WriteClassHeader(writer, table, context);
\r
177 /// Writes all properties, depending on the use (simple property or FK)
\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
184 foreach (var property in table.Type.Columns)
\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
191 WriteClassProperty(writer, property, relatedAssociations, context);
\r
195 protected virtual string GetTypeOrExtendedType(CodeWriter writer, Column property)
\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
205 /// Writes class property
\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
213 using (writer.WriteRegion(string.Format("{0} {1}", GetTypeOrExtendedType(writer, property), property.Member)))
\r
215 WriteClassPropertyBackingField(writer, property, context);
\r
216 WriteClassPropertyAccessors(writer, property, relatedAssociations, context);
\r
220 protected virtual void WriteClassPropertyBackingField(CodeWriter writer, Column property, GenerationContext context)
\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
232 /// Returns a name from a given fullname
\r
234 /// <param name="fullName"></param>
\r
235 /// <returns></returns>
\r
236 protected virtual string GetName(string fullName)
\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
247 /// Returns name for given list of attributes
\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
254 return (from a in attributes select GetName(a)).ToArray();
\r
258 /// Writes property accessor
\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
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
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
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
294 // on auto storage, we're just lazy
\r
295 if (property.Storage == null)
\r
296 writer.WriteAutomaticPropertyGetSet();
\r
299 using (writer.WritePropertyGet())
\r
301 writer.WriteLine(writer.GetReturnStatement(writer.GetVariableExpression(property.Storage)));
\r
303 using (writer.WritePropertySet())
\r
305 WriteClassPropertyAccessorSet(writer, property, relatedAssociations, context);
\r
312 /// Writes property setter, for FK properties
\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
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
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
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
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
335 writer.WriteLine(writer.GetThrowStatement(writer.GetNewExpression(
\r
336 writer.GetMethodCallExpression(
\r
337 writer.GetLiteralFullType(
\r
340 ForeignKeyReferenceAlreadyHasValueException
\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
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
362 /// Returns all children (ie members of EntitySet)
\r
364 /// <param name="table"></param>
\r
365 /// <returns></returns>
\r
366 protected virtual IEnumerable<Association> GetClassChildren(Table table)
\r
368 return table.Type.Associations.Where(a => !a.IsForeignKey);
\r
372 /// Returns all parents (ie member referenced as EntityRef)
\r
374 /// <param name="table"></param>
\r
375 /// <returns></returns>
\r
376 protected virtual IEnumerable<Association> GetClassParents(Table table)
\r
378 return table.Type.Associations.Where(a => a.IsForeignKey);
\r
381 protected virtual void WriteClassChildren(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
383 var children = GetClassChildren(table).ToList();
\r
384 if (children.Count > 0)
\r
386 using (writer.WriteRegion("Children"))
\r
388 foreach (var child in children)
\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
397 private void WriteClassChild(CodeWriter writer, Association child, bool hasDuplicates, Database schema, GenerationContext context)
\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
403 //Logger.Write(Level.Error, "ERROR L143 target table class not found:" + child.Type);
\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
413 SpecificationDefinition specifications;
\r
414 if (child.AccessModifierSpecified)
\r
415 specifications = GetSpecificationDefinition(child.AccessModifier);
\r
417 specifications = SpecificationDefinition.Public;
\r
418 if (child.ModifierSpecified)
\r
419 specifications |= GetSpecificationDefinition(child.Modifier);
\r
421 var propertyName = hasDuplicates
\r
422 ? child.Member + "_" + string.Join("", child.OtherKeys.ToArray())
\r
425 var propertyType = writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type);
\r
427 if (child.Storage != null)
\r
428 writer.WriteField(SpecificationDefinition.Private, child.Storage, propertyType);
\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
435 // if we have a backing field, use it
\r
436 if (child.Storage != null)
\r
438 // the getter returns the field
\r
439 using (writer.WritePropertyGet())
\r
441 writer.WriteLine(writer.GetReturnStatement(
\r
445 // the setter assigns the field
\r
446 using (writer.WritePropertySet())
\r
448 writer.WriteLine(writer.GetStatement(
\r
449 writer.GetAssignmentExpression(
\r
451 writer.GetPropertySetValueExpression())
\r
455 // otherwise, use automatic property
\r
457 writer.WriteAutomaticPropertyGetSet();
\r
459 writer.WriteLine();
\r
462 protected virtual void WriteClassParents(CodeWriter writer, Table table, Database schema, GenerationContext context)
\r
464 var parents = GetClassParents(table).ToList();
\r
465 if (parents.Count > 0)
\r
467 using (writer.WriteRegion("Parents"))
\r
469 foreach (var parent in parents)
\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
478 protected virtual void WriteClassParent(CodeWriter writer, Association parent, bool hasDuplicates, Database schema, GenerationContext context)
\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
484 //Logger.Write(Level.Error, "ERROR L191 target table type not found: " + parent.Type + " (processing " + parent.Name + ")");
\r
488 string member = parent.Member;
\r
489 string storageField = parent.Storage;
\r
490 // TODO: remove this
\r
491 if (member == parent.ThisKey)
\r
493 member = parent.ThisKey + targetTable.Type.Name; //repeat name to prevent collision (same as Linq)
\r
494 storageField = "_x_" + parent.Member;
\r
497 writer.WriteField(SpecificationDefinition.Private, storageField,
\r
498 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)),
\r
499 targetTable.Type.Name));
\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
508 SpecificationDefinition specifications;
\r
509 if (parent.AccessModifierSpecified)
\r
510 specifications = GetSpecificationDefinition(parent.AccessModifier);
\r
512 specifications = SpecificationDefinition.Public;
\r
513 if (parent.ModifierSpecified)
\r
514 specifications |= GetSpecificationDefinition(parent.Modifier);
\r
516 var propertyName = hasDuplicates
\r
517 ? member + "_" + string.Join("", parent.TheseKeys.ToArray())
\r
520 using (writer.WriteAttribute(storageAttribute))
\r
521 using (writer.WriteAttribute(NewAttributeDefinition<DebuggerNonUserCodeAttribute>()))
\r
522 using (writer.WriteProperty(specifications, propertyName, targetTable.Type.Name))
\r
524 string storage = writer.GetMemberExpression(storageField, "Entity");
\r
525 using (writer.WritePropertyGet())
\r
527 writer.WriteLine(writer.GetReturnStatement(storage));
\r
529 using (writer.WritePropertySet())
\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
544 // 6.1. set FK members to defaults (null or 0)
\r
545 // 7. implementationas after change
\r
547 //writer.WriteLine(writer.GetStatement(writer.GetAssignmentExpression(storage, writer.GetPropertySetValueExpression())));
\r
548 var entityMember = writer.GetMemberExpression(parent.Storage, "Entity");
\r
550 using (writer.WriteIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(),
\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
559 using (writer.WriteIf(writer.GetDifferentExpression(entityMember, writer.GetNullExpression())))
\r
561 var previousEntityRefName = "previous" + parent.Type;
\r
563 writer.WriteLine(writer.GetStatement(
\r
564 writer.GetVariableDeclarationInitialization(parent.Type, previousEntityRefName, entityMember)
\r
567 writer.WriteLine(writer.GetStatement(
\r
568 writer.GetAssignmentExpression(entityMember, writer.GetNullExpression())
\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
578 writer.WriteLine(writer.GetStatement(
\r
579 writer.GetAssignmentExpression(entityMember, writer.GetPropertySetValueExpression())
\r
582 // 5. if value is null or not
\r
583 writer.WriteRawIf(writer.GetDifferentExpression(writer.GetPropertySetValueExpression(), writer.GetNullExpression()));
\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
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
596 var parentKeys = parent.OtherKeys.ToArray();
\r
598 for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)
\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
607 writer.WriteRawElse();
\r
610 for (int keyIndex = 0; keyIndex < parentKeys.Length; keyIndex++)
\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
621 writer.WriteRawEndif();
\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
631 writer.WriteLine();
\r
635 /// Returns event method name related to a child
\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
642 return string.Format("{0}_{1}", child.Member, method);
\r
646 /// Returns child attach method name
\r
648 /// <param name="child"></param>
\r
649 /// <returns></returns>
\r
650 protected virtual string GetChildAttachMethodName(Association child)
\r
652 return GetChildMethodName(child, "Attach");
\r
656 /// Returns child detach method name
\r
658 /// <param name="child"></param>
\r
659 /// <returns></returns>
\r
660 protected virtual string GetChildDetachMethodName(Association child)
\r
662 return GetChildMethodName(child, "Detach");
\r
666 /// Writes attach/detach method
\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
674 var children = GetClassChildren(table).ToList();
\r
675 if (children.Count > 0)
\r
677 using (writer.WriteRegion("Attachement handlers"))
\r
679 foreach (var child in children)
\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
692 writer.GetStatement(
\r
693 writer.GetAssignmentExpression(
\r
694 writer.GetMemberExpression(entityParameter.Name, memberName),
\r
695 writer.GetThisExpression())));
\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
703 writer.GetStatement(
\r
704 writer.GetAssignmentExpression(
\r
705 writer.GetMemberExpression(entityParameter.Name, memberName),
\r
706 writer.GetNullExpression())));
\r
708 writer.WriteLine();
\r
715 /// Writes class ctor.
\r
716 /// EntitySet initializations
\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
724 using (writer.WriteRegion("ctor"))
\r
725 using (writer.WriteCtor(SpecificationDefinition.Public, table.Type.Name, new ParameterDefinition[0], null))
\r
727 // children are EntitySet
\r
728 foreach (var child in GetClassChildren(table))
\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
735 writer.GetNewExpression(writer.GetMethodCallExpression(
\r
736 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntitySet<>)), child.Type),
\r
737 GetChildAttachMethodName(child),
\r
738 GetChildDetachMethodName(child)
\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
746 var entityRefMember = parent.Storage;
\r
747 writer.WriteLine(writer.GetStatement(
\r
748 writer.GetAssignmentExpression(
\r
750 writer.GetNewExpression(writer.GetMethodCallExpression(
\r
751 writer.GetGenericName(TypeExtensions.GetShortName(typeof(EntityRef<>)), parent.Type)
\r