/// <summary> /// Initializes a new instance. /// </summary> internal ChangeEntry(ChangeType changeType, IUberMap map, object entity) { UberMap = map; ChangeType = changeType; Entity = entity; MetaEntity = MetaEntity.Locate(entity); }
/// <summary> /// Captures the current child entities of this instance for the given member. /// </summary> internal static void CaptureMetaMemberChilds(this MetaEntity meta, IUberMember member) { var entity = meta.Entity; if (member.DependencyMode == MemberDependencyMode.Child && member.ElementInfo.CanRead && member.ElementInfo.ElementType.IsListAlike()) { var type = member.ElementInfo.ElementType.ListAlikeMemberType(); if (type != null && (type.IsClass || type.IsInterface)) { HashSet <object> portion = null; if (!meta.ChildDependencies.TryGetValue(member.Name, out portion)) { meta.ChildDependencies.Add(member.Name, (portion = new HashSet <object>())); } portion.Clear(); if (meta.UberMap.Repository.TrackChildEntities) { var iter = member.ElementInfo.GetValue(entity) as IEnumerable; foreach (var obj in iter) { if (obj != null) { portion.Add(obj); } } } } } }
/// <summary> /// Returns the meta entity associated with the given object, creating it if needed and /// requested. Returns null otherwise. /// </summary> /// <param name="entity">The object to obtain its meta entity from.</param> /// <param name="create">True to create the meta entity if it did not exist.</param> /// <returns>The requested meta entity, or null.</returns> public static MetaEntity Locate(object entity, bool create = true) { if (entity == null) throw new ArgumentNullException("entity", "Entity cannot be null."); var type = entity.GetType(); if (!type.IsClass) throw new InvalidOperationException( "Entity '{0}' of type '{1}' is not a class.".FormatWith(entity.Sketch(), type.EasyName())); lock (entity) // Yes, I know... but I want to use at most a local lock on the entity! { AttributeCollection list = TypeDescriptor.GetAttributes(entity); MetaEntity meta = list.OfType<MetaEntity>().FirstOrDefault(); if (meta == null && create) { meta = new MetaEntity(); TypeDescriptor.AddAttributes(entity, meta); meta._SerialId = ++Uber.MetaEntityLastSerial; meta._EntityType = type; } if (meta != null && !meta.HasValidEntity) { meta._WeakReference = new WeakReference(entity); } return meta; } }
/// <summary> /// Returns an ad-hoc record containing the changes experimented by the entity since the /// last time its internal record was captured, or null if no changes can be detected. /// </summary> internal static IRecord GetRecordChanges(this MetaEntity meta) { object obj = meta.Entity; if (obj == null) { return(null); } if (meta.UberMap == null) { return(null); } if (meta.Record == null) { return(null); } var current = new Core.Concrete.Record(meta.UberMap.Schema); meta.UberMap.WriteRecord(obj, current); var changes = current.Changes(meta.Record); current.Dispose(); return(changes); }
/// <summary> /// Returns the meta entity associated with the given object, creating it if needed and /// requested. Returns null otherwise. /// </summary> /// <param name="entity">The object to obtain its meta entity from.</param> /// <param name="create">True to create the meta entity if it did not exist.</param> /// <returns>The requested meta entity, or null.</returns> public static MetaEntity Locate(object entity, bool create = true) { if (entity == null) { throw new ArgumentNullException("entity", "Entity cannot be null."); } var type = entity.GetType(); if (!type.IsClass) { throw new InvalidOperationException( "Entity '{0}' of type '{1}' is not a class.".FormatWith(entity.Sketch(), type.EasyName())); } lock (entity) // Yes, I know... but I want to use at most a local lock on the entity! { AttributeCollection list = TypeDescriptor.GetAttributes(entity); MetaEntity meta = list.OfType <MetaEntity>().FirstOrDefault(); if (meta == null && create) { meta = new MetaEntity(); TypeDescriptor.AddAttributes(entity, meta); meta._SerialId = ++Uber.MetaEntityLastSerial; meta._EntityType = type; } if (meta != null && !meta.HasValidEntity) { meta._WeakReference = new WeakReference(entity); } return(meta); } }
/// <summary> /// Returns whether this child entity requires a save operation. /// </summary> internal static bool DoesMetaNeedSave(this MetaEntity child, bool cascade, object origin = null) { if (child.Map == null) { return(true); } if (child.HasRecordChanges()) { return(true); } var list = child.GetRemovedChilds(forgetRemoved: false); var need = list.Count != 0; list.Clear(); list = null; if (need) { return(true); } if (cascade) { list = child.GetDependencies(child.UberMap, MemberDependencyMode.Child); foreach (var obj in list) { if (obj == null) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } if (object.ReferenceEquals(origin, obj)) { continue; } var meta = MetaEntity.Locate(obj); var temp = meta.UberMap ?? child.UberMap.Repository.LocateUberMap(type); if (temp == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } if (meta.DoesMetaNeedSave(cascade: false)) { need = true; break; } } list.Clear(); list = null; if (need) { return(true); } } return(false); }
/// <summary> /// Initializes a new instance. /// </summary> /// <param name="map">The map this command will be associated with.</param> /// <param name="entity">The entity affected by this operation.</param> protected MetaOperation(DataMap <T> map, T entity) : base(map) { if (entity == null) { throw new ArgumentNullException("entity", "Entity cannot be null."); } _Entity = entity; _MetaEntity = MetaEntity.Locate(entity); }
/// <summary> /// Returns whether the entity has experimented any changes since the last time its /// internal record was captured, or not. /// </summary> internal static bool HasRecordChanges(this MetaEntity meta) { var changes = meta.GetRecordChanges(); bool r = changes != null; if (changes != null) { changes.Dispose(disposeSchema: true); } return(r); }
/// <summary> /// Returns a list containing the dependencies of the given entity whose mode match the /// one given. /// </summary> internal static List <object> GetDependencies(this MetaEntity meta, IUberMap map, MemberDependencyMode mode) { var list = new List <object>(); var entity = meta.Entity; if (entity != null && map != null && !map.IsDisposed) { foreach (var member in (IEnumerable <IUberMember>)map.Members) { if (member.DependencyMode != mode) { continue; } if (!member.ElementInfo.CanRead) { continue; } var source = member.ElementInfo.GetValue(entity); if (source == null) { continue; } var type = source.GetType(); if (!type.IsListAlike()) { if (type.IsClass) { list.Add(source); } } else { type = type.ListAlikeMemberType(); if (type.IsClass) { var iter = source as IEnumerable; foreach (var item in iter) { if (item != null) { list.Add(item); } } } } } } return(list); }
/// <summary> /// Generates a delete core command for the given entity, or returns null if such /// command cannot be generated for whatever reasons. /// </summary> internal static IDeleteCommand GenerateDeleteCommand(this IUberMap map, object entity) { if (entity == null) { return(null); } if (map == null || map.IsDisposed || !map.IsValidated) { return(null); } IDeleteCommand cmd = null; MetaEntity meta = MetaEntity.Locate(entity, create: true); if (meta.Record == null) { var record = new Core.Concrete.Record(map.Schema); map.WriteRecord(entity, record); meta.Record = record; } var id = map.ExtractId(meta.Record); if (id != null) { cmd = map.Link.Engine.CreateDeleteCommand(map.Link, x => map.Table); if (map.Discriminator != null) { cmd.Where(map.Discriminator); } var tag = new DynamicNode.Argument("x"); for (int i = 0; i < id.Count; i++) { var left = new DynamicNode.GetMember(tag, id.Schema[i].ColumnName); var bin = new DynamicNode.Binary(left, ExpressionType.Equal, id[i]); cmd.Where(x => bin); left.Dispose(); bin.Dispose(); } tag.Dispose(); id.Dispose(); } return(cmd); }
/// <summary> /// Invoked when disposing or finalizing this instance. /// </summary> /// <param name="disposing">True if the object is being disposed, false otherwise.</param> protected override void OnDispose(bool disposing) { if (disposing) { try { if (Repository != null && !Repository.IsDisposed) { lock (Repository.MasterLock) { Repository.UberOperations.Remove(this); } } } catch { } } _MetaEntity = null; _Entity = null; base.OnDispose(disposing); }
/// <summary> /// Returns a list with the child dependencies that have been removed since the last /// time those were captured. /// </summary> internal static List <object> GetRemovedChilds(this MetaEntity meta, bool forgetRemoved) { var list = new List <object>(); var entity = meta.Entity; if (entity != null) { var type = entity.GetType(); foreach (var kvp in meta.ChildDependencies) { // Obtaining the current state... var info = ElementInfo.Create(type, kvp.Key, flags: TypeEx.FlattenInstancePublicAndHidden); if (!info.CanRead) { continue; } var curr = ((IEnumerable)info.GetValue(entity)).Cast <object>().ToList(); // Adding the entities that has been removed... foreach (var item in kvp.Value) { if (!curr.Contains(item)) { list.Add(item); } } curr.Clear(); curr = null; if (forgetRemoved) // Foget removed childs if such is requested { foreach (var item in list) { kvp.Value.Remove(item); } } } } return(list); }
/// <summary> /// Returns all the operations associated with the given entity, or null. /// </summary> internal List <IUberOperation> FindAllMeta(MetaEntity meta) { return(_Items.FindAll(x => object.ReferenceEquals(x.MetaEntity, meta))); }
/// <summary> /// Returns the last operation associated with the given entity, or null. /// </summary> internal IUberOperation FindLastMeta(MetaEntity meta) { return(_Items.FindLast(x => object.ReferenceEquals(x.MetaEntity, meta))); }
/// <summary> /// Invoked to obtain additional info when tracing an empty command. /// </summary> protected override string OnTraceCommandEmpty() { return(MetaEntity == null ? string.Empty : MetaEntity.ToString()); }
/// <summary> /// Validates that the row version captured in the meta-entity is the same as the current /// one in the database, throwing an exception if a change is detected. /// </summary> internal static void ValidateRowVersion(this MetaEntity meta) { var entity = meta.Entity; if (entity == null) { return; } if (meta.UberMap == null) { return; } if (meta.Record == null) { return; } var vc = meta.UberMap.VersionColumn; if (vc.Name == null) { return; } // Getting the most updated record, if any... var cmd = meta.UberMap.Link.From(x => meta.UberMap.Table); var tag = new DynamicNode.Argument("x"); var id = meta.UberMap.ExtractId(meta.Record); if (id == null) { throw new InvalidOperationException( "Cannot obtain identity from entity '{0}'".FormatWith(meta)); } for (int i = 0; i < id.Count; i++) { var left = new DynamicNode.GetMember(tag, id.Schema[i].ColumnName); var bin = new DynamicNode.Binary(left, ExpressionType.Equal, id[i]); cmd.Where(x => bin); bin.Dispose(); left.Dispose(); } id.Dispose(); tag.Dispose(); var rec = (IRecord)cmd.First(); cmd.Dispose(); if (rec == null) { return; } // Comparing values.... string captured = vc.ValueToString == null ? meta.Record[vc.Name].Sketch() : vc.ValueToString(meta.Record[vc.Name]); string current = vc.ValueToString == null ? rec[vc.Name].Sketch() : vc.ValueToString(rec[vc.Name]); if (string.Compare(captured, current) != 0) { throw new ChangedException( "Captured version '{0}' for entity '{1}' differs from the database's current one '{2}'." .FormatWith(captured, meta, current)); } }
/// <summary> /// Generates an update core command for the given entity, or returns null if such /// command cannot be generated for whatever reasons. /// </summary> internal static IUpdateCommand GenerateUpdateCommand(this IUberMap map, object entity) { if (entity == null) { return(null); } if (map == null || map.IsDisposed || !map.IsValidated) { return(null); } IUpdateCommand cmd = null; MetaEntity meta = MetaEntity.Locate(entity, create: true); if (meta.Record == null) { var record = new Core.Concrete.Record(map.Schema); map.WriteRecord(entity, record); meta.Record = record; } var changes = meta.GetRecordChanges(); if (changes == null) { return(null); } var num = changes.Schema.Count(x => !x.IsReadOnlyColumn); if (num != 0) { var id = map.ExtractId(meta.Record); if (id != null) { cmd = map.Link.Engine.CreateUpdateCommand(map.Link, x => map.Table); if (map.Discriminator != null) { cmd.Where(map.Discriminator); } var tag = new DynamicNode.Argument("x"); for (int i = 0; i < id.Count; i++) { var left = new DynamicNode.GetMember(tag, id.Schema[i].ColumnName); var bin = new DynamicNode.Binary(left, ExpressionType.Equal, id[i]); cmd.Where(x => bin); left.Dispose(); bin.Dispose(); } for (int i = 0; i < changes.Count; i++) { if (changes.Schema[i].IsReadOnlyColumn) { continue; } var node = new DynamicNode.SetMember(tag, changes.Schema[i].ColumnName, changes[i]); cmd.Columns(x => node); node.Dispose(); } tag.Dispose(); id.Dispose(); } } changes.Dispose(disposeSchema: true); return(cmd); }
/// <summary> /// Executes the command if it has not been executed yet in this instance. Returns true /// if a new record is available, or false otherwise. /// </summary> public bool MoveNext() { if (IsDisposed) { throw new ObjectDisposedException(this.ToString()); } try { if (_CoreCommand == null) // Preparing iterations... { _CoreCommand = _Command.GenerateCoreCommand(); if (_CoreCommand == null) { throw new CannotCreateException("Cannot create a core query command for this '{0}'.".FormatWith(this)); } _Enumerator = _CoreCommand.GetEnumerator(); if (_Enumerator == null) { throw new CannotCreateException("Cannot create a core query enumerator for this '{0}'.".FormatWith(this)); } DebugEx.IndentWriteLine("\n- Query: entering '{0}'...".FormatWith(_Command.TraceString())); _Indented = true; } _Current = null; bool r = _Enumerator.MoveNext(); if (r) // Current iteration... { var record = _Enumerator.CurrentRecord; var disposable = true; var map = _Command.Map; lock (map.Repository.MasterLock) { if (map.TrackEntities) { var node = map.MetaEntities.FindNode(record); if (node != null) { foreach (var meta in node) { T obj = (T)meta.Entity; if (obj == null) { DebugEx.IndentWriteLine("\n- Query: meta invalid '{0}'.".FormatWith(meta)); DebugEx.Unindent(); continue; } if (_Current == null) { _Current = obj; } else if (map.ProxyType != null && map.ProxyType != _Current.GetType() && map.ProxyType == obj.GetType()) { _Current = obj; } if (meta.Completed) { DebugEx.IndentWriteLine("\n- Query: meta completed '{0}'.".FormatWith(meta)); DebugEx.Unindent(); continue; } DebugEx.IndentWriteLine("\n- Query: hydrating meta '{0}'.".FormatWith(meta)); meta.Record = record.Clone(); map.LoadEntity(meta.Record, obj); map.CompleteMembers(meta); DebugEx.Unindent(); } } } if (_Current == null) { DebugEx.IndentWriteLine("\n- Query: new meta '{0}({1})'.".FormatWith(map.EntityType.EasyName(), record)); _Current = map.NewEntity(); var meta = MetaEntity.Locate(_Current); meta.Record = record; disposable = false; meta.UberMap = map; if (map.TrackEntities) { map.MetaEntities.Add(meta); } map.LoadEntity(meta.Record, _Current); map.CompleteMembers(meta); DebugEx.Unindent(); } } if (disposable) { record.Dispose(); } } else { Reset(); } return(r); } catch { try { Dispose(); } catch { } throw; } }
/// <summary> /// Invoked to execute this operation. /// </summary> internal void OnExecute(object origin = null) { lock (Repository.MasterLock) { List <object> list = null; ChangeEntry change = null; ChangeType changeType = MetaEntity.UberMap == null ? ChangeType.Insert : ChangeType.Update; IEnumerableCommand cmd = null; IRecord rec = null; DebugEx.IndentWriteLine("\n- Preparing '{1}({0})'...", MetaEntity, changeType); try { if (changeType == ChangeType.Update) { list = MetaEntity.GetRemovedChilds(forgetRemoved: true); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } var op = map.Delete(obj); try { ((IUberOperation)op).OnExecute(origin: Entity); } finally { op.Dispose(); } } list.Clear(); list = null; } list = MetaEntity.GetDependencies(Map, MemberDependencyMode.Parent); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } if (meta.DoesMetaNeedSave(true, origin: Entity)) { var op = map.Save(obj); try { ((IUberOperation)op).OnExecute(origin: Entity); } finally { op.Dispose(); } } else { change = new ChangeEntry(ChangeType.Refresh, map, obj); Repository.ChangeEntries.Add(change); } } if (changeType == ChangeType.Insert) { cmd = Map.GenerateInsertCommand(Entity); if (cmd == null) { throw new CannotCreateException("Cannot create an insert command for '{0}'.".FormatWith(MetaEntity)); } DebugEx.IndentWriteLine("\n- Executing '{0}'...", cmd); try { rec = (IRecord)cmd.First(); if (rec == null) { throw new CannotExecuteException("Cannot execute '{0}'.".FormatWith(cmd)); } } finally { DebugEx.Unindent(); } MetaEntity.Record = rec; Map.LoadEntity(rec, Entity); MetaEntity.UberMap = Map; if (Map.TrackEntities) { Map.MetaEntities.Add(MetaEntity); } MetaEntity.Completed = false; change = new ChangeEntry(ChangeType.Insert, Map, Entity); Repository.ChangeEntries.Add(change); } else if (changeType == ChangeType.Update) { cmd = Map.GenerateUpdateCommand(Entity); if (cmd != null) { DebugEx.IndentWriteLine("\n- Executing '{0}'...", cmd); try { MetaEntity.ValidateRowVersion(); rec = (IRecord)cmd.First(); if (rec == null) { throw new CannotExecuteException("Cannot execute '{0}'.".FormatWith(cmd)); } } finally { DebugEx.Unindent(); } MetaEntity.Record = rec; Map.LoadEntity(rec, Entity); MetaEntity.Completed = false; change = new ChangeEntry(ChangeType.Update, Map, Entity); Repository.ChangeEntries.Add(change); } else { change = new ChangeEntry(ChangeType.Refresh, Map, Entity); Repository.ChangeEntries.Add(change); } } list = MetaEntity.GetDependencies(Map, MemberDependencyMode.Child); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } if (meta.DoesMetaNeedSave(cascade: true)) { var op = map.Save(obj); try { ((IUberOperation)op).OnExecute(origin: Entity); } finally { op.Dispose(); } } else { change = new ChangeEntry(ChangeType.Refresh, map, obj); Repository.ChangeEntries.Add(change); } } list.Clear(); list = null; } finally { if (cmd != null) { cmd.Dispose(); } cmd = null; if (list != null) { list.Clear(); } list = null; DebugEx.Unindent(); } } }
/// <summary> /// Invoked to execute this operation. /// </summary> internal void OnExecute(object origin = null) { lock (Repository.MasterLock) { List <object> list = null; ChangeEntry change = null; IDeleteCommand cmd = null; DebugEx.IndentWriteLine("\n- Preparing 'Delete({0})'...", MetaEntity); try { list = MetaEntity.GetRemovedChilds(forgetRemoved: true); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } var op = map.Delete(obj); try { ((IUberOperation)op).OnExecute(origin: Entity); } finally { op.Dispose(); } } list.Clear(); list = null; list = MetaEntity.GetDependencies(Map, MemberDependencyMode.Child); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } var op = map.Delete(obj); try { ((IUberOperation)op).OnExecute(origin: Entity); } finally { op.Dispose(); } } list.Clear(); list = null; cmd = Map.GenerateDeleteCommand(Entity); if (cmd != null) { DebugEx.IndentWriteLine("\n- Executing '{0}'...", cmd); try { MetaEntity.ValidateRowVersion(); int n = cmd.Execute(); } finally { DebugEx.Unindent(); } } Map.Detach(Entity); change = new ChangeEntry(ChangeType.Delete, Map, Entity); Repository.ChangeEntries.Add(change); list = MetaEntity.GetDependencies(Map, MemberDependencyMode.Parent); foreach (var obj in list) { if (obj == null) { continue; } if (object.ReferenceEquals(obj, origin)) { continue; } var type = obj.GetType(); if (!type.IsClass && !type.IsInterface) { continue; } var meta = MetaEntity.Locate(obj); var map = meta.UberMap ?? Repository.LocateUberMap(type); if (map == null) { throw new NotFoundException("Cannot find map for type '{0}'.".FormatWith(type.EasyName())); } change = new ChangeEntry(ChangeType.Refresh, map, obj); Repository.ChangeEntries.Add(change); } } finally { if (cmd != null) { cmd.Dispose(); } cmd = null; if (list != null) { list.Clear(); } list = null; DebugEx.Unindent(); } } }
/// <summary> /// Returns the string representation of this instance. /// </summary> /// <returns>A string containing the standard representation of this instance.</returns> public override string ToString() { var str = string.Format("{0}({1})", ChangeType, MetaEntity == null ? string.Empty : MetaEntity.ToString()); return(str); }