ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ptupit...@apache.org
Subject ignite git commit: Merge session state item collection classes
Date Tue, 13 Sep 2016 16:31:20 GMT
Repository: ignite
Updated Branches:
  refs/heads/ignite-3199-1 815cde40c -> 11e69b482


Merge session state item collection classes


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/11e69b48
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/11e69b48
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/11e69b48

Branch: refs/heads/ignite-3199-1
Commit: 11e69b482bc7d9b18cad9ffe6b21e641bfc61a65
Parents: 815cde4
Author: Pavel Tupitsyn <ptupitsyn@apache.org>
Authored: Tue Sep 13 19:31:08 2016 +0300
Committer: Pavel Tupitsyn <ptupitsyn@apache.org>
Committed: Tue Sep 13 19:31:08 2016 +0300

----------------------------------------------------------------------
 .../Apache.Ignite.AspNet.csproj                 |   3 +-
 .../Impl/IgniteSessionStateItemCollection.cs    | 492 ++++++++++++++++--
 .../Impl/IgniteSessionStateStoreData.cs         |  10 +-
 .../Impl/KeyValueDirtyTrackedCollection.cs      | 507 -------------------
 .../Apache.Ignite.Core.Tests.csproj             |   1 -
 .../IgniteSessionStateItemCollectionTest.cs     | 176 ++++++-
 .../KeyValueDirtyTrackedCollectionTest.cs       | 260 ----------
 7 files changed, 608 insertions(+), 841 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
index b17c9f3..1ac452f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
@@ -51,9 +51,8 @@
     <Compile Include="IgniteWebUtils.cs" />
     <Compile Include="Impl\ConfigUtil.cs" />
     <Compile Include="Impl\ExpiryCacheHolder.cs" />
-    <Compile Include="Impl\IgniteSessionStateItemCollection.cs" />
     <Compile Include="Impl\IgniteSessionStateStoreData.cs" />
-    <Compile Include="Impl\KeyValueDirtyTrackedCollection.cs" />
+    <Compile Include="Impl\IgniteSessionStateItemCollection.cs" />
     <Compile Include="Impl\SessionStateLockResult.cs" />
     <Compile Include="Package-Info.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
index 946e277..1e17bcb 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
@@ -19,49 +19,90 @@ namespace Apache.Ignite.AspNet.Impl
 {
     using System;
     using System.Collections;
+    using System.Collections.Generic;
     using System.Collections.Specialized;
     using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.IO;
+    using System.Linq;
+    using System.Runtime.Serialization.Formatters.Binary;
     using System.Web.SessionState;
+    using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Impl.Common;
 
     /// <summary>
-    /// Wrapper for <see cref="KeyValueDirtyTrackedCollection" />.
+    /// Binarizable key-value collection with dirty item tracking.
     /// </summary>
     internal class IgniteSessionStateItemCollection : ISessionStateItemCollection
     {
-        /** Wrapped collection */
-        private readonly KeyValueDirtyTrackedCollection _collection;
+        /** */
+        private readonly Dictionary<string, int> _dict;
+
+        /** */
+        private readonly List<Entry> _list;
+
+        /** Indicates where this is a new collection, not a deserialized old one. */
+        private readonly bool _isNew;
+
+        /** Indicates that this instance is a diff. */
+        private readonly bool _isDiff;
+
+        /** Removed keys. Hash set because keys can be removed multiple times. */
+        private HashSet<string> _removedKeys;
+
+        /** Indicates that entire collection is dirty and can't be written as a diff. */
+        private bool _dirtyAll;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="IgniteSessionStateItemCollection"/> class.
         /// </summary>
-        /// <param name="collection">The collection.</param>
-        public IgniteSessionStateItemCollection(KeyValueDirtyTrackedCollection collection)
+        /// <param name="reader">The binary reader.</param>
+        internal IgniteSessionStateItemCollection(IBinaryRawReader reader)
         {
-            Debug.Assert(collection != null);
+            Debug.Assert(reader != null);
+
+            _isDiff = reader.ReadBoolean();
+
+            var count = reader.ReadInt();
+
+            _dict = new Dictionary<string, int>(count);
+            _list = new List<Entry>(count);
+
+            for (var i = 0; i < count; i++)
+            {
+                var key = reader.ReadString();
+
+                var valBytes = reader.ReadByteArray();
+
+                if (valBytes != null)
+                {
+                    var entry = new Entry(key, true, valBytes);
+
+                    _dict[key] = _list.Count;
 
-            // TODO: Merge classes
-            _collection = collection;
+                    _list.Add(entry);
+                }
+                else
+                    AddRemovedKey(key);
+            }
+
+            _isNew = false;
         }
 
         /// <summary>
-        /// Gets the collection.
+        /// Initializes a new instance of the <see cref="IgniteSessionStateItemCollection"/> class.
         /// </summary>
-        public KeyValueDirtyTrackedCollection Collection
+        public IgniteSessionStateItemCollection()
         {
-            get { return _collection; }
+            _dict = new Dictionary<string, int>();
+            _list = new List<Entry>();
+            _isNew = true;
         }
 
         /** <inheritdoc /> */
-        public IEnumerator GetEnumerator()
-        {
-            // This should return only keys.
-            return _collection.GetKeys().GetEnumerator();
-        }
-
-        /** <inheritdoc /> */
-        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", 
-            "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated.")]
+        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", 
+            Justification = "Validation is present.")]
         public void CopyTo(Array array, int index)
         {
             IgniteArgumentCheck.NotNull(array, "array");
@@ -69,20 +110,27 @@ namespace Apache.Ignite.AspNet.Impl
                 "The number of elements in the source collection is greater than the available space " +
                 "from specified index to the end of the array.");
 
-            foreach (var key in _collection.GetKeys())
+            foreach (var key in GetKeys())
                 array.SetValue(key, index++);
         }
 
         /** <inheritdoc /> */
+        public IEnumerator GetEnumerator()
+        {
+            // This should return only keys.
+            return GetKeys().GetEnumerator();
+        }
+
+        /** <inheritdoc /> */
         public int Count
         {
-            get { return _collection.Count; }
+            get { return _dict.Count; }
         }
 
         /** <inheritdoc /> */
         public object SyncRoot
         {
-            get { return _collection; }
+            get { return _list; }
         }
 
         /** <inheritdoc /> */
@@ -92,48 +140,404 @@ namespace Apache.Ignite.AspNet.Impl
         }
 
         /** <inheritdoc /> */
-        public void Remove(string name)
+        public object this[string key]
         {
-            _collection.Remove(name);
+            get
+            {
+                var entry = GetEntry(key);
+
+                if (entry == null)
+                    return null;
+
+                SetDirtyOnRead(entry);
+
+                return entry.Value;
+            }
+            set
+            {
+                var entry = GetOrCreateDirtyEntry(key);
+
+                entry.Value = value;
+            }
         }
 
         /** <inheritdoc /> */
-        public void RemoveAt(int index)
+        public object this[int index]
         {
-            _collection.RemoveAt(index);
+            get
+            {
+                var entry = _list[index];
+
+                SetDirtyOnRead(entry);
+
+                return entry.Value;
+            }
+            set
+            {
+                var entry = _list[index];
+
+                entry.IsDirty = true;
+
+                entry.Value = value;
+            }
         }
 
         /** <inheritdoc /> */
-        public void Clear()
+        public NameObjectCollectionBase.KeysCollection Keys
         {
-            _collection.Clear();
+            get { return new NameObjectCollection(this).Keys; }
         }
 
+
         /** <inheritdoc /> */
-        public object this[string name]
+        public bool Dirty
         {
-            get { return _collection[name]; }
-            set { _collection[name] = value; }
+            get { return _dirtyAll || _list.Any(x => x.IsDirty); }
+            set { _dirtyAll = value; }
         }
 
-        /** <inheritdoc /> */
-        public object this[int index]
+        /// <summary>
+        /// Gets or sets a value indicating whether only dirty changed things should be serialized.
+        /// </summary>
+        public bool WriteChangesOnly { get; set; }
+
+        /// <summary>
+        /// Gets the keys.
+        /// </summary>
+        public IEnumerable<string> GetKeys()
         {
-            get { return _collection[index]; }
-            set { _collection[index] = value; }
+            return _list.Select(x => x.Key);
         }
 
-        /** <inheritdoc /> */
-        public NameObjectCollectionBase.KeysCollection Keys
+        /// <summary>
+        /// Writes this object to the given writer.
+        /// </summary>
+        /// <param name="writer">Writer.</param>
+        public void WriteBinary(IBinaryRawWriter writer)
         {
-            get { return new NameObjectCollection(this).Keys; }
+            IgniteArgumentCheck.NotNull(writer, "writer");
+
+            if (_isDiff)
+                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
+                    "Cannot serialize incomplete {0}.", GetType()));
+
+            if (_isNew || _dirtyAll || !WriteChangesOnly || (_removedKeys == null && _list.All(x => x.IsDirty)))
+            {
+                // Write in full mode.
+                writer.WriteBoolean(false);
+                writer.WriteInt(_list.Count);
+
+                foreach (var entry in _list)
+                {
+                    writer.WriteString(entry.Key);
+
+                    // Write as byte array to enable partial deserialization.
+                    writer.WriteByteArray(entry.GetBytes());
+                }
+            }
+            else
+            {
+                // Write in diff mode.
+                writer.WriteBoolean(true);
+
+                var removed = GetRemovedKeys();
+
+                var count = _list.Count(x => x.IsDirty) + (removed == null ? 0 : removed.Count);
+
+                writer.WriteInt(count);  // reserve count
+
+                // Write removed keys as [key + null].
+                if (removed != null)
+                {
+                    foreach (var removedKey in removed)
+                    {
+                        writer.WriteString(removedKey);
+                        writer.WriteByteArray(null);
+                    }
+                }
+
+                // Write dirty items.
+                foreach (var entry in _list)
+                {
+                    if (!entry.IsDirty)
+                        continue;
+
+                    writer.WriteString(entry.Key);
+
+                    // Write as byte array to enable partial deserialization.
+                    writer.WriteByteArray(entry.GetBytes());
+                }
+            }
         }
 
-        /** <inheritdoc /> */
-        public bool Dirty
+        /// <summary>
+        /// Gets the removed keys.
+        /// </summary>
+        private ICollection<string> GetRemovedKeys()
+        {
+            if (_removedKeys == null)
+                return null;
+
+            // Filter out existing keys.
+            var removed = new HashSet<string>(_removedKeys);
+
+            foreach (var entry in _list)
+                removed.Remove(entry.Key);
+
+            return removed;
+        }
+
+        /// <summary>
+        /// Removes the specified key.
+        /// </summary>
+        public void Remove(string key)
+        {
+            var index = GetIndex(key);
+
+            if (index < 0)
+                return;
+
+            var entry = _list[index];
+            Debug.Assert(key == entry.Key);
+
+            _list.RemoveAt(index);
+            _dict.Remove(key);
+
+            // Update all indexes.
+            for (var i = 0; i < _list.Count; i++)
+                _dict[_list[i].Key] = i;
+
+            if (entry.IsInitial)
+                AddRemovedKey(key);
+        }
+
+        /// <summary>
+        /// Removes at specified index.
+        /// </summary>
+        public void RemoveAt(int index)
+        {
+            var entry = _list[index];
+
+            _list.RemoveAt(index);
+            _dict.Remove(entry.Key);
+
+            if (entry.IsInitial)
+                AddRemovedKey(entry.Key);
+        }
+
+        /// <summary>
+        /// Clears this instance.
+        /// </summary>
+        public void Clear()
+        {
+            foreach (var entry in _list)
+            {
+                if (entry.IsInitial)
+                    AddRemovedKey(entry.Key);
+            }
+
+            _list.Clear();
+            _dict.Clear();
+
+            _dirtyAll = true;
+        }
+
+        /// <summary>
+        /// Applies the changes.
+        /// </summary>
+        public void ApplyChanges(IgniteSessionStateItemCollection changes)
+        {
+            var removed = changes._removedKeys;
+
+            if (removed != null)
+            {
+                foreach (var key in removed)
+                    Remove(key);
+            }
+            else if (!changes._isDiff)
+            {
+                // Not a diff: replace all.
+                Clear();
+            }
+
+            foreach (var changedEntry in changes._list)
+            {
+                var entry = GetOrCreateDirtyEntry(changedEntry.Key);
+
+                // Copy without deserialization.
+                changedEntry.CopyTo(entry);
+            }
+        }
+
+        /// <summary>
+        /// Adds the removed key.
+        /// </summary>
+        private void AddRemovedKey(string key)
+        {
+            Debug.Assert(!_isNew);
+
+            if (_removedKeys == null)
+                _removedKeys = new HashSet<string>();
+
+            _removedKeys.Add(key);
+        }
+
+        /// <summary>
+        /// Gets or creates an entry.
+        /// </summary>
+        private Entry GetOrCreateDirtyEntry(string key)
+        {
+            var entry = GetEntry(key);
+
+            if (entry == null)
+            {
+                entry = new Entry(key, false, null);
+
+                _dict[key] = _list.Count;
+                _list.Add(entry);
+            }
+
+            entry.IsDirty = true;
+
+            return entry;
+        }
+
+        /// <summary>
+        /// Gets the entry.
+        /// </summary>
+        private Entry GetEntry(string key)
+        {
+            IgniteArgumentCheck.NotNull(key, "key");
+
+            int index;
+
+            return !_dict.TryGetValue(key, out index) ? null : _list[index];
+        }
+
+        /// <summary>
+        /// Gets the index.
+        /// </summary>
+        private int GetIndex(string key)
+        {
+            int index;
+
+            return !_dict.TryGetValue(key, out index) ? -1 : index;
+        }
+
+        /// <summary>
+        /// Sets the dirty on read.
+        /// </summary>
+        private static void SetDirtyOnRead(Entry entry)
+        {
+            var type = entry.Value.GetType();
+
+            if (IsImmutable(type))
+                return;
+
+            entry.IsDirty = true;
+        }
+
+        /// <summary>
+        /// Determines whether the specified type is immutable.
+        /// </summary>
+        private static bool IsImmutable(Type type)
         {
-            get { return _collection.IsDirty; }
-            set { _collection.IsDirty = value; }
+            type = Nullable.GetUnderlyingType(type) ?? type;  // Unwrap nullable.
+
+            if (type.IsPrimitive)
+                return true;
+
+            if (type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid) || type == typeof(decimal))
+                return true;
+
+            return false;
+        }
+
+        /// <summary>
+        /// Inner entry.
+        /// </summary>
+        private class Entry
+        {
+            /** */
+            public readonly bool IsInitial;
+
+            /** */
+            public readonly string Key;
+
+            /** */
+            public bool IsDirty;
+
+            /** */
+            private object _value;
+
+            /** */
+            private bool _isDeserialized;
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="Entry"/> class.
+            /// </summary>
+            public Entry(string key, bool isInitial, object value)
+            {
+                Debug.Assert(key != null);
+
+                Key = key;
+                IsInitial = isInitial;
+                _isDeserialized = !isInitial;
+                _value = value;
+            }
+
+            /// <summary>
+            /// Gets or sets the value.
+            /// </summary>
+            public object Value
+            {
+                get
+                {
+                    if (!_isDeserialized)
+                    {
+                        using (var stream = new MemoryStream((byte[])_value))
+                        {
+                            _value = new BinaryFormatter().Deserialize(stream);
+                        }
+
+                        _isDeserialized = true;
+                    }
+
+                    return _value;
+                }
+                set
+                {
+                    _value = value;
+                    _isDeserialized = true;
+                }
+            }
+
+            /// <summary>
+            /// Copies contents to another entry.
+            /// </summary>
+            public void CopyTo(Entry entry)
+            {
+                Debug.Assert(entry != null);
+
+                entry._isDeserialized = _isDeserialized;
+                entry._value = _value;
+            }
+
+            /// <summary>
+            /// Gets the bytes.
+            /// </summary>
+            public byte[] GetBytes()
+            {
+                if (!_isDeserialized)
+                    return (byte[]) _value;
+
+                using (var stream = new MemoryStream())
+                {
+                    new BinaryFormatter().Serialize(stream, _value);
+
+                    return stream.ToArray();
+                }
+            }
         }
 
         /// <summary>
@@ -153,4 +557,4 @@ namespace Apache.Ignite.AspNet.Impl
             }
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
index bdd619c..44b0633 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
@@ -33,7 +33,7 @@ namespace Apache.Ignite.AspNet.Impl
         /// </summary>
         /// <param name="reader">The reader.</param>
         public IgniteSessionStateStoreData(IBinaryRawReader reader) : base(
-            new IgniteSessionStateItemCollection(new KeyValueDirtyTrackedCollection(reader)),
+            new IgniteSessionStateItemCollection(reader),
             DeserializeStaticObjects(reader.ReadByteArray()), reader.ReadInt())
         {
             LockNodeId = reader.ReadGuid();
@@ -47,7 +47,7 @@ namespace Apache.Ignite.AspNet.Impl
         /// <param name="writer">Writer.</param>
         public void WriteBinary(IBinaryRawWriter writer)
         {
-            ((IgniteSessionStateItemCollection)Items).Collection.WriteBinary(writer);
+            ((IgniteSessionStateItemCollection)Items).WriteBinary(writer);
             writer.WriteByteArray(SerializeStaticObjects());
             writer.WriteInt(Timeout);
 
@@ -76,8 +76,8 @@ namespace Apache.Ignite.AspNet.Impl
         /// </summary>
         public bool WriteChangesOnly
         {
-            get { return ((IgniteSessionStateItemCollection) Items).Collection.WriteChangesOnly; }
-            set { ((IgniteSessionStateItemCollection) Items).Collection.WriteChangesOnly = value; }
+            get { return ((IgniteSessionStateItemCollection) Items).WriteChangesOnly; }
+            set { ((IgniteSessionStateItemCollection) Items).WriteChangesOnly = value; }
         }
 
         /// <summary>
@@ -86,7 +86,7 @@ namespace Apache.Ignite.AspNet.Impl
         /// <param name="staticObjects">The static objects.</param>
         /// <param name="timeout">The timeout.</param>
         public IgniteSessionStateStoreData(HttpStaticObjectsCollection staticObjects, int timeout) 
-            : base(new IgniteSessionStateItemCollection(new KeyValueDirtyTrackedCollection()), staticObjects, timeout)
+            : base(new IgniteSessionStateItemCollection(), staticObjects, timeout)
         {
             // No-op.
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/KeyValueDirtyTrackedCollection.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/KeyValueDirtyTrackedCollection.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/KeyValueDirtyTrackedCollection.cs
deleted file mode 100644
index e70cea4..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/KeyValueDirtyTrackedCollection.cs
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-namespace Apache.Ignite.AspNet.Impl
-{
-    using System;
-    using System.Collections.Generic;
-    using System.Diagnostics;
-    using System.Globalization;
-    using System.IO;
-    using System.Linq;
-    using System.Runtime.Serialization.Formatters.Binary;
-    using Apache.Ignite.Core.Binary;
-    using Apache.Ignite.Core.Impl.Common;
-
-    /// <summary>
-    /// Binarizable key-value collection with dirty item tracking.
-    /// </summary>
-    internal class KeyValueDirtyTrackedCollection
-    {
-        /** */
-        private readonly Dictionary<string, int> _dict;
-
-        /** */
-        private readonly List<Entry> _list;
-
-        /** Indicates where this is a new collection, not a deserialized old one. */
-        private readonly bool _isNew;
-
-        /** Indicates that this instance is a diff. */
-        private readonly bool _isDiff;
-
-        /** Removed keys. Hash set because keys can be removed multiple times. */
-        private HashSet<string> _removedKeys;
-
-        /** Indicates that entire collection is dirty and can't be written as a diff. */
-        private bool _dirtyAll;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="KeyValueDirtyTrackedCollection"/> class.
-        /// </summary>
-        /// <param name="reader">The binary reader.</param>
-        internal KeyValueDirtyTrackedCollection(IBinaryRawReader reader)
-        {
-            Debug.Assert(reader != null);
-
-            _isDiff = reader.ReadBoolean();
-
-            var count = reader.ReadInt();
-
-            _dict = new Dictionary<string, int>(count);
-            _list = new List<Entry>(count);
-
-            for (var i = 0; i < count; i++)
-            {
-                var key = reader.ReadString();
-
-                var valBytes = reader.ReadByteArray();
-
-                if (valBytes != null)
-                {
-                    var entry = new Entry(key, true, valBytes);
-
-                    _dict[key] = _list.Count;
-
-                    _list.Add(entry);
-                }
-                else
-                    AddRemovedKey(key);
-            }
-
-            _isNew = false;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="KeyValueDirtyTrackedCollection"/> class.
-        /// </summary>
-        public KeyValueDirtyTrackedCollection()
-        {
-            _dict = new Dictionary<string, int>();
-            _list = new List<Entry>();
-            _isNew = true;
-        }
-
-        /// <summary>
-        /// Gets the number of elements contained in the <see cref="T:System.Collections.ICollection" />.
-        /// </summary>
-        public int Count
-        {
-            get { return _dict.Count; }
-        }
-
-        /// <summary>
-        /// Gets or sets the value with the specified key.
-        /// </summary>
-        public object this[string key]
-        {
-            get
-            {
-                var entry = GetEntry(key);
-
-                if (entry == null)
-                    return null;
-
-                SetDirtyOnRead(entry);
-
-                return entry.Value;
-            }
-            set
-            {
-                var entry = GetOrCreateDirtyEntry(key);
-
-                entry.Value = value;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the value at the specified index.
-        /// </summary>
-        public object this[int index]
-        {
-            get
-            {
-                var entry = _list[index];
-
-                SetDirtyOnRead(entry);
-
-                return entry.Value;
-            }
-            set
-            {
-                var entry = _list[index];
-
-                entry.IsDirty = true;
-
-                entry.Value = value;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is dirty.
-        /// </summary>
-        public bool IsDirty
-        {
-            get { return _dirtyAll || _list.Any(x => x.IsDirty); }
-            set { _dirtyAll = value; }
-        }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether only dirty changed things should be serialized.
-        /// </summary>
-        public bool WriteChangesOnly { get; set; }
-
-        /// <summary>
-        /// Gets the keys.
-        /// </summary>
-        public IEnumerable<string> GetKeys()
-        {
-            return _list.Select(x => x.Key);
-        }
-
-        /// <summary>
-        /// Writes this object to the given writer.
-        /// </summary>
-        /// <param name="writer">Writer.</param>
-        public void WriteBinary(IBinaryRawWriter writer)
-        {
-            IgniteArgumentCheck.NotNull(writer, "writer");
-
-            if (_isDiff)
-                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
-                    "Cannot serialize incomplete {0}.", GetType()));
-
-            if (_isNew || _dirtyAll || !WriteChangesOnly || (_removedKeys == null && _list.All(x => x.IsDirty)))
-            {
-                // Write in full mode.
-                writer.WriteBoolean(false);
-                writer.WriteInt(_list.Count);
-
-                foreach (var entry in _list)
-                {
-                    writer.WriteString(entry.Key);
-
-                    // Write as byte array to enable partial deserialization.
-                    writer.WriteByteArray(entry.GetBytes());
-                }
-            }
-            else
-            {
-                // Write in diff mode.
-                writer.WriteBoolean(true);
-
-                var removed = GetRemovedKeys();
-
-                var count = _list.Count(x => x.IsDirty) + (removed == null ? 0 : removed.Count);
-
-                writer.WriteInt(count);  // reserve count
-
-                // Write removed keys as [key + null].
-                if (removed != null)
-                {
-                    foreach (var removedKey in removed)
-                    {
-                        writer.WriteString(removedKey);
-                        writer.WriteByteArray(null);
-                    }
-                }
-
-                // Write dirty items.
-                foreach (var entry in _list)
-                {
-                    if (!entry.IsDirty)
-                        continue;
-
-                    writer.WriteString(entry.Key);
-
-                    // Write as byte array to enable partial deserialization.
-                    writer.WriteByteArray(entry.GetBytes());
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the removed keys.
-        /// </summary>
-        private ICollection<string> GetRemovedKeys()
-        {
-            if (_removedKeys == null)
-                return null;
-
-            // Filter out existing keys.
-            var removed = new HashSet<string>(_removedKeys);
-
-            foreach (var entry in _list)
-                removed.Remove(entry.Key);
-
-            return removed;
-        }
-
-        /// <summary>
-        /// Removes the specified key.
-        /// </summary>
-        public void Remove(string key)
-        {
-            var index = GetIndex(key);
-
-            if (index < 0)
-                return;
-
-            var entry = _list[index];
-            Debug.Assert(key == entry.Key);
-
-            _list.RemoveAt(index);
-            _dict.Remove(key);
-
-            // Update all indexes.
-            for (var i = 0; i < _list.Count; i++)
-                _dict[_list[i].Key] = i;
-
-            if (entry.IsInitial)
-                AddRemovedKey(key);
-        }
-
-        /// <summary>
-        /// Removes at specified index.
-        /// </summary>
-        public void RemoveAt(int index)
-        {
-            var entry = _list[index];
-
-            _list.RemoveAt(index);
-            _dict.Remove(entry.Key);
-
-            if (entry.IsInitial)
-                AddRemovedKey(entry.Key);
-        }
-
-        /// <summary>
-        /// Clears this instance.
-        /// </summary>
-        public void Clear()
-        {
-            foreach (var entry in _list)
-            {
-                if (entry.IsInitial)
-                    AddRemovedKey(entry.Key);
-            }
-
-            _list.Clear();
-            _dict.Clear();
-
-            _dirtyAll = true;
-        }
-
-        /// <summary>
-        /// Applies the changes.
-        /// </summary>
-        public void ApplyChanges(KeyValueDirtyTrackedCollection changes)
-        {
-            var removed = changes._removedKeys;
-
-            if (removed != null)
-            {
-                foreach (var key in removed)
-                    Remove(key);
-            }
-            else if (!changes._isDiff)
-            {
-                // Not a diff: replace all.
-                Clear();
-            }
-
-            foreach (var changedEntry in changes._list)
-            {
-                var entry = GetOrCreateDirtyEntry(changedEntry.Key);
-
-                // Copy without deserialization.
-                changedEntry.CopyTo(entry);
-            }
-        }
-
-        /// <summary>
-        /// Adds the removed key.
-        /// </summary>
-        private void AddRemovedKey(string key)
-        {
-            Debug.Assert(!_isNew);
-
-            if (_removedKeys == null)
-                _removedKeys = new HashSet<string>();
-
-            _removedKeys.Add(key);
-        }
-
-        /// <summary>
-        /// Gets or creates an entry.
-        /// </summary>
-        private Entry GetOrCreateDirtyEntry(string key)
-        {
-            var entry = GetEntry(key);
-
-            if (entry == null)
-            {
-                entry = new Entry(key, false, null);
-
-                _dict[key] = _list.Count;
-                _list.Add(entry);
-            }
-
-            entry.IsDirty = true;
-
-            return entry;
-        }
-
-        /// <summary>
-        /// Gets the entry.
-        /// </summary>
-        private Entry GetEntry(string key)
-        {
-            IgniteArgumentCheck.NotNull(key, "key");
-
-            int index;
-
-            return !_dict.TryGetValue(key, out index) ? null : _list[index];
-        }
-
-        /// <summary>
-        /// Gets the index.
-        /// </summary>
-        private int GetIndex(string key)
-        {
-            int index;
-
-            return !_dict.TryGetValue(key, out index) ? -1 : index;
-        }
-
-        /// <summary>
-        /// Sets the dirty on read.
-        /// </summary>
-        private static void SetDirtyOnRead(Entry entry)
-        {
-            var type = entry.Value.GetType();
-
-            if (IsImmutable(type))
-                return;
-
-            entry.IsDirty = true;
-        }
-
-        /// <summary>
-        /// Determines whether the specified type is immutable.
-        /// </summary>
-        private static bool IsImmutable(Type type)
-        {
-            type = Nullable.GetUnderlyingType(type) ?? type;  // Unwrap nullable.
-
-            if (type.IsPrimitive)
-                return true;
-
-            if (type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid) || type == typeof(decimal))
-                return true;
-
-            return false;
-        }
-
-        /// <summary>
-        /// Inner entry.
-        /// </summary>
-        private class Entry
-        {
-            /** */
-            public readonly bool IsInitial;
-
-            /** */
-            public readonly string Key;
-
-            /** */
-            public bool IsDirty;
-
-            /** */
-            private object _value;
-
-            /** */
-            private bool _isDeserialized;
-
-            /// <summary>
-            /// Initializes a new instance of the <see cref="Entry"/> class.
-            /// </summary>
-            public Entry(string key, bool isInitial, object value)
-            {
-                Debug.Assert(key != null);
-
-                Key = key;
-                IsInitial = isInitial;
-                _isDeserialized = !isInitial;
-                _value = value;
-            }
-
-            /// <summary>
-            /// Gets or sets the value.
-            /// </summary>
-            public object Value
-            {
-                get
-                {
-                    if (!_isDeserialized)
-                    {
-                        using (var stream = new MemoryStream((byte[])_value))
-                        {
-                            _value = new BinaryFormatter().Deserialize(stream);
-                        }
-
-                        _isDeserialized = true;
-                    }
-
-                    return _value;
-                }
-                set
-                {
-                    _value = value;
-                    _isDeserialized = true;
-                }
-            }
-
-            /// <summary>
-            /// Copies contents to another entry.
-            /// </summary>
-            public void CopyTo(Entry entry)
-            {
-                Debug.Assert(entry != null);
-
-                entry._isDeserialized = _isDeserialized;
-                entry._value = _value;
-            }
-
-            /// <summary>
-            /// Gets the bytes.
-            /// </summary>
-            public byte[] GetBytes()
-            {
-                if (!_isDeserialized)
-                    return (byte[]) _value;
-
-                using (var stream = new MemoryStream())
-                {
-                    new BinaryFormatter().Serialize(stream, _value);
-
-                    return stream.ToArray();
-                }
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index 5e54115..12fa03a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -71,7 +71,6 @@
     <Compile Include="Cache\Affinity\AffinityTopologyVersionTest.cs" />
     <Compile Include="Cache\CacheResultTest.cs" />
     <Compile Include="Cache\Store\CacheStoreAdapterTest.cs" />
-    <Compile Include="AspNet\KeyValueDirtyTrackedCollectionTest.cs" />
     <Compile Include="Collections\MultiValueDictionaryTest.cs" />
     <Compile Include="Collections\ReadOnlyCollectionTest.cs" />
     <Compile Include="Collections\ReadOnlyDictionaryTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/IgniteSessionStateItemCollectionTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/IgniteSessionStateItemCollectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/IgniteSessionStateItemCollectionTest.cs
index 9f8f34d..45c2326 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/IgniteSessionStateItemCollectionTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/IgniteSessionStateItemCollectionTest.cs
@@ -18,9 +18,11 @@
 namespace Apache.Ignite.Core.Tests.AspNet
 {
     using System;
+    using System.IO;
     using System.Linq;
     using Apache.Ignite.AspNet.Impl;
-    using Apache.Ignite.Core.Impl.Collections;
+    using Apache.Ignite.Core.Impl.Binary;
+    using Apache.Ignite.Core.Impl.Binary.IO;
     using NUnit.Framework;
 
     /// <summary>
@@ -34,28 +36,33 @@ namespace Apache.Ignite.Core.Tests.AspNet
         [Test]
         public void TestEmpty()
         {
-            var col = new IgniteSessionStateItemCollection(new KeyValueDirtyTrackedCollection());
+            var col1 = new IgniteSessionStateItemCollection();
+            var col2 = SerializeDeserialize(col1);
 
-            // Check empty.
-            Assert.IsFalse(col.Dirty);
-            Assert.IsFalse(col.IsSynchronized);
-            Assert.AreEqual(0, col.Count);
-            Assert.IsNotNull(col.SyncRoot);
-            Assert.IsEmpty(col);
-            Assert.IsEmpty(col.OfType<string>().ToArray());
-            Assert.IsEmpty(col.Keys);
-            Assert.IsNotNull(col.SyncRoot);
+            foreach (var col in new[] { col1, col2 })
+            {
+                Assert.IsFalse(col.Dirty);
+                Assert.IsFalse(col.IsSynchronized);
+                Assert.AreEqual(0, col.Count);
+                Assert.IsNotNull(col.SyncRoot);
+                Assert.IsEmpty(col);
+                Assert.IsEmpty(col.OfType<string>().ToArray());
+                Assert.IsEmpty(col.Keys);
+                Assert.IsNotNull(col.SyncRoot);
 
-            col.Clear();
-            col.Remove("key");
-            Assert.Throws<ArgumentOutOfRangeException>(() => col.RemoveAt(0));
+                Assert.IsNull(col["key"]);
+                Assert.Throws<ArgumentOutOfRangeException>(() => col[0] = "x");
+                Assert.Throws<ArgumentOutOfRangeException>(() => Assert.AreEqual(0, col[0]));
+                Assert.Throws<ArgumentOutOfRangeException>(() => col.RemoveAt(0));
 
-            Assert.IsNull(col["key"]);
-            Assert.Throws<ArgumentOutOfRangeException>(() => Assert.IsNull(col[0]));
+                col.Clear();
+                col.Remove("test");
 
-            var keys = new[] {"1"};
-            col.CopyTo(keys, 0);
-            Assert.AreEqual(new[] {"1"}, keys);
+                Assert.AreEqual(0, col.Count);
+
+                col.Dirty = true;
+                Assert.IsTrue(col.Dirty);
+            }
         }
 
         /// <summary>
@@ -64,8 +71,7 @@ namespace Apache.Ignite.Core.Tests.AspNet
         [Test]
         public void TestModification()
         {
-            var innerCol = new KeyValueDirtyTrackedCollection();
-            var col = new IgniteSessionStateItemCollection(innerCol);
+            var col = new IgniteSessionStateItemCollection();
 
             // Populate and check.
             col["key"] = "val";
@@ -131,8 +137,134 @@ namespace Apache.Ignite.Core.Tests.AspNet
             Assert.AreEqual(0, col.Count);
 
             // Set dirty.
-            var col1 = new IgniteSessionStateItemCollection(innerCol) {Dirty = true};
+            var col1 = new IgniteSessionStateItemCollection {Dirty = true};
             Assert.IsTrue(col1.Dirty);
         }
+
+        /// <summary>
+        /// Tests dirty tracking.
+        /// </summary>
+        [Test]
+        public void TestApplyChanges()
+        {
+            Func<IgniteSessionStateItemCollection> getCol = () =>
+            {
+                var res = new IgniteSessionStateItemCollection();
+
+                res["1"] = 1;
+                res["2"] = 2;
+                res["3"] = 3;
+
+                return res;
+            };
+
+            var col = getCol();
+
+            var col0 = SerializeDeserialize(col);
+
+            Assert.AreEqual(3, col0.Count);
+
+            col0.Remove("1");
+            col0["2"] = 22;
+            col0["4"] = 44;
+
+            // Apply non-serialized changes.
+            col.ApplyChanges(col0);
+
+            Assert.AreEqual(3, col.Count);
+            Assert.AreEqual(null, col["1"]);
+            Assert.AreEqual(22, col["2"]);
+            Assert.AreEqual(3, col["3"]);
+            Assert.AreEqual(44, col["4"]);
+
+            // Apply serialized changes without WriteChangesOnly.
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(3, col.Count);
+            Assert.AreEqual(null, col["1"]);
+            Assert.AreEqual(22, col["2"]);
+            Assert.AreEqual(3, col["3"]);
+            Assert.AreEqual(44, col["4"]);
+
+            // Apply serialized changes with WriteChangesOnly.
+            col0.WriteChangesOnly = true;
+
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(3, col.Count);
+            Assert.AreEqual(null, col["1"]);
+            Assert.AreEqual(22, col["2"]);
+            Assert.AreEqual(3, col["3"]);
+            Assert.AreEqual(44, col["4"]);
+
+            // Remove key then add back.
+            col0.Remove("2");
+            col0.Remove("3");
+            col0["2"] = 222;
+
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(2, col.Count);
+            Assert.AreEqual(222, col["2"]);
+            Assert.AreEqual(44, col["4"]);
+
+            // Remove all.
+            col0 = SerializeDeserialize(getCol());
+            col0.WriteChangesOnly = true;
+            col0.Clear();
+
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(0, col.Count);
+
+            // Add to empty.
+            col0["-1"] = -1;
+            col0["-2"] = -2;
+
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(2, col.Count);
+            Assert.AreEqual(-1, col0["-1"]);
+            Assert.AreEqual(-2, col0["-2"]);
+
+            // Remove initial key, then add it back, then remove again.
+            col0 = SerializeDeserialize(getCol());
+            col0.WriteChangesOnly = true;
+
+            col0.Remove("1");
+            col0.Remove("2");
+            col0["1"] = "111";
+            col0.Remove("1");
+
+            col = getCol();
+            col.ApplyChanges(SerializeDeserialize(col0));
+
+            Assert.AreEqual(1, col.Count);
+            Assert.AreEqual(3, col["3"]);
+        }
+
+        /// <summary>
+        /// Serializes and deserializes back an instance.
+        /// </summary>
+        private static IgniteSessionStateItemCollection SerializeDeserialize(IgniteSessionStateItemCollection data)
+        {
+            var marsh = BinaryUtils.Marshaller;
+
+            using (var stream = new BinaryHeapStream(128))
+            {
+                var writer = marsh.StartMarshal(stream);
+
+                data.WriteBinary(writer.GetRawWriter());
+
+                stream.Seek(0, SeekOrigin.Begin);
+
+                return new IgniteSessionStateItemCollection(marsh.StartUnmarshal(stream));
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/11e69b48/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/KeyValueDirtyTrackedCollectionTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/KeyValueDirtyTrackedCollectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/KeyValueDirtyTrackedCollectionTest.cs
deleted file mode 100644
index 9c8947b..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/KeyValueDirtyTrackedCollectionTest.cs
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-namespace Apache.Ignite.Core.Tests.AspNet
-{
-    using System;
-    using System.IO;
-    using System.Linq;
-    using Apache.Ignite.AspNet.Impl;
-    using Apache.Ignite.Core.Impl.Binary;
-    using Apache.Ignite.Core.Impl.Binary.IO;
-    using NUnit.Framework;
-
-    /// <summary>
-    /// Tests for <see cref="KeyValueDirtyTrackedCollection"/>
-    /// </summary>
-    public class KeyValueDirtyTrackedCollectionTest
-    {
-        /// <summary>
-        /// Tests the empty collection.
-        /// </summary>
-        [Test]
-        public void TestEmpty()
-        {
-            var col1 = new KeyValueDirtyTrackedCollection();
-            var col2 = SerializeDeserialize(col1);
-
-            foreach (var col in new[] {col1, col2})
-            {
-                Assert.AreEqual(0, col.Count);
-                Assert.IsFalse(col.IsDirty);
-                Assert.IsEmpty(col.GetKeys());
-
-                Assert.IsNull(col["key"]);
-                Assert.Throws<ArgumentOutOfRangeException>(() => col[0] = "x");
-                Assert.Throws<ArgumentOutOfRangeException>(() => Assert.AreEqual(0, col[0]));
-                Assert.Throws<ArgumentOutOfRangeException>(() => col.RemoveAt(0));
-
-                col.Clear();
-                col.Remove("test");
-
-                Assert.AreEqual(0, col.Count);
-
-                col.IsDirty = true;
-                Assert.IsTrue(col.IsDirty);
-            }
-        }
-
-        /// <summary>
-        /// Tests the modification.
-        /// </summary>
-        [Test]
-        public void TestModification()
-        {
-            var col = new KeyValueDirtyTrackedCollection();
-
-            // Populate and check.
-            col["key"] = "val";
-            col["1"] = 1;
-
-            Assert.AreEqual("val", col["key"]);
-            Assert.AreEqual(1, col["1"]);
-
-            Assert.AreEqual(2, col.Count);
-            Assert.IsTrue(col.IsDirty);
-            Assert.AreEqual(new[] {"key", "1"}, col.GetKeys().ToArray());
-
-            // Modify using index.
-            col[0] = "val1";
-            col[1] = 2;
-
-            Assert.AreEqual("val1", col["key"]);
-            Assert.AreEqual(2, col["1"]);
-
-            // Modify using key.
-            col["1"] = 3;
-            col["key"] = "val2";
-
-            Assert.AreEqual("val2", col["key"]);
-            Assert.AreEqual(3, col["1"]);
-
-            // Serialize.
-            var col0 = SerializeDeserialize(col);
-
-            Assert.AreEqual(col.GetKeys(), col0.GetKeys());
-            Assert.AreEqual(col.GetKeys().Select(x => col[x]), col0.GetKeys().Select(x => col0[x]));
-
-            // Remove.
-            col["2"] = 2;
-            col["3"] = 3;
-
-            col.Remove("invalid");
-            Assert.AreEqual(4, col.Count);
-
-            col.Remove("1");
-
-            Assert.AreEqual(new[] { "key", "2", "3" }, col.GetKeys());
-            Assert.AreEqual(null, col["1"]);
-
-            Assert.AreEqual("val2", col["key"]);
-            Assert.AreEqual("val2", col[0]);
-
-            Assert.AreEqual(2, col["2"]);
-            Assert.AreEqual(2, col[1]);
-
-            Assert.AreEqual(3, col["3"]);
-            Assert.AreEqual(3, col[2]);
-
-            // RemoveAt.
-            col0.RemoveAt(0);
-            Assert.AreEqual(new[] { "1" }, col0.GetKeys());
-
-            // Clear.
-            Assert.AreEqual(3, col.Count);
-
-            col.Clear();
-            Assert.AreEqual(0, col.Count);
-        }
-
-        /// <summary>
-        /// Tests dirty tracking.
-        /// </summary>
-        [Test]
-        public void TestApplyChanges()
-        {
-            Func<KeyValueDirtyTrackedCollection> getCol = () =>
-            {
-                var res = new KeyValueDirtyTrackedCollection();
-
-                res["1"] = 1;
-                res["2"] = 2;
-                res["3"] = 3;
-
-                return res;
-            };
-
-            var col = getCol();
-
-            var col0 = SerializeDeserialize(col);
-
-            Assert.AreEqual(3, col0.Count);
-
-            col0.Remove("1");
-            col0["2"] = 22;
-            col0["4"] = 44;
-
-            // Apply non-serialized changes.
-            col.ApplyChanges(col0);
-
-            Assert.AreEqual(3, col.Count);
-            Assert.AreEqual(null, col["1"]);
-            Assert.AreEqual(22, col["2"]);
-            Assert.AreEqual(3, col["3"]);
-            Assert.AreEqual(44, col["4"]);
-
-            // Apply serialized changes without WriteChangesOnly.
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(3, col.Count);
-            Assert.AreEqual(null, col["1"]);
-            Assert.AreEqual(22, col["2"]);
-            Assert.AreEqual(3, col["3"]);
-            Assert.AreEqual(44, col["4"]);
-
-            // Apply serialized changes with WriteChangesOnly.
-            col0.WriteChangesOnly = true;
-
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(3, col.Count);
-            Assert.AreEqual(null, col["1"]);
-            Assert.AreEqual(22, col["2"]);
-            Assert.AreEqual(3, col["3"]);
-            Assert.AreEqual(44, col["4"]);
-
-            // Remove key then add back.
-            col0.Remove("2");
-            col0.Remove("3");
-            col0["2"] = 222;
-
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(2, col.Count);
-            Assert.AreEqual(222, col["2"]);
-            Assert.AreEqual(44, col["4"]);
-
-            // Remove all.
-            col0 = SerializeDeserialize(getCol());
-            col0.WriteChangesOnly = true;
-            col0.Clear();
-
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(0, col.Count);
-
-            // Add to empty.
-            col0["-1"] = -1;
-            col0["-2"] = -2;
-
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(2, col.Count);
-            Assert.AreEqual(-1, col0["-1"]);
-            Assert.AreEqual(-2, col0["-2"]);
-
-            // Remove initial key, then add it back, then remove again.
-            col0 = SerializeDeserialize(getCol());
-            col0.WriteChangesOnly = true;
-
-            col0.Remove("1");
-            col0.Remove("2");
-            col0["1"] = "111";
-            col0.Remove("1");
-
-            col = getCol();
-            col.ApplyChanges(SerializeDeserialize(col0));
-
-            Assert.AreEqual(1, col.Count);
-            Assert.AreEqual(3, col["3"]);
-        }
-
-        /// <summary>
-        /// Serializes and deserializes back an instance.
-        /// </summary>
-        private static KeyValueDirtyTrackedCollection SerializeDeserialize(KeyValueDirtyTrackedCollection data)
-        {
-            var marsh = BinaryUtils.Marshaller;
-
-            using (var stream = new BinaryHeapStream(128))
-            {
-                var writer = marsh.StartMarshal(stream);
-
-                data.WriteBinary(writer.GetRawWriter());
-
-                stream.Seek(0, SeekOrigin.Begin);
-
-                return new KeyValueDirtyTrackedCollection(marsh.StartUnmarshal(stream));
-            }
-        }
-    }
-}


Mime
View raw message