PageRenderTime 63ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/1.Framework/Vita.Core/Entities/Model/Construction/EntityModelInfoBuilder.cs

http://vita.codeplex.com
C# | 717 lines | 569 code | 57 blank | 91 comment | 119 complexity | 91d34904e6cb06a350f545da8266c623 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ComponentModel;
  6. using System.Reflection;
  7. using System.Collections;
  8. using Vita.Common;
  9. using Vita.Common.Graphs;
  10. using Vita.Entities;
  11. using Vita.Entities.Runtime;
  12. using Vita.Logging;
  13. namespace Vita.Entities.Model.Construction {
  14. public class EntityModelInfoBuilder {
  15. Log _log;
  16. EntityModel _model;
  17. EntityModelInfo _modelInfo;
  18. //Temporary lists
  19. IList<EntityInfo> _allEntities; // initial list of all entities
  20. Dictionary<Type, EntityInfo> _entitiesByType;
  21. AttributeContext _attributeContext;
  22. /// <summary>Builds runtime entity model info. </summary>
  23. public static EntityModelInfo Build(EntityModel model, Log log) {
  24. var modelBuilder = new EntityModelInfoBuilder();
  25. var result = modelBuilder.BuildModelInfo(model, log);
  26. if (log.HasErrors)
  27. throw new ActivationException("Entity model build failed.", log.GetAllAsText());
  28. return result;
  29. }
  30. private EntityModelInfo BuildModelInfo(EntityModel model, Log log) {
  31. _log = log;
  32. _model = model;
  33. _modelInfo = model.ModelInfo = new EntityModelInfo(_model);
  34. _attributeContext = new AttributeContext(this._modelInfo, _log);
  35. _allEntities = new List<EntityInfo>();
  36. _entitiesByType = new Dictionary<Type, EntityInfo>();
  37. //First add entities that are not registered explicitly but referenced through other entities' properties
  38. CollectInitialEntities();
  39. VerifyEntityReferences();
  40. ProcessReplacements();
  41. RegisterEntities();
  42. BuildEntityMembers(); //processes column, size attribute
  43. //Attributes
  44. CollectAllAttributes();
  45. // Basic infrastructure - PKs, FKs
  46. ProcessSpecialAttributes();
  47. if (Failed())
  48. return _modelInfo;
  49. // We have to run expansion twice. First time right after processing special attributes - to expand foreign keys.
  50. // Indexes (specified in non-special attributes) may refer to columns created from KeyColumns in foreign keys of entity references, so these keys must be expanded.
  51. // The second time we expand key members to expand indexes themselves.
  52. ExpandEntityKeyMembers();
  53. ProcessNonSpecialModelAttributes();
  54. ExpandEntityKeyMembers();
  55. ValidateIndexes();
  56. CompleteMembersSetup();
  57. BuildDefaultInitialValues();
  58. BuildEntityClasses();
  59. ComputeTopologicalIndexes();
  60. CollectEnumTypes();
  61. if (Failed())
  62. return _modelInfo;
  63. //commands
  64. BuildCommands();
  65. return _modelInfo;
  66. }//method
  67. internal void LogError(string message, params object[] args) {
  68. _log.Error(message, args);
  69. }
  70. private bool Failed() {
  71. return _log.HasErrors;
  72. }
  73. //Collects initially registered entities plus all entities found in properties of registered entities;
  74. // creates EntityInfo objects for each entity and adds them to Model's Entities set.
  75. private void CollectInitialEntities() {
  76. // Collect initial entities
  77. foreach (var module in _modelInfo.Model.Modules)
  78. foreach (var entType in module.Entities)
  79. AddEntity(entType, module);
  80. }//method
  81. // Discover other entities in properties. A property may be an Entity reference, or entity list - in the latter case we get the entity in the list;
  82. // also the list might be many-to-many, so we need to check Link entity specified by ManyToMany attribute.
  83. private void VerifyEntityReferences(int startIndex = 0) {
  84. //initialize all-entities list
  85. Type typeInList;
  86. // Iterate through the list using index and append new entities as we discover them
  87. for (int i = startIndex; i < _allEntities.Count; i++) {
  88. var entInfo = _allEntities[i];
  89. var props = entInfo.EntityType.GetAllProperties();
  90. foreach (var prop in props) {
  91. var propType = prop.PropertyType;
  92. if (propType.IsEntity())
  93. CheckReferencedEntity(propType, prop.Name, entInfo);
  94. else if (ReflectionHelper.IsEntityListInterface(propType, out typeInList)) {
  95. CheckReferencedEntity(typeInList, prop.Name, entInfo);
  96. //For many-to-many, we may need to add LinkEntity
  97. var m2mAttr = prop.GetAttribute<ManyToManyAttribute>();
  98. if (m2mAttr != null && m2mAttr.LinkEntity.IsEntity())
  99. CheckReferencedEntity(m2mAttr.LinkEntity, prop.Name, entInfo);
  100. }// else if IsEntityList
  101. }//foreac prop
  102. }//for i
  103. }//method
  104. private EntityInfo GetEntity(Type entityType) {
  105. EntityInfo entInfo;
  106. _entitiesByType.TryGetValue(entityType, out entInfo);
  107. return entInfo;
  108. }
  109. private void CheckReferencedEntity(Type entityType, string propertyName, EntityInfo owner) {
  110. EntityInfo entInfo;
  111. if (!_entitiesByType.TryGetValue(entityType, out entInfo))
  112. LogError("Property {0}.{1}: referenced entity type {2} is not registered as an entity.", owner.EntityType.Name, propertyName, entityType.Name);
  113. }
  114. private EntityInfo AddEntity(Type entityType, EntityModule module) {
  115. EntityInfo entInfo;
  116. if (_entitiesByType.TryGetValue(entityType, out entInfo))
  117. return entInfo;
  118. entInfo = new EntityInfo(module, entityType);
  119. _allEntities.Add(entInfo);
  120. _entitiesByType[entityType] = entInfo;
  121. return entInfo;
  122. }
  123. private void ProcessReplacements() {
  124. var oldCount = _allEntities.Count;
  125. // 1. Go thru replacements, find/create entity info for "new" entities, and register this entity info under the key of replaced entity type
  126. foreach (var replInfo in _modelInfo.Model.Replacements) {
  127. var oldEntInfo = GetEntity(replInfo.ReplacedType);
  128. Util.Check(oldEntInfo != null, "Replaced entity ({0}) must be explicitly registered in one of the modules.", replInfo.ReplacedType);
  129. var newType = replInfo.NewType;
  130. var newEntInfo = _modelInfo.GetEntityInfo(replInfo.NewType);
  131. if (newEntInfo == null) //not registered. Means we import this entity into original module
  132. newEntInfo = AddEntity(replInfo.NewType, oldEntInfo.Module);
  133. oldEntInfo.ReplacedBy = newEntInfo;
  134. }
  135. // 2. Scan newly added entities for more referenced entities
  136. VerifyEntityReferences(oldCount);
  137. // 3. Trace replacedBy reference, find final replacing type and register entity info for final type under the "replaced type" key
  138. foreach (var entInfo in _allEntities) {
  139. if (entInfo.ReplacedBy == null) continue;
  140. entInfo.ReplacedBy = GetFinalReplacement(entInfo);
  141. entInfo.ReplacedBy.ReplacesTypes.Add(entInfo.EntityType);
  142. }
  143. }
  144. private void RegisterEntities() {
  145. foreach (var entInfo in _allEntities) {
  146. var otherEnt = _modelInfo.GetEntityInfo(entInfo.FullName);
  147. if (otherEnt != null) {
  148. LogError("Duplicate entity full name: entity {0} is already registered.", entInfo.FullName);
  149. continue;
  150. }
  151. _modelInfo.RegisterEntity(entInfo);
  152. EntityModelInfo.RegisterEntity(entInfo.EntityType, entInfo);
  153. //Also save entity info for replaced types
  154. foreach (var t in entInfo.ReplacesTypes)
  155. EntityModelInfo.RegisterEntity(t, entInfo);
  156. }
  157. }
  158. //Note: returns entityInfo if there is no replacement
  159. private EntityInfo GetFinalReplacement(EntityInfo entityInfo) {
  160. var current = entityInfo;
  161. while (current.ReplacedBy != null)
  162. current = current.ReplacedBy;
  163. return current;
  164. }
  165. private void BuildEntityMembers() {
  166. //Now build entity info for type in final types
  167. foreach (var entInfo in _modelInfo.Entities) {
  168. //Create data members
  169. var props = entInfo.EntityType.GetAllProperties();
  170. EntityMemberInfo member;
  171. foreach (var prop in props) {
  172. MemberKind kind;
  173. if (TryGetMemberKind(entInfo, prop, out kind)) {
  174. member = new EntityMemberInfo(entInfo, kind, prop); //member is added to members automatically
  175. }
  176. }
  177. }//foreach entType
  178. }
  179. private bool TryGetMemberKind(EntityInfo entity, PropertyInfo property, out MemberKind kind) {
  180. var dataType = property.PropertyType;
  181. kind = MemberKind.Column;
  182. if (dataType.IsValueType || dataType == typeof(string))
  183. return true;
  184. var genType = dataType.IsGenericType ? dataType.GetGenericTypeDefinition() : null;
  185. if (genType == typeof(Nullable<>))
  186. return true;
  187. if (dataType.IsEntity()) {
  188. kind = MemberKind.EntityRef;
  189. return true;
  190. }
  191. if (genType == typeof(IList<>)) {
  192. kind = MemberKind.EntityList;
  193. return true;
  194. }
  195. // properly report common mistake
  196. if (genType == typeof(List<>)) {
  197. this.LogError("Invalid entity member {0}.{1}. Use IList<T> interface for list members. ", entity.Name, property.Name);
  198. return false;
  199. }
  200. //default: Column
  201. return true; //Column; there are some specific types that turn into column (Binary for ex)
  202. }
  203. private void CollectAllAttributes() {
  204. //1. Collection attributes on entities and members themselves
  205. foreach (var entity in _modelInfo.Entities) {
  206. entity.Attributes.AddRange(entity.EntityType.GetAllAttributes());
  207. foreach (var member in entity.Members)
  208. member.AddAttributes(member.Property.GetCustomAttributes(true));
  209. }//foreach entity
  210. // 2. Collect attributes from companion types
  211. foreach (var companion in _modelInfo.Model.CompanionTypes) {
  212. Type entType = null;
  213. // First see if ForEntity attr is specified on companion type; if not, find target entity in inheritance chain
  214. // - companion types usually inherit from entities they accompany
  215. var feAtt = companion.GetAttribute<ForEntityAttribute>();
  216. if (feAtt != null) {
  217. entType = feAtt.EntityType;
  218. }
  219. if (entType == null)
  220. entType = FindBaseEntity(companion);
  221. if (entType == null) {
  222. LogError("Could not find target Entity type for companion type {0}.", companion);
  223. continue;
  224. }
  225. //Find entity info
  226. var entity = _modelInfo.GetEntityInfo(entType);
  227. if (entity == null) {
  228. LogError("Entity type {0} for companion type {1} is not registered as Entity.", entType, companion);
  229. continue;
  230. }
  231. entity.CompanionTypes.Add(companion);
  232. //Add attributes
  233. entity.Attributes.AddRange(companion.GetAllAttributes(inherit: false));
  234. //Add attributes from members of Companion Type. Note we use DeclaredOnly to catch only those that are declared
  235. // by the type itself, not inherited.
  236. const BindingFlags propBindingFlags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.DeclaredOnly;
  237. var props = companion.GetProperties(propBindingFlags);
  238. foreach (var prop in props) {
  239. var member = entity.GetMember(prop.Name);
  240. if (member != null)
  241. member.AddAttributes(prop.GetCustomAttributes(false));
  242. }//foreach prop
  243. }//foreach cpmnType
  244. }
  245. private Type FindBaseEntity(Type type) {
  246. var types = type.GetInterfaces();
  247. var ent = types.FirstOrDefault(t => t.IsEntity());
  248. return ent;
  249. }
  250. private void ProcessSpecialAttributes() {
  251. // Pass 1
  252. foreach (var entity in _modelInfo.Entities) {
  253. var attr = entity.GetAttribute<EntityAttribute>();
  254. if (attr == null)
  255. Util.Throw("Entity {0}: EntityAttribute not found.", entity.FullName);
  256. attr.Apply(_attributeContext, attr, entity);
  257. foreach (var member in entity.Members) {
  258. //Apply NoColumn attribute - it might change member kind
  259. FindAndApplyAttribute<NoColumnAttribute>(member);
  260. // Apply nullable attribute
  261. FindAndApplyAttribute<NullableAttribute>(member);
  262. // Set default for string size, might be changed later by attributes
  263. if (member.DataType == typeof(string))
  264. member.Size = entity.Module.Settings.DefaultStringLength;
  265. //apply Size attr if present; Column attr might overwrite size value
  266. FindAndApplyAttribute<SizeAttribute>(member);
  267. //apply column attr if present; we apply it here rather than inside switch, to detect improper use
  268. FindAndApplyAttribute<ColumnAttribute>(member);
  269. }//foreach member
  270. }//foreach entity -- end pass 1
  271. if (Failed())
  272. return;
  273. // Pass 2 - primary keys. PK may reference members using column names, so we do it after applying Column attribute to all members
  274. foreach (var entity in _modelInfo.Entities) {
  275. bool pkAttrFound = false;
  276. var entPkAttr = entity.GetAttribute<PrimaryKeyAttribute>();
  277. if (entPkAttr != null) {
  278. entPkAttr.Apply(_attributeContext, entPkAttr, entity);
  279. pkAttrFound = true;
  280. }
  281. foreach (var member in entity.Members) {
  282. pkAttrFound |= FindAndApplyAttribute<PrimaryKeyAttribute>(member);
  283. }
  284. if (pkAttrFound)
  285. continue; //next entity
  286. // if PK attr is not found, try matching by default PK member name
  287. var dftPk = entity.Module.Settings.DefaultPrimaryKeyProperty;
  288. if (!string.IsNullOrWhiteSpace(dftPk)) {
  289. var dftPkMember = entity.FindMemberOrColumn(dftPk);
  290. if (dftPkMember != null) {
  291. // If match, create artificial pk attr and apply it.
  292. var pkAttr = new PrimaryKeyAttribute(dftPk);
  293. entity.Attributes.Add(pkAttr);
  294. pkAttr.Apply(_attributeContext, pkAttr, entity);
  295. }
  296. }
  297. if (entity.PrimaryKey == null) {
  298. LogError("Entity {0} has no PrimaryKey attribute, and no property matches default primary key name.", entity.FullName);
  299. }
  300. }//foreach entity
  301. // Pass 3 - entity references. Relies on PKs already created
  302. foreach (var entity in _modelInfo.Entities) {
  303. foreach (var member in entity.Members) {
  304. if (member.Kind != MemberKind.EntityRef) continue;
  305. var erAttr = member.GetAttribute<EntityRefAttribute>();
  306. if (erAttr == null) {
  307. erAttr = new EntityRefAttribute();
  308. member.Attributes.Add(erAttr);
  309. }
  310. erAttr.Apply(_attributeContext, erAttr, member);
  311. }//foreach member
  312. }// foreach entity
  313. // Pass 4 - entity lists. Assumes entity references are processed
  314. foreach (var entity in _modelInfo.Entities) {
  315. foreach (var member in entity.Members) {
  316. if (member.Kind != MemberKind.EntityList) continue;
  317. // Check many-to-many
  318. if (FindAndApplyAttribute<ManyToManyAttribute>(member)) continue;
  319. // try to find and apply OneToMany
  320. if (FindAndApplyAttribute<OneToManyAttribute>(member)) continue;
  321. // If neither attribute is found, create OneToMany and apply it
  322. var attr = new OneToManyAttribute();
  323. member.Attributes.Add(attr);
  324. attr.Apply(_attributeContext, attr, member);
  325. }
  326. }
  327. }//method
  328. private bool FindAndApplyAttribute<TAttr>(EntityMemberInfo member) where TAttr : ModelAttribute {
  329. var attr = member.GetAttribute<TAttr>();
  330. if (attr == null) return false;
  331. attr.Apply(_attributeContext, attr, member);
  332. return true;
  333. }
  334. #region AttributeTuple inner class
  335. class AttributeTuple {
  336. public IAttributeHandler Handler;
  337. public Attribute Attribute;
  338. public EntityInfo Entity;
  339. public EntityMemberInfo Member;
  340. public override string ToString() {
  341. return Attribute.ToString();
  342. }
  343. public static int CompareOrder(AttributeTuple x, AttributeTuple y) {
  344. return ((int)x.Handler.ApplyOrder).CompareTo((int)y.Handler.ApplyOrder);
  345. }
  346. public void Apply(AttributeContext context) {
  347. var attrDesc = Attribute.GetType().Name;
  348. if (this.Entity != null)
  349. try {
  350. this.Handler.Apply(context, Attribute, Entity);
  351. } catch (Exception ex) {
  352. context.Log.Error("Exception thrown when applying attribute {0} on entity {1}: {2}", attrDesc, Entity.Name, ex.Message);
  353. }
  354. if (this.Member != null)
  355. try {
  356. Handler.Apply(context, Attribute, Member);
  357. } catch (Exception ex) {
  358. context.Log.Error("Exception thrown when applying attribute {0} on property {1}.{2}: {3}", attrDesc, Member.Entity.Name, Member.MemberName, ex.Message);
  359. }
  360. }
  361. }
  362. #endregion
  363. //SpecialModelAttributes are processed by model builder according to its own logic. Like PrimaryKey attr is already processsed when we were building
  364. // entities. Here we process all other, non-special attributes
  365. private void ProcessNonSpecialModelAttributes() {
  366. //Collect model attributes
  367. var attrTuples = new List<AttributeTuple>();
  368. foreach(var ent in _allEntities) {
  369. var nsAttrs = ent.Attributes.Where(a => !(a is SpecialModelAttribute));
  370. attrTuples.AddRange(nsAttrs.Select(a => new AttributeTuple() { Attribute = a, Entity = ent, Handler = GetAttributeHandler(a)}).Where(t => t.Handler != null));
  371. foreach (var member in ent.Members) {
  372. nsAttrs = member.Attributes.Where(a => !(a is SpecialModelAttribute));
  373. attrTuples.AddRange(nsAttrs.Select(a => new AttributeTuple() { Attribute = a, Member = member, Handler = GetAttributeHandler(a) }).Where(t => t.Handler != null));
  374. }
  375. }//foreach ent
  376. //Sort
  377. attrTuples.Sort(AttributeTuple.CompareOrder);
  378. //Apply
  379. attrTuples.ForEach(t => t.Apply(_attributeContext));
  380. }//method
  381. private IAttributeHandler GetAttributeHandler(Attribute attribute) {
  382. IAttributeHandler handler = attribute as IAttributeHandler;
  383. if (handler != null)
  384. return handler;
  385. CustomAttributeHandler customHandler;
  386. if (_model.AttributeHandlers.TryGetValue(attribute.GetType(), out customHandler))
  387. return customHandler;
  388. return null;
  389. }
  390. private void ValidateIndexes() {
  391. foreach (var entity in _modelInfo.Entities) {
  392. //Clustered indexes
  393. var ciKeys = entity.Keys.FindAll(k => k.KeyType.IsSet(KeyType.Clustered));
  394. switch (ciKeys.Count) {
  395. case 0:
  396. // Unless specific flag is set saying 'no clustered index', we assume that PK is a clustered index, just like SQL Server default
  397. if (!entity.Flags.IsSet(EntityFlags.NoClusteredIndex))
  398. entity.PrimaryKey.KeyType |= KeyType.Clustered;
  399. break;
  400. case 1: //nothing to do
  401. break;
  402. default:
  403. LogError("More than one clustered index specified on entity {0}", entity.FullName);
  404. break;
  405. } //switch
  406. }//foreach entity
  407. }//method
  408. // Some keys may contain members that are entity references;
  409. // We need to replace them with foreign key members (which are values actually saved in database).
  410. // The problem is that we cannot simply expand primary keys, then foreign keys, and then all others
  411. // Primary keys may contain entity references, in effect containing members of foreign keys which should be expanded first
  412. // from primary keys of target references. So this is an iterative multi-pass process of expansion
  413. private void ExpandEntityKeyMembers() {
  414. var allKeys = _modelInfo.Entities.SelectMany(e => e.Keys).Where(k => !k.IsExpanded).ToList();
  415. var currList = allKeys;
  416. //Make 5 rounds
  417. for (int i = 0; i < 10; i++) {
  418. var newList = new List<EntityKeyInfo>();
  419. foreach (var key in currList)
  420. if (!ExpandKey(key)) newList.Add(key);
  421. if (newList.Count == 0)
  422. return; //we are done, all keys are expanded
  423. //set currList from newList, and go for another iteration
  424. currList = newList;
  425. }//for i
  426. //if we are here, we failed to expand all keys
  427. var keyDescr = string.Join(", ", currList);
  428. LogError("Invalid key composition, circular references in keys: {0} ", keyDescr);
  429. }
  430. private bool ExpandKey(EntityKeyInfo key) {
  431. if (key.IsExpanded) return true;
  432. key.ExpandedMembers.Clear();
  433. if (key.KeyType.IsSet(KeyType.ForeignKey))
  434. return ExpandForeignKey(key);
  435. //Any other key type
  436. foreach (var member in key.Members) {
  437. if (member.Kind == MemberKind.EntityRef) {
  438. var fkey = member.ReferenceInfo.FromKey;
  439. if (!fkey.IsExpanded) return false;
  440. key.ExpandedMembers.AddRange(fkey.ExpandedMembers);
  441. } else
  442. key.ExpandedMembers.Add(member);
  443. }//foreach member
  444. key.IsExpanded = true;
  445. key.HasIdentityMember = key.ExpandedMembers.Any(m => m.AutoValueType == AutoType.Identity);
  446. return true;
  447. }
  448. //FK expansion is a special case - we expand members from target expanded members (of target PrimaryKey)
  449. private bool ExpandForeignKey(EntityKeyInfo key) {
  450. var refMember = key.Members[0];
  451. var nullable = refMember.Flags.IsSet(EntityMemberFlags.Nullable);
  452. var isPk = refMember.Flags.IsSet(EntityMemberFlags.PrimaryKey);
  453. var refInfo = refMember.ReferenceInfo;
  454. if (!refInfo.ToKey.IsExpanded)
  455. return false;
  456. var pkMembers = refInfo.ToKey.ExpandedMembers;
  457. // Check if we have explicitly specified names in ForeignKey attribute
  458. string[] fkNames = null;
  459. if (!string.IsNullOrEmpty(refInfo.ForeignKeyColumns)) {
  460. fkNames = refInfo.ForeignKeyColumns.Split(',', ';');
  461. if (fkNames.Length != pkMembers.Count) {
  462. LogError("Invalid ForeignKey specification in property {0}.{1}: # of columns ({2}) does not match # of columns ({3}) in target primary key.",
  463. refMember.Entity.FullName, refMember.MemberName, fkNames.Length, pkMembers.Count);
  464. return true;
  465. }
  466. }
  467. // build members
  468. for (var i = 0; i < pkMembers.Count; i++) {
  469. var targetMember = pkMembers[i];
  470. var fkMemberName = fkNames == null ? refMember.MemberName + "_" + targetMember.MemberName : fkNames[i];
  471. var memberType = targetMember.DataType;
  472. //If reference is nullable, then force member to be nullable too - and flip c# type to nullable
  473. if (nullable && (memberType.IsValueType || memberType.IsEnum)) {
  474. //CLR type is not nullable - flip it to nullable
  475. memberType = ReflectionHelper.GetNullable(memberType);
  476. }
  477. var fkMember = new EntityMemberInfo(key.Entity, MemberKind.Column, fkMemberName, memberType);
  478. fkMember.Flags |= EntityMemberFlags.ForeignKey;
  479. fkMember.ExplicitDbType = targetMember.ExplicitDbType;
  480. if (targetMember.Size > 0)
  481. fkMember.Size = targetMember.Size;
  482. if (targetMember.Flags.IsSet(EntityMemberFlags.AutoValue)) {
  483. fkMember.Flags |= EntityMemberFlags.AutoValue;
  484. }
  485. fkMember.ForeignKeyOwner = refMember;
  486. if (nullable)
  487. fkMember.Flags |= EntityMemberFlags.Nullable;
  488. if (isPk)
  489. fkMember.Flags |= EntityMemberFlags.PrimaryKey;
  490. key.ExpandedMembers.Add(fkMember);
  491. }//foreach targetMember
  492. key.IsExpanded = true;
  493. return true;
  494. }
  495. //Important - this should be done after processing attributes
  496. private void CompleteMembersSetup() {
  497. foreach (var ent in _modelInfo.Entities) {
  498. ent.PersistentValuesCount = 0;
  499. ent.TransientValuesCount = 0;
  500. var hasUpdatableMembers = false;
  501. foreach (var member in ent.Members) {
  502. if (member.Kind == MemberKind.Column) {
  503. member.ValueIndex = ent.PersistentValuesCount++;
  504. if (member.Flags.IsSet(EntityMemberFlags.PrimaryKey))
  505. member.Flags |= EntityMemberFlags.ExcludeFromUpdate;
  506. if (!member.Flags.IsSet(EntityMemberFlags.ExcludeFromUpdate))
  507. hasUpdatableMembers = true;
  508. } else
  509. member.ValueIndex = ent.TransientValuesCount++;
  510. }//foreach member
  511. if (!hasUpdatableMembers)
  512. ent.Flags |= EntityFlags.NoUpdate;
  513. }//foreach ent
  514. }
  515. private void BuildEntityClasses() {
  516. var classBuilder = new EntityClassBuilder();
  517. classBuilder.BuildEntityClasses(_modelInfo);
  518. }
  519. private void CollectEnumTypes() {
  520. var typeSet = new HashSet<Type>();
  521. foreach (var ent in _modelInfo.Entities)
  522. foreach (var member in ent.Members) {
  523. var type = member.DataType;
  524. if (type.IsEnum && !typeSet.Contains(type)) {
  525. typeSet.Add(type);
  526. _modelInfo.AddEnumType(type);
  527. }
  528. }//foreach member
  529. }
  530. EntityCommandBuilder _commandBuilder;
  531. private void BuildCommands() {
  532. _commandBuilder = new EntityCommandBuilder(_modelInfo, _log);
  533. BuildCrudCommands();
  534. BuildCustomCommands();
  535. BuildCommandDescriptiveTags();
  536. _commandBuilder = null;
  537. }
  538. private void BuildCrudCommands() {
  539. foreach (var entity in _modelInfo.Entities) {
  540. var entType = entity.EntityType;
  541. var cmds = entity.CrudCommands = new EntityCrudCommands();
  542. cmds.SelectAll = _commandBuilder.BuildCrudSelectAllCommand(entity);
  543. cmds.SelectAllPaged = _commandBuilder.BuildCrudSelectAllPagedCommand(entity);
  544. cmds.SelectByPrimaryKey = entity.PrimaryKey.SelectByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, entity.PrimaryKey);
  545. cmds.Insert = _commandBuilder.BuildCrudInsertCommand(entity);
  546. if (!entity.Flags.IsSet(EntityFlags.NoUpdate))
  547. cmds.Update = _commandBuilder.BuildCrudUpdateCommand(entity);
  548. cmds.Delete = _commandBuilder.BuildCrudDeleteCommand(entity);
  549. // Special member-attached commands
  550. foreach (var member in entity.Members) {
  551. switch (member.Kind) {
  552. case MemberKind.EntityRef:
  553. var fk = member.ReferenceInfo.FromKey;
  554. fk.SelectByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, fk);
  555. fk.SelectTopOneByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, fk, topOne: true);
  556. break;
  557. case MemberKind.EntityList:
  558. var listInfo = member.ChildListInfo;
  559. if (listInfo.RelationType == EntityRelationType.ManyToMany)
  560. listInfo.SelectManyToManyCommand = _commandBuilder.BuildSelectByKeyManyToMany(listInfo);
  561. break;
  562. }//switch
  563. }//foreach member
  564. }//foreach ent
  565. }//method
  566. private void BuildCustomCommands() {
  567. foreach (var module in _modelInfo.Model.Modules)
  568. foreach (var cmd in module.CustomCommands) {
  569. _commandBuilder.ProcessCustomCommand(cmd);
  570. _modelInfo.AddCommand(cmd);
  571. }
  572. }
  573. private void BuildCommandDescriptiveTags() {
  574. foreach (var cmd in _modelInfo.Commands)
  575. _commandBuilder.BuildCommandDescriptiveTag(cmd);
  576. }
  577. // Note about special case: members with CascadeDelete attribute.
  578. // Demo case setup. 3 entities, IBook, IAuthor, and IBookAuthor as link table; IBookAuthor references IBook with CascadeDelete,
  579. // and references IAuthor without cascade.
  580. // Because of CascadeDelete, when we delete IBook and IBookAuthor in one operation, the order of IBook vs IBookAuthor does not matter:
  581. // even if IBook comes before IBookAuthor, delete will succeed because of cascade delete of IBookAuthor.
  582. // The problem case is when we are deleting IBook and IAuthor, without explicitly deleting IBookAuthor.
  583. // In this case IAuthor should be deleted after IBook - otherwise still existing IBookAuthor record
  584. // would prevent it from deleting. As there's no explicit IBookAuthor in delete set, and there's
  585. // no FK links between IAuthor and IBook - then they may come to delete in any order, and trans might fail.
  586. // The solution is to introduce an extra direct link between IBook and IAuthor in abstract SCC node tree.
  587. // This extra link will ensure proper topological ordering of IBook and IAuthor.
  588. // Note that we still need to add link between IBookAuthor and IBook - for proper ordering of inserts.
  589. private void ComputeTopologicalIndexes() {
  590. // Run SCC algorithm
  591. var g = new Graph();
  592. //Perform SCC analysis.
  593. foreach (var ent in _modelInfo.Entities)
  594. ent.SccVertex = g.Add(ent);
  595. //setup links
  596. foreach (var ent in _modelInfo.Entities) {
  597. var cascadeMembers = new List<EntityMemberInfo>();
  598. var nonCascadeMembers = new List<EntityMemberInfo>();
  599. foreach (var member in ent.RefMembers) {
  600. var targetEnt = member.ReferenceInfo.ToKey.Entity;
  601. ent.SccVertex.AddLink(targetEnt.SccVertex);
  602. if (member.Flags.IsSet(EntityMemberFlags.CascadeDelete))
  603. cascadeMembers.Add(member);
  604. else
  605. nonCascadeMembers.Add(member);
  606. }//foreach member
  607. //For all cascade member (IBookAuthor.Author) targets add direct links to all non-cascade member targets
  608. // (from IBook to IAuthor)
  609. foreach (var cascMember in cascadeMembers) {
  610. var cascTarget = cascMember.ReferenceInfo.ToKey.Entity;
  611. foreach (var nonCascMember in nonCascadeMembers) {
  612. var nonCascTarget = nonCascMember.ReferenceInfo.ToKey.Entity;
  613. cascTarget.SccVertex.AddLink(nonCascTarget.SccVertex);
  614. }
  615. }//foreach cascMember
  616. }//foreach t
  617. //Build SCC
  618. var sccCount = g.BuildScc();
  619. //read scc index and clear vertex fields
  620. foreach (var ent in _modelInfo.Entities) {
  621. var v = ent.SccVertex;
  622. ent.TopologicalIndex = v.SccIndex;
  623. if (v.NonTrivialGroup)
  624. ent.Flags |= EntityFlags.TopologicalGroupNonTrivial;
  625. ent.SccVertex = null;
  626. }
  627. }
  628. //Builds entity.InitialValues array that will be used to initialize new entities
  629. private void BuildDefaultInitialValues() {
  630. var zero8bytes = new byte[] {0,0,0,0, 0,0,0,0}; //8 zero bytes
  631. foreach (var entity in _modelInfo.Entities) {
  632. entity.InitialColumnValues = new object[entity.PersistentValuesCount];
  633. foreach (var member in entity.Members)
  634. switch (member.Kind) {
  635. case MemberKind.Column:
  636. object dftValue;
  637. if (member.Flags.IsSet(EntityMemberFlags.ForeignKey))
  638. dftValue = DBNull.Value;
  639. else if (member.AutoValueType == AutoType.RowVersion) {
  640. dftValue = zero8bytes;
  641. } else
  642. dftValue = member.DataType.IsValueType ? Activator.CreateInstance(member.DataType) : DBNull.Value;
  643. member.DefaultValue = member.DefaultValue = dftValue;
  644. entity.InitialColumnValues[member.ValueIndex] = dftValue;
  645. break;
  646. case MemberKind.Transient:
  647. member.DefaultValue = member.DeniedValue = null;
  648. break;
  649. case MemberKind.EntityRef:
  650. member.DefaultValue = member.DeniedValue = DBNull.Value;
  651. break;
  652. case MemberKind.EntityList:
  653. member.DefaultValue = null;
  654. member.DeniedValue = ReflectionHelper.CreateReadOnlyCollection(member.ChildListInfo.TargetEntity.EntityType);
  655. break;
  656. }//switch
  657. } // foreach entity
  658. }
  659. }//class
  660. }