PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

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

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