/1.Framework/Vita.Core/Entities/Model/Construction/EntityModelInfoBuilder.cs
C# | 717 lines | 569 code | 57 blank | 91 comment | 119 complexity | 91d34904e6cb06a350f545da8266c623 MD5 | raw file
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ComponentModel;
- using System.Reflection;
- using System.Collections;
-
- using Vita.Common;
- using Vita.Common.Graphs;
- using Vita.Entities;
- using Vita.Entities.Runtime;
- using Vita.Logging;
-
- namespace Vita.Entities.Model.Construction {
-
-
- public class EntityModelInfoBuilder {
- Log _log;
- EntityModel _model;
- EntityModelInfo _modelInfo;
- //Temporary lists
- IList<EntityInfo> _allEntities; // initial list of all entities
- Dictionary<Type, EntityInfo> _entitiesByType;
- AttributeContext _attributeContext;
-
- /// <summary>Builds runtime entity model info. </summary>
- public static EntityModelInfo Build(EntityModel model, Log log) {
- var modelBuilder = new EntityModelInfoBuilder();
- var result = modelBuilder.BuildModelInfo(model, log);
- if (log.HasErrors)
- throw new ActivationException("Entity model build failed.", log.GetAllAsText());
- return result;
- }
-
-
- private EntityModelInfo BuildModelInfo(EntityModel model, Log log) {
- _log = log;
- _model = model;
- _modelInfo = model.ModelInfo = new EntityModelInfo(_model);
- _attributeContext = new AttributeContext(this._modelInfo, _log);
- _allEntities = new List<EntityInfo>();
- _entitiesByType = new Dictionary<Type, EntityInfo>();
-
- //First add entities that are not registered explicitly but referenced through other entities' properties
- CollectInitialEntities();
- VerifyEntityReferences();
- ProcessReplacements();
- RegisterEntities();
- BuildEntityMembers(); //processes column, size attribute
-
- //Attributes
- CollectAllAttributes();
- // Basic infrastructure - PKs, FKs
- ProcessSpecialAttributes();
- if (Failed())
- return _modelInfo;
- // We have to run expansion twice. First time right after processing special attributes - to expand foreign keys.
- // 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.
- // The second time we expand key members to expand indexes themselves.
- ExpandEntityKeyMembers();
- ProcessNonSpecialModelAttributes();
- ExpandEntityKeyMembers();
-
- ValidateIndexes();
- CompleteMembersSetup();
- BuildDefaultInitialValues();
- BuildEntityClasses();
- ComputeTopologicalIndexes();
- CollectEnumTypes();
- if (Failed())
- return _modelInfo;
- //commands
- BuildCommands();
- return _modelInfo;
- }//method
-
- internal void LogError(string message, params object[] args) {
- _log.Error(message, args);
- }
- private bool Failed() {
- return _log.HasErrors;
- }
-
- //Collects initially registered entities plus all entities found in properties of registered entities;
- // creates EntityInfo objects for each entity and adds them to Model's Entities set.
- private void CollectInitialEntities() {
- // Collect initial entities
- foreach (var module in _modelInfo.Model.Modules)
- foreach (var entType in module.Entities)
- AddEntity(entType, module);
- }//method
-
- // 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;
- // also the list might be many-to-many, so we need to check Link entity specified by ManyToMany attribute.
- private void VerifyEntityReferences(int startIndex = 0) {
- //initialize all-entities list
- Type typeInList;
- // Iterate through the list using index and append new entities as we discover them
- for (int i = startIndex; i < _allEntities.Count; i++) {
- var entInfo = _allEntities[i];
- var props = entInfo.EntityType.GetAllProperties();
- foreach (var prop in props) {
- var propType = prop.PropertyType;
- if (propType.IsEntity())
- CheckReferencedEntity(propType, prop.Name, entInfo);
- else if (ReflectionHelper.IsEntityListInterface(propType, out typeInList)) {
- CheckReferencedEntity(typeInList, prop.Name, entInfo);
- //For many-to-many, we may need to add LinkEntity
- var m2mAttr = prop.GetAttribute<ManyToManyAttribute>();
- if (m2mAttr != null && m2mAttr.LinkEntity.IsEntity())
- CheckReferencedEntity(m2mAttr.LinkEntity, prop.Name, entInfo);
- }// else if IsEntityList
- }//foreac prop
- }//for i
- }//method
-
- private EntityInfo GetEntity(Type entityType) {
- EntityInfo entInfo;
- _entitiesByType.TryGetValue(entityType, out entInfo);
- return entInfo;
- }
-
- private void CheckReferencedEntity(Type entityType, string propertyName, EntityInfo owner) {
- EntityInfo entInfo;
- if (!_entitiesByType.TryGetValue(entityType, out entInfo))
- LogError("Property {0}.{1}: referenced entity type {2} is not registered as an entity.", owner.EntityType.Name, propertyName, entityType.Name);
- }
-
- private EntityInfo AddEntity(Type entityType, EntityModule module) {
- EntityInfo entInfo;
- if (_entitiesByType.TryGetValue(entityType, out entInfo))
- return entInfo;
- entInfo = new EntityInfo(module, entityType);
- _allEntities.Add(entInfo);
- _entitiesByType[entityType] = entInfo;
- return entInfo;
- }
-
- private void ProcessReplacements() {
- var oldCount = _allEntities.Count;
- // 1. Go thru replacements, find/create entity info for "new" entities, and register this entity info under the key of replaced entity type
- foreach (var replInfo in _modelInfo.Model.Replacements) {
- var oldEntInfo = GetEntity(replInfo.ReplacedType);
- Util.Check(oldEntInfo != null, "Replaced entity ({0}) must be explicitly registered in one of the modules.", replInfo.ReplacedType);
- var newType = replInfo.NewType;
- var newEntInfo = _modelInfo.GetEntityInfo(replInfo.NewType);
- if (newEntInfo == null) //not registered. Means we import this entity into original module
- newEntInfo = AddEntity(replInfo.NewType, oldEntInfo.Module);
- oldEntInfo.ReplacedBy = newEntInfo;
- }
- // 2. Scan newly added entities for more referenced entities
- VerifyEntityReferences(oldCount);
- // 3. Trace replacedBy reference, find final replacing type and register entity info for final type under the "replaced type" key
- foreach (var entInfo in _allEntities) {
- if (entInfo.ReplacedBy == null) continue;
- entInfo.ReplacedBy = GetFinalReplacement(entInfo);
- entInfo.ReplacedBy.ReplacesTypes.Add(entInfo.EntityType);
- }
- }
-
- private void RegisterEntities() {
- foreach (var entInfo in _allEntities) {
- var otherEnt = _modelInfo.GetEntityInfo(entInfo.FullName);
- if (otherEnt != null) {
- LogError("Duplicate entity full name: entity {0} is already registered.", entInfo.FullName);
- continue;
- }
- _modelInfo.RegisterEntity(entInfo);
- EntityModelInfo.RegisterEntity(entInfo.EntityType, entInfo);
- //Also save entity info for replaced types
- foreach (var t in entInfo.ReplacesTypes)
- EntityModelInfo.RegisterEntity(t, entInfo);
- }
- }
-
- //Note: returns entityInfo if there is no replacement
- private EntityInfo GetFinalReplacement(EntityInfo entityInfo) {
- var current = entityInfo;
- while (current.ReplacedBy != null)
- current = current.ReplacedBy;
- return current;
- }
-
- private void BuildEntityMembers() {
- //Now build entity info for type in final types
- foreach (var entInfo in _modelInfo.Entities) {
- //Create data members
- var props = entInfo.EntityType.GetAllProperties();
- EntityMemberInfo member;
- foreach (var prop in props) {
- MemberKind kind;
- if (TryGetMemberKind(entInfo, prop, out kind)) {
- member = new EntityMemberInfo(entInfo, kind, prop); //member is added to members automatically
- }
- }
- }//foreach entType
- }
-
- private bool TryGetMemberKind(EntityInfo entity, PropertyInfo property, out MemberKind kind) {
- var dataType = property.PropertyType;
- kind = MemberKind.Column;
- if (dataType.IsValueType || dataType == typeof(string))
- return true;
- var genType = dataType.IsGenericType ? dataType.GetGenericTypeDefinition() : null;
- if (genType == typeof(Nullable<>))
- return true;
- if (dataType.IsEntity()) {
- kind = MemberKind.EntityRef;
- return true;
- }
- if (genType == typeof(IList<>)) {
- kind = MemberKind.EntityList;
- return true;
- }
- // properly report common mistake
- if (genType == typeof(List<>)) {
- this.LogError("Invalid entity member {0}.{1}. Use IList<T> interface for list members. ", entity.Name, property.Name);
- return false;
- }
- //default: Column
- return true; //Column; there are some specific types that turn into column (Binary for ex)
- }
-
- private void CollectAllAttributes() {
- //1. Collection attributes on entities and members themselves
- foreach (var entity in _modelInfo.Entities) {
- entity.Attributes.AddRange(entity.EntityType.GetAllAttributes());
- foreach (var member in entity.Members)
- member.AddAttributes(member.Property.GetCustomAttributes(true));
- }//foreach entity
-
- // 2. Collect attributes from companion types
- foreach (var companion in _modelInfo.Model.CompanionTypes) {
- Type entType = null;
- // First see if ForEntity attr is specified on companion type; if not, find target entity in inheritance chain
- // - companion types usually inherit from entities they accompany
- var feAtt = companion.GetAttribute<ForEntityAttribute>();
- if (feAtt != null) {
- entType = feAtt.EntityType;
- }
- if (entType == null)
- entType = FindBaseEntity(companion);
- if (entType == null) {
- LogError("Could not find target Entity type for companion type {0}.", companion);
- continue;
- }
- //Find entity info
- var entity = _modelInfo.GetEntityInfo(entType);
- if (entity == null) {
- LogError("Entity type {0} for companion type {1} is not registered as Entity.", entType, companion);
- continue;
- }
- entity.CompanionTypes.Add(companion);
- //Add attributes
- entity.Attributes.AddRange(companion.GetAllAttributes(inherit: false));
- //Add attributes from members of Companion Type. Note we use DeclaredOnly to catch only those that are declared
- // by the type itself, not inherited.
- const BindingFlags propBindingFlags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.DeclaredOnly;
- var props = companion.GetProperties(propBindingFlags);
- foreach (var prop in props) {
- var member = entity.GetMember(prop.Name);
- if (member != null)
- member.AddAttributes(prop.GetCustomAttributes(false));
- }//foreach prop
- }//foreach cpmnType
- }
-
- private Type FindBaseEntity(Type type) {
- var types = type.GetInterfaces();
- var ent = types.FirstOrDefault(t => t.IsEntity());
- return ent;
- }
-
- private void ProcessSpecialAttributes() {
- // Pass 1
- foreach (var entity in _modelInfo.Entities) {
- var attr = entity.GetAttribute<EntityAttribute>();
- if (attr == null)
- Util.Throw("Entity {0}: EntityAttribute not found.", entity.FullName);
- attr.Apply(_attributeContext, attr, entity);
- foreach (var member in entity.Members) {
- //Apply NoColumn attribute - it might change member kind
- FindAndApplyAttribute<NoColumnAttribute>(member);
- // Apply nullable attribute
- FindAndApplyAttribute<NullableAttribute>(member);
- // Set default for string size, might be changed later by attributes
- if (member.DataType == typeof(string))
- member.Size = entity.Module.Settings.DefaultStringLength;
- //apply Size attr if present; Column attr might overwrite size value
- FindAndApplyAttribute<SizeAttribute>(member);
- //apply column attr if present; we apply it here rather than inside switch, to detect improper use
- FindAndApplyAttribute<ColumnAttribute>(member);
- }//foreach member
- }//foreach entity -- end pass 1
- if (Failed())
- return;
-
- // Pass 2 - primary keys. PK may reference members using column names, so we do it after applying Column attribute to all members
- foreach (var entity in _modelInfo.Entities) {
- bool pkAttrFound = false;
- var entPkAttr = entity.GetAttribute<PrimaryKeyAttribute>();
- if (entPkAttr != null) {
- entPkAttr.Apply(_attributeContext, entPkAttr, entity);
- pkAttrFound = true;
- }
- foreach (var member in entity.Members) {
- pkAttrFound |= FindAndApplyAttribute<PrimaryKeyAttribute>(member);
- }
- if (pkAttrFound)
- continue; //next entity
- // if PK attr is not found, try matching by default PK member name
- var dftPk = entity.Module.Settings.DefaultPrimaryKeyProperty;
- if (!string.IsNullOrWhiteSpace(dftPk)) {
- var dftPkMember = entity.FindMemberOrColumn(dftPk);
- if (dftPkMember != null) {
- // If match, create artificial pk attr and apply it.
- var pkAttr = new PrimaryKeyAttribute(dftPk);
- entity.Attributes.Add(pkAttr);
- pkAttr.Apply(_attributeContext, pkAttr, entity);
- }
- }
- if (entity.PrimaryKey == null) {
- LogError("Entity {0} has no PrimaryKey attribute, and no property matches default primary key name.", entity.FullName);
- }
- }//foreach entity
-
- // Pass 3 - entity references. Relies on PKs already created
- foreach (var entity in _modelInfo.Entities) {
- foreach (var member in entity.Members) {
- if (member.Kind != MemberKind.EntityRef) continue;
- var erAttr = member.GetAttribute<EntityRefAttribute>();
- if (erAttr == null) {
- erAttr = new EntityRefAttribute();
- member.Attributes.Add(erAttr);
- }
- erAttr.Apply(_attributeContext, erAttr, member);
- }//foreach member
- }// foreach entity
-
- // Pass 4 - entity lists. Assumes entity references are processed
- foreach (var entity in _modelInfo.Entities) {
- foreach (var member in entity.Members) {
- if (member.Kind != MemberKind.EntityList) continue;
- // Check many-to-many
- if (FindAndApplyAttribute<ManyToManyAttribute>(member)) continue;
- // try to find and apply OneToMany
- if (FindAndApplyAttribute<OneToManyAttribute>(member)) continue;
- // If neither attribute is found, create OneToMany and apply it
- var attr = new OneToManyAttribute();
- member.Attributes.Add(attr);
- attr.Apply(_attributeContext, attr, member);
- }
- }
-
- }//method
-
- private bool FindAndApplyAttribute<TAttr>(EntityMemberInfo member) where TAttr : ModelAttribute {
- var attr = member.GetAttribute<TAttr>();
- if (attr == null) return false;
- attr.Apply(_attributeContext, attr, member);
- return true;
- }
-
-
-
- #region AttributeTuple inner class
- class AttributeTuple {
- public IAttributeHandler Handler;
- public Attribute Attribute;
- public EntityInfo Entity;
- public EntityMemberInfo Member;
-
- public override string ToString() {
- return Attribute.ToString();
- }
- public static int CompareOrder(AttributeTuple x, AttributeTuple y) {
- return ((int)x.Handler.ApplyOrder).CompareTo((int)y.Handler.ApplyOrder);
- }
- public void Apply(AttributeContext context) {
- var attrDesc = Attribute.GetType().Name;
- if (this.Entity != null)
- try {
- this.Handler.Apply(context, Attribute, Entity);
- } catch (Exception ex) {
- context.Log.Error("Exception thrown when applying attribute {0} on entity {1}: {2}", attrDesc, Entity.Name, ex.Message);
- }
- if (this.Member != null)
- try {
- Handler.Apply(context, Attribute, Member);
- } catch (Exception ex) {
- context.Log.Error("Exception thrown when applying attribute {0} on property {1}.{2}: {3}", attrDesc, Member.Entity.Name, Member.MemberName, ex.Message);
- }
- }
- }
- #endregion
-
- //SpecialModelAttributes are processed by model builder according to its own logic. Like PrimaryKey attr is already processsed when we were building
- // entities. Here we process all other, non-special attributes
- private void ProcessNonSpecialModelAttributes() {
- //Collect model attributes
- var attrTuples = new List<AttributeTuple>();
- foreach(var ent in _allEntities) {
- var nsAttrs = ent.Attributes.Where(a => !(a is SpecialModelAttribute));
- attrTuples.AddRange(nsAttrs.Select(a => new AttributeTuple() { Attribute = a, Entity = ent, Handler = GetAttributeHandler(a)}).Where(t => t.Handler != null));
- foreach (var member in ent.Members) {
- nsAttrs = member.Attributes.Where(a => !(a is SpecialModelAttribute));
- attrTuples.AddRange(nsAttrs.Select(a => new AttributeTuple() { Attribute = a, Member = member, Handler = GetAttributeHandler(a) }).Where(t => t.Handler != null));
- }
- }//foreach ent
- //Sort
- attrTuples.Sort(AttributeTuple.CompareOrder);
- //Apply
- attrTuples.ForEach(t => t.Apply(_attributeContext));
- }//method
-
- private IAttributeHandler GetAttributeHandler(Attribute attribute) {
- IAttributeHandler handler = attribute as IAttributeHandler;
- if (handler != null)
- return handler;
- CustomAttributeHandler customHandler;
- if (_model.AttributeHandlers.TryGetValue(attribute.GetType(), out customHandler))
- return customHandler;
- return null;
- }
-
- private void ValidateIndexes() {
- foreach (var entity in _modelInfo.Entities) {
- //Clustered indexes
- var ciKeys = entity.Keys.FindAll(k => k.KeyType.IsSet(KeyType.Clustered));
- switch (ciKeys.Count) {
- case 0:
- // Unless specific flag is set saying 'no clustered index', we assume that PK is a clustered index, just like SQL Server default
- if (!entity.Flags.IsSet(EntityFlags.NoClusteredIndex))
- entity.PrimaryKey.KeyType |= KeyType.Clustered;
- break;
- case 1: //nothing to do
- break;
- default:
- LogError("More than one clustered index specified on entity {0}", entity.FullName);
- break;
- } //switch
- }//foreach entity
- }//method
-
- // Some keys may contain members that are entity references;
- // We need to replace them with foreign key members (which are values actually saved in database).
- // The problem is that we cannot simply expand primary keys, then foreign keys, and then all others
- // Primary keys may contain entity references, in effect containing members of foreign keys which should be expanded first
- // from primary keys of target references. So this is an iterative multi-pass process of expansion
- private void ExpandEntityKeyMembers() {
- var allKeys = _modelInfo.Entities.SelectMany(e => e.Keys).Where(k => !k.IsExpanded).ToList();
- var currList = allKeys;
- //Make 5 rounds
- for (int i = 0; i < 10; i++) {
- var newList = new List<EntityKeyInfo>();
- foreach (var key in currList)
- if (!ExpandKey(key)) newList.Add(key);
- if (newList.Count == 0)
- return; //we are done, all keys are expanded
- //set currList from newList, and go for another iteration
- currList = newList;
- }//for i
- //if we are here, we failed to expand all keys
- var keyDescr = string.Join(", ", currList);
- LogError("Invalid key composition, circular references in keys: {0} ", keyDescr);
- }
-
- private bool ExpandKey(EntityKeyInfo key) {
- if (key.IsExpanded) return true;
- key.ExpandedMembers.Clear();
- if (key.KeyType.IsSet(KeyType.ForeignKey))
- return ExpandForeignKey(key);
- //Any other key type
- foreach (var member in key.Members) {
- if (member.Kind == MemberKind.EntityRef) {
- var fkey = member.ReferenceInfo.FromKey;
- if (!fkey.IsExpanded) return false;
- key.ExpandedMembers.AddRange(fkey.ExpandedMembers);
- } else
- key.ExpandedMembers.Add(member);
- }//foreach member
- key.IsExpanded = true;
- key.HasIdentityMember = key.ExpandedMembers.Any(m => m.AutoValueType == AutoType.Identity);
- return true;
- }
-
- //FK expansion is a special case - we expand members from target expanded members (of target PrimaryKey)
- private bool ExpandForeignKey(EntityKeyInfo key) {
- var refMember = key.Members[0];
- var nullable = refMember.Flags.IsSet(EntityMemberFlags.Nullable);
- var isPk = refMember.Flags.IsSet(EntityMemberFlags.PrimaryKey);
- var refInfo = refMember.ReferenceInfo;
- if (!refInfo.ToKey.IsExpanded)
- return false;
- var pkMembers = refInfo.ToKey.ExpandedMembers;
- // Check if we have explicitly specified names in ForeignKey attribute
- string[] fkNames = null;
- if (!string.IsNullOrEmpty(refInfo.ForeignKeyColumns)) {
- fkNames = refInfo.ForeignKeyColumns.Split(',', ';');
- if (fkNames.Length != pkMembers.Count) {
- LogError("Invalid ForeignKey specification in property {0}.{1}: # of columns ({2}) does not match # of columns ({3}) in target primary key.",
- refMember.Entity.FullName, refMember.MemberName, fkNames.Length, pkMembers.Count);
- return true;
- }
- }
- // build members
- for (var i = 0; i < pkMembers.Count; i++) {
- var targetMember = pkMembers[i];
- var fkMemberName = fkNames == null ? refMember.MemberName + "_" + targetMember.MemberName : fkNames[i];
- var memberType = targetMember.DataType;
- //If reference is nullable, then force member to be nullable too - and flip c# type to nullable
- if (nullable && (memberType.IsValueType || memberType.IsEnum)) {
- //CLR type is not nullable - flip it to nullable
- memberType = ReflectionHelper.GetNullable(memberType);
- }
- var fkMember = new EntityMemberInfo(key.Entity, MemberKind.Column, fkMemberName, memberType);
- fkMember.Flags |= EntityMemberFlags.ForeignKey;
- fkMember.ExplicitDbType = targetMember.ExplicitDbType;
- if (targetMember.Size > 0)
- fkMember.Size = targetMember.Size;
- if (targetMember.Flags.IsSet(EntityMemberFlags.AutoValue)) {
- fkMember.Flags |= EntityMemberFlags.AutoValue;
- }
- fkMember.ForeignKeyOwner = refMember;
- if (nullable)
- fkMember.Flags |= EntityMemberFlags.Nullable;
- if (isPk)
- fkMember.Flags |= EntityMemberFlags.PrimaryKey;
- key.ExpandedMembers.Add(fkMember);
- }//foreach targetMember
- key.IsExpanded = true;
- return true;
- }
-
- //Important - this should be done after processing attributes
- private void CompleteMembersSetup() {
- foreach (var ent in _modelInfo.Entities) {
- ent.PersistentValuesCount = 0;
- ent.TransientValuesCount = 0;
- var hasUpdatableMembers = false;
- foreach (var member in ent.Members) {
- if (member.Kind == MemberKind.Column) {
- member.ValueIndex = ent.PersistentValuesCount++;
- if (member.Flags.IsSet(EntityMemberFlags.PrimaryKey))
- member.Flags |= EntityMemberFlags.ExcludeFromUpdate;
- if (!member.Flags.IsSet(EntityMemberFlags.ExcludeFromUpdate))
- hasUpdatableMembers = true;
- } else
- member.ValueIndex = ent.TransientValuesCount++;
- }//foreach member
- if (!hasUpdatableMembers)
- ent.Flags |= EntityFlags.NoUpdate;
- }//foreach ent
- }
-
- private void BuildEntityClasses() {
- var classBuilder = new EntityClassBuilder();
- classBuilder.BuildEntityClasses(_modelInfo);
- }
-
- private void CollectEnumTypes() {
- var typeSet = new HashSet<Type>();
- foreach (var ent in _modelInfo.Entities)
- foreach (var member in ent.Members) {
- var type = member.DataType;
- if (type.IsEnum && !typeSet.Contains(type)) {
- typeSet.Add(type);
- _modelInfo.AddEnumType(type);
- }
- }//foreach member
- }
-
-
- EntityCommandBuilder _commandBuilder;
- private void BuildCommands() {
- _commandBuilder = new EntityCommandBuilder(_modelInfo, _log);
- BuildCrudCommands();
- BuildCustomCommands();
- BuildCommandDescriptiveTags();
- _commandBuilder = null;
- }
-
- private void BuildCrudCommands() {
- foreach (var entity in _modelInfo.Entities) {
- var entType = entity.EntityType;
- var cmds = entity.CrudCommands = new EntityCrudCommands();
- cmds.SelectAll = _commandBuilder.BuildCrudSelectAllCommand(entity);
- cmds.SelectAllPaged = _commandBuilder.BuildCrudSelectAllPagedCommand(entity);
- cmds.SelectByPrimaryKey = entity.PrimaryKey.SelectByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, entity.PrimaryKey);
- cmds.Insert = _commandBuilder.BuildCrudInsertCommand(entity);
- if (!entity.Flags.IsSet(EntityFlags.NoUpdate))
- cmds.Update = _commandBuilder.BuildCrudUpdateCommand(entity);
- cmds.Delete = _commandBuilder.BuildCrudDeleteCommand(entity);
-
- // Special member-attached commands
- foreach (var member in entity.Members) {
- switch (member.Kind) {
- case MemberKind.EntityRef:
- var fk = member.ReferenceInfo.FromKey;
- fk.SelectByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, fk);
- fk.SelectTopOneByKeyCommand = _commandBuilder.BuildCrudSelectByKeyCommand(entity, fk, topOne: true);
- break;
- case MemberKind.EntityList:
- var listInfo = member.ChildListInfo;
- if (listInfo.RelationType == EntityRelationType.ManyToMany)
- listInfo.SelectManyToManyCommand = _commandBuilder.BuildSelectByKeyManyToMany(listInfo);
- break;
- }//switch
- }//foreach member
- }//foreach ent
- }//method
-
- private void BuildCustomCommands() {
- foreach (var module in _modelInfo.Model.Modules)
- foreach (var cmd in module.CustomCommands) {
- _commandBuilder.ProcessCustomCommand(cmd);
- _modelInfo.AddCommand(cmd);
- }
- }
-
- private void BuildCommandDescriptiveTags() {
- foreach (var cmd in _modelInfo.Commands)
- _commandBuilder.BuildCommandDescriptiveTag(cmd);
- }
-
- // Note about special case: members with CascadeDelete attribute.
- // Demo case setup. 3 entities, IBook, IAuthor, and IBookAuthor as link table; IBookAuthor references IBook with CascadeDelete,
- // and references IAuthor without cascade.
- // Because of CascadeDelete, when we delete IBook and IBookAuthor in one operation, the order of IBook vs IBookAuthor does not matter:
- // even if IBook comes before IBookAuthor, delete will succeed because of cascade delete of IBookAuthor.
- // The problem case is when we are deleting IBook and IAuthor, without explicitly deleting IBookAuthor.
- // In this case IAuthor should be deleted after IBook - otherwise still existing IBookAuthor record
- // would prevent it from deleting. As there's no explicit IBookAuthor in delete set, and there's
- // no FK links between IAuthor and IBook - then they may come to delete in any order, and trans might fail.
- // The solution is to introduce an extra direct link between IBook and IAuthor in abstract SCC node tree.
- // This extra link will ensure proper topological ordering of IBook and IAuthor.
- // Note that we still need to add link between IBookAuthor and IBook - for proper ordering of inserts.
- private void ComputeTopologicalIndexes() {
- // Run SCC algorithm
- var g = new Graph();
- //Perform SCC analysis.
- foreach (var ent in _modelInfo.Entities)
- ent.SccVertex = g.Add(ent);
- //setup links
- foreach (var ent in _modelInfo.Entities) {
- var cascadeMembers = new List<EntityMemberInfo>();
- var nonCascadeMembers = new List<EntityMemberInfo>();
- foreach (var member in ent.RefMembers) {
- var targetEnt = member.ReferenceInfo.ToKey.Entity;
- ent.SccVertex.AddLink(targetEnt.SccVertex);
- if (member.Flags.IsSet(EntityMemberFlags.CascadeDelete))
- cascadeMembers.Add(member);
- else
- nonCascadeMembers.Add(member);
- }//foreach member
- //For all cascade member (IBookAuthor.Author) targets add direct links to all non-cascade member targets
- // (from IBook to IAuthor)
- foreach (var cascMember in cascadeMembers) {
- var cascTarget = cascMember.ReferenceInfo.ToKey.Entity;
- foreach (var nonCascMember in nonCascadeMembers) {
- var nonCascTarget = nonCascMember.ReferenceInfo.ToKey.Entity;
- cascTarget.SccVertex.AddLink(nonCascTarget.SccVertex);
- }
- }//foreach cascMember
- }//foreach t
-
- //Build SCC
- var sccCount = g.BuildScc();
- //read scc index and clear vertex fields
- foreach (var ent in _modelInfo.Entities) {
- var v = ent.SccVertex;
- ent.TopologicalIndex = v.SccIndex;
- if (v.NonTrivialGroup)
- ent.Flags |= EntityFlags.TopologicalGroupNonTrivial;
- ent.SccVertex = null;
- }
- }
-
- //Builds entity.InitialValues array that will be used to initialize new entities
- private void BuildDefaultInitialValues() {
- var zero8bytes = new byte[] {0,0,0,0, 0,0,0,0}; //8 zero bytes
- foreach (var entity in _modelInfo.Entities) {
- entity.InitialColumnValues = new object[entity.PersistentValuesCount];
- foreach (var member in entity.Members)
- switch (member.Kind) {
- case MemberKind.Column:
- object dftValue;
- if (member.Flags.IsSet(EntityMemberFlags.ForeignKey))
- dftValue = DBNull.Value;
- else if (member.AutoValueType == AutoType.RowVersion) {
- dftValue = zero8bytes;
- } else
- dftValue = member.DataType.IsValueType ? Activator.CreateInstance(member.DataType) : DBNull.Value;
- member.DefaultValue = member.DefaultValue = dftValue;
- entity.InitialColumnValues[member.ValueIndex] = dftValue;
- break;
- case MemberKind.Transient:
- member.DefaultValue = member.DeniedValue = null;
- break;
- case MemberKind.EntityRef:
- member.DefaultValue = member.DeniedValue = DBNull.Value;
- break;
- case MemberKind.EntityList:
- member.DefaultValue = null;
- member.DeniedValue = ReflectionHelper.CreateReadOnlyCollection(member.ChildListInfo.TargetEntity.EntityType);
- break;
- }//switch
-
- } // foreach entity
- }
-
-
- }//class
-
- }
-