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