public void Dispose()
 {
     if (!_isDisposing)
     {
         _isDisposing = true;
         _entity      = null;
         _projection  = null;
         _changes.Clear();
         _lastChange = null;
         _validEntitiesProperties.Clear <Type, string>();
         _entityUpdateHandlers.Clear();
         _entityCollectionChangedHandlers.Clear();
         _entityCollectionAddedHandlers.Clear();
         _entityCollectionRemovedHandlers.Clear();
         _entitiesPaths.Clear <string, string>();
         _observableCollectionType = null;
         _emptyObjectArray         = null;
         _validationRulesFilters.Clear();
         _isTracked = null;
         _removeHandlersBeforeRecursionAction           = null;
         _removeHandlersCollectionPropertyAction        = null;
         _createHandlersAfterRecursionAction            = null;
         _createHandlersBeforeCollectionRecursionAction = null;
         _createHandlersAfterCollectionRecursionAction  = null;
         _isDisposing = false;
     }
 }
        public void Validate(PropertyChange change, bool isIDbEntity)
        {
            DateTime start = DateTime.UtcNow;
            //string propertyPath = change.PropertyPath;
            //if (!change.IsDbEntityEnumerable || change.After != null)
            //    propertyPath = change.DbEntityPropertyPath(_entity);

            //string propertyPathPrefix = propertyPath;
            //if (!isIDbEntity)
            //    propertyPathPrefix = propertyPath.Substring(0, propertyPath.LastIndexOf("."));

            //if (propertyPathPrefix.Contains("["))
            //    propertyPathPrefix = propertyPathPrefix.Substring(0, propertyPathPrefix.LastIndexOf("["));

            //if (string.IsNullOrEmpty(propertyPathPrefix))
            //    propertyPathPrefix = ".";

            IDictionary <string, ICollection <IValidationRule> > allRules = ValidationRules.Where(r => _validationRulesFilters.Any(p => p.Match(r.Key).Success)).ToDictionary(p => p.Key, p => p.Value);
            //if (allRules.Count == 0)
            //    allRules = ValidationRules.Where(p => p.Key.StartsWith(propertyPathPrefix)).ToDictionary(p => p.Key, p => p.Value);

            ICollection <IValidationResult> results = GetValidationResults(allRules);

            RaiseValidated(results);
        }
        public void Undo(PropertyChange lastChange, bool inclusive = true)
        {
            if (_changes.Count == 0)
            {
                throw new ArgumentException("Cannot undo before any changes are tracked.");
            }

            Untracked((e) =>
            {
                PropertyChange originalLastChange = lastChange;
                string groupGuid = lastChange.GroupGuid;
                lastChange       = Undo(e, lastChange);
                while (lastChange != null && lastChange.IsGrouped && lastChange.GroupGuid.Equals(groupGuid))
                {
                    lastChange = Undo(e, lastChange);
                }

                if (!inclusive)
                {
                    AddChange(originalLastChange);
                }
            });

            RaiseChangeUndone();
        }
 private PropertyChange Undo(IDbEntity e, PropertyChange lastChange)
 {
     if (!lastChange.IsOrphan(e) ||
         (lastChange.IsDbEntityEnumerable && lastChange.After == null && lastChange.Before != null))
     {
         lastChange.Revert(e);
     }
     return(MoveLastChange(lastChange));
 }
        public void PurgeChanges(IDbEntity root = null, long utcTimestamp = long.MaxValue)
        {
            ICollection <PropertyChange> changesToDelete = ChangesForNonDeletedEntities(GetChangePathsPrefixes(root), utcTimestamp);

            foreach (PropertyChange change in changesToDelete)
            {
                RemoveChange(change);
            }
            _lastChange = _changes.LastOrDefault();
            changesToDelete.Clear();
        }
 private PropertyChange MoveLastChange(PropertyChange lastChange)
 {
     if (lastChange.Equals(_changes.First()))
     {
         _lastChange = null;
     }
     else
     {
         _lastChange = _changes[_changes.IndexOf(lastChange) - 1];
     }
     return(_lastChange);
 }
        private void RemoveChange(PropertyChange change)
        {
            if (_changes.Count > 1 && _lastChange.Equals(_changes.Last()))
            {
                _lastChange = _changes.ElementAt(_changes.Count - 2);
            }
            else if (_changes.Count == 1)
            {
                _lastChange = null;
            }

            _changes.Remove(change);
            RaiseChangeRemoved();
        }
        private void Redo(PropertyChange lastChange)
        {
            Untracked((e) =>
            {
                string groupGuid = lastChange.GroupGuid;
                lastChange       = Redo(e, lastChange);
                while (lastChange != null && lastChange.IsGrouped && lastChange.GroupGuid.Equals(groupGuid))
                {
                    lastChange = Redo(e, lastChange);
                }
            });

            RaiseChangeRedone();
        }
        private void ProcessCollectionChange(NotifyCollectionChangedAction action, IEnumerable items, string path, string propertyName, IDbEntity parentEntity)
        {
            if (!_isCreatingHandlers && IsValid(parentEntity.GetType(), path, propertyName))
            {
                if (action == NotifyCollectionChangedAction.Add)
                {
                    ICollection <PropertyChange> changes = new List <PropertyChange>();
                    foreach (IDbEntity navigationEntity in items.Cast <IDbEntity>())
                    {
                        string         entityPath = DbEntityUtilities.GenerateCollectionItemPath(path + propertyName, navigationEntity.Guid);
                        PropertyChange change     = new PropertyChange(entityPath, string.Empty, navigationEntity.Guid, null, navigationEntity, true, navigationEntity.State);
                        if (_entity.IsTrackingChanges)
                        {
                            changes.Add(change);
                            MergeChanges(navigationEntity, entityPath, !_entity.IsTrackingChanges);
                        }
                        CreateHandlers(navigationEntity, entityPath);
                    }

                    if (_entity.IsTrackingChanges)
                    {
                        AddChanges(changes);
                    }
                    Validate(null, true);
                }
                else if (action == NotifyCollectionChangedAction.Remove)
                {
                    ICollection <PropertyChange> changes = new List <PropertyChange>();
                    string propertyPath = DbEntityUtilities.GeneratePropertyPath(path, propertyName);
                    foreach (IDbEntity removedEntity in items.Cast <IDbEntity>())
                    {
                        string         entityPath = DbEntityUtilities.GenerateCollectionItemPath(propertyPath, removedEntity.Guid);
                        PropertyChange change     = new PropertyChange(entityPath, string.Empty, removedEntity.Guid, removedEntity, null, true, removedEntity.State);
                        if (_entity.IsTrackingChanges)
                        {
                            changes.Add(change);
                        }
                        RemoveHandlers(removedEntity, entityPath);
                        Validate(change, true);
                    }

                    if (_entity.IsTrackingChanges)
                    {
                        AddChanges(changes);
                    }
                    changes.Clear();
                }
            }
        }
        private PropertyChange Redo(IDbEntity e, PropertyChange lastChange)
        {
            if (!lastChange.IsOrphan(e) ||
                (lastChange.IsDbEntityEnumerable && lastChange.After != null && lastChange.Before == null))
            {
                lastChange.Apply(e);
            }
            _lastChange = lastChange;
            PropertyChange nextChange = null;

            if (!_lastChange.Equals(_changes.Last()))
            {
                nextChange = _changes[_changes.IndexOf(_lastChange) + 1];
            }
            return(nextChange);
        }
        private bool PurgeLast(Type propertyReturnType, PropertyChange change)
        {
            bool           purged     = false;
            PropertyChange lastChange = _changes.LastOrDefault();

            /*
             * Tries to reduce the number of changes recorded when string properties are being updated on every user key stroke. In these cases, if the last change was on the same property and the length difference between both is only one character, the last change is purged.
             */
            if (_changes.Count > 0 &&
                propertyReturnType.Equals(typeof(string)) &&
                lastChange.PropertyPath.Equals(change.PropertyPath) &&
                change.After != null &&
                lastChange.After != null &&
                Math.Abs((lastChange.After as string).Length - (change.After as string).Length) == 1)
            {
                RemoveChange(lastChange);
                change.Before = lastChange.Before;
                purged        = true;
            }
            return(purged);
        }
        private void AddChanges(IEnumerable <PropertyChange> changes, bool raiseEvent = true)
        {
            if (_lastChange != null && !_lastChange.Equals(_changes.Last()))
            {
                int lastChangeIndex = _changes.IndexOf(_lastChange) + 1;
                _changes.RemoveRange(lastChangeIndex, _changes.Count - lastChangeIndex);
            }
            else if (_lastChange == null && _changes.Count > 0)
            {
                _changes.Clear();
            }

            foreach (PropertyChange change in changes)
            {
                _lastChange = change;
                _changes.Add(_lastChange);
            }

            if (raiseEvent)
            {
                RaiseChangesAdded();
            }
        }
 private void AddChange(PropertyChange change)
 {
     AddChanges(new PropertyChange[] { change });
 }
        private void EntityPropertyChanged(object sender, DbEntityPropertyChangedEventArgs e, string path)
        {
            IDbEntity      entity = sender as IDbEntity;
            PropertyChange change = null;

            // Save a reference to the property changed
            if (!_isCreatingHandlers && IsValid(sender.GetType(), path, e.PropertyName) && (!e.PropertyName.Equals("IsDeleted") || entity.State == EntityState.Persisted))
            {
                string       propertyPath         = path + e.PropertyName;
                PropertyInfo p                    = entity.GetType().GetProperty(e.PropertyName);
                bool         isEnumerable         = p.GetGetMethod().ReturnType.IsIEnumerable();
                bool         isDbEntityEnumerable = false;
                bool         isIDbEntity          = DbEntityUtilities.IsIDbEntity(p);
                if (isIDbEntity)
                {
                    propertyPath = propertyPath + ".";
                    if (e.Before != null)
                    {
                        RemoveHandlers(e.Before as IDbEntity, propertyPath);
                    }

                    if (e.After != null)
                    {
                        // Defer any changes tracking that might have been recorded on the navigation entity to the root entity
                        IDbEntity navigationEntity = e.After as IDbEntity;
                        if (_entity.IsTrackingChanges)
                        {
                            MergeChanges(navigationEntity, propertyPath, !_entity.IsTrackingChanges);
                        }
                        CreateHandlers(navigationEntity, propertyPath);
                    }
                }
                else if ((isEnumerable && p.GetGetMethod().ReturnType.GetGenericArguments().Count() > 0 && DbEntityUtilities.IsIDbEntity(p.GetGetMethod().ReturnType.GetGenericArguments()[0])))
                {
                    isDbEntityEnumerable = true;
                    if (e.Before != null)
                    {
                        RemoveCollectionHandlers(entity, e.Before as IEnumerable <IDbEntity>, path, propertyPath + ".", p);
                    }

                    if (e.After != null)
                    {
                        foreach (IDbEntity navigationEntity in e.After as IEnumerable <IDbEntity> )
                        {
                            if (navigationEntity != null)
                            {
                                string entityPath = DbEntityUtilities.GenerateCollectionItemPath(propertyPath, navigationEntity.Guid);
                                if (_entity.IsTrackingChanges)
                                {
                                    MergeChanges(navigationEntity, entityPath, !_entity.IsTrackingChanges);
                                }
                                CreateHandlers(navigationEntity, entityPath);
                            }
                        }
                    }
                }

                if (_entity.IsTrackingChanges)
                {
                    change = new PropertyChange(path, e.PropertyName, entity.Guid, e.Before, e.After, entity.State);
                    if (isDbEntityEnumerable)
                    {
                        change = new PropertyChange(path, e.PropertyName, entity.Guid, e.Before, e.After);
                    }

                    PurgeLast(p.GetGetMethod().ReturnType, change);
                    AddChange(change);
                    Validate(change, isIDbEntity);
                }

                //_logTo.DebugQueue(new EntityPropertyChangedLogEntry(SettingsModel.Current.User.id, entity.PrimaryKeys, entity.GetType(), propertyPath));
            }
        }