private void AssignChildObjectStates(object parent, InitialObjectState state, IList<ObjectStateInfo> childStateInfos)
        {
            if (childStateInfos.IsNull())
            {
                childStateInfos = new List<ObjectStateInfo>();
            }

            foreach (var childState in state.ChildObjectStates)
            {
                var childStateInfo = new ObjectStateInfo
                {
                    ObjectType = childState.ObjectType,
                    PropertyName = childState.PropertyName
                };

                AssignObjectState(parent, childState, childStateInfo, true);
                AssignChildState(childState, childStateInfo);

                childStateInfos.Add(childStateInfo);
            }
        }
        private void AssignObjectState(object item, InitialObjectState state, ObjectStateInfo stateInfo, bool isChild)
        {
            var value = GetValue(item, state, isChild);

            if (!InitialObjectStates.ContainsKey(state.Id))
            {
                stateInfo.ObjectState = ObjectState.New;
                return;
            }

            var stateAsParent = InitialObjectStates[state.Id];
            var stateAsChild = isChild ? GetChildInitialState(item, value) : null;

            if (stateAsParent.IsDeleted || (isChild && stateAsChild.IsNotNull() && stateAsChild.IsDeleted))
            {
                stateInfo.ObjectState = ObjectState.Deleted;
                return;
            }

            var hash = _hashHelper.GenerateHash(value);

            if (String.Compare(stateAsParent.HashCode, hash, StringComparison.InvariantCulture) != 0)
            {
                stateInfo.ObjectState = ObjectState.Updated;
                return;
            }

            stateInfo.ObjectState = ObjectState.NoChange;
            return;
        }
        private InitialObjectState ExtractStateInfo(object value, Type type)
        {
            var id = GetId(value, type);
            var hash = _hashHelper.GenerateHash(value);
            var objectState = new InitialObjectState
            {
                Id = id,
                ObjectType = type,
                HashCode = hash,
            };

            var joinProperties = type
                .GetProperties()
                .Where(p => p.GetCustomAttributes(typeof(JoinAttribute), true)
                    .IsNotNullOrEmpty())
                .ToList();

            foreach (var property in joinProperties)
            {
                var propertyValue = property.GetValue(value);
                var propertyType = property.PropertyType;
                Type valueType = null;
                var isEnumerable = IsEnumerable(propertyType);

                if (propertyValue.IsNotNull())
                {
                    if (isEnumerable)
                    {
                        valueType = propertyType.GetGenericArguments().First();
                        var objectValues = (IEnumerable) propertyValue;

                        foreach (var objectValue in objectValues)
                        {
                            var state = ExtractStateInfo(objectValue, valueType);

                            state.PropertyName = property.Name;
                            state.ParentId = id;
                            objectState.ChildObjectStates.Add(state);
                        }
                    }
                    else
                    {
                        valueType = propertyType;
                        var state = ExtractStateInfo(propertyValue, valueType);
                        state.ParentId = id;
                        objectState.ChildObjectStates.Add(state);
                    }
                }
            }

            return objectState;
        }
        private static void UpdateChildState(InitialObjectState objectState, InitialObjectState childState,
            InitialObjectState state, int index)
        {
            if (childState.Id != objectState.Id || childState.ObjectType != objectState.ObjectType)
            {
                return;
            }

            childState.HashCode = objectState.HashCode;
            state.ChildObjectStates[index] = childState;
        }
        private void UpdateChildStates(InitialObjectState objectState)
        {
            foreach (var state in InitialObjectStates.Values
                .SafeWhere(i => i.ChildObjectStates.IsNotNullOrEmpty()))
            {
                for (var index = 0; index < state.ChildObjectStates.Count; index++)
                {
                    var childState = state.ChildObjectStates[index];

                    UpdateChildState(objectState, childState, state, index);
                }

                InitialObjectStates[state.Id] = state;
            }
        }
        private void AssignChildState(InitialObjectState childObjectState, ObjectStateInfo childStateInfo)
        {
            var foundAsParent = InitialObjectStates.ContainsKey(childObjectState.Id);
            var attachedToParent = false;

            if (InitialObjectStates.ContainsKey(childObjectState.ParentId))
            {
                var parentState = InitialObjectStates[childObjectState.ParentId];

                attachedToParent = parentState.ChildObjectStates
                    .Any(child => child.Id == childObjectState.Id);
            }

            var childState = AssignChildStateInternal(foundAsParent, attachedToParent);

            childStateInfo.ChildState = childState;
        }
        private object GetValue(object item, InitialObjectState state, bool isChild)
        {
            if (!isChild)
            {
                return item;
            }

            var itemType = item.GetType();
            var property = itemType.GetProperties().First(p => p.Name == state.PropertyName);
            var value = property.GetValue(item);
            var expectedId = state.Id;

            if (!IsEnumerable(property.PropertyType))
            {
                var valueId = GetId(value, property.PropertyType);

                if (expectedId != valueId)
                {
                    // TODO: Convert to ChildIdMismatchException.
                    throw new InvalidOperationException("Child Id does not match.  Expected {0}.  Actual {1}"
                        .FormatString(expectedId, valueId));
                }

                return value;
            }

            foreach (var enumeratedItem in (IEnumerable) value)
            {
                var itemId = GetId(enumeratedItem, enumeratedItem.GetType());

                if (expectedId == itemId)
                {
                    return enumeratedItem;
                }
            }

            // TODO: Convert to ChildNotFoundException.
            throw new InvalidOperationException("Could not locate child object.");
        }