ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ptupit...@apache.org
Subject ignite git commit: Refactor provider to perform all cache operation via the extension
Date Tue, 13 Sep 2016 14:57:20 GMT
Repository: ignite
Updated Branches:
  refs/heads/ignite-3199-1 b718bba55 -> 491057ac3


Refactor provider to perform all cache operation via the extension


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

Branch: refs/heads/ignite-3199-1
Commit: 491057ac3a38297c6f4401e108efbbc84124df90
Parents: b718bba
Author: Pavel Tupitsyn <ptupitsyn@apache.org>
Authored: Tue Sep 13 17:57:07 2016 +0300
Committer: Pavel Tupitsyn <ptupitsyn@apache.org>
Committed: Tue Sep 13 17:57:07 2016 +0300

----------------------------------------------------------------------
 .../PlatformDotnetSessionCacheExtension.java    |  47 +-
 .../Apache.Ignite.AspNet.csproj                 |   3 +
 .../IgniteSessionStateStoreProvider.cs          |  90 +++-
 .../Impl/IgniteSessionStateItemCollection.cs    |   1 -
 .../Impl/IgniteSessionStateStoreData.cs         |   1 -
 .../Impl/KeyValueDirtyTrackedCollection.cs      | 500 ++++++++++++++++++
 .../Impl/SessionStateData.cs                    | 101 ++++
 .../Impl/SessionStateLockResult.cs              |  85 ++++
 .../Apache.Ignite.Core.Tests.csproj             |   2 +-
 .../BinarizableSessionStateStoreDataTest.cs     |   2 +-
 .../KeyValueDirtyTrackedCollectionTest.cs       | 238 +++++++++
 .../KeyValueDirtyTrackedCollectionTest.cs       | 238 ---------
 .../Apache.Ignite.Core.csproj                   |   3 -
 .../Impl/AspNet/SessionStateData.cs             | 105 ----
 .../Impl/AspNet/SessionStateLockResult.cs       |  93 ----
 .../Impl/Binary/Marshaller.cs                   |   8 +-
 .../Apache.Ignite.Core/Impl/Cache/CacheImpl.cs  |   7 +-
 .../Impl/Cache/ICacheInternal.cs                |   4 +-
 .../KeyValueDirtyTrackedCollection.cs           | 509 -------------------
 19 files changed, 1055 insertions(+), 982 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotnetSessionCacheExtension.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotnetSessionCacheExtension.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotnetSessionCacheExtension.java
index 20a5dbd..9ee72ec 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotnetSessionCacheExtension.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotnetSessionCacheExtension.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.platform.websession;
 
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.binary.BinaryReader;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
 import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
 import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
@@ -35,10 +36,19 @@ public class PlatformDotnetSessionCacheExtension implements PlatformCacheExtensi
     private static final int EXT_ID = 0;
 
     /** Operation: session lock. */
-    private static final int OP_SESSION_LOCK = 1;
+    private static final int OP_LOCK = 1;
 
     /** Operation: session set/unlock. */
-    private static final int OP_SESSION_SET_AND_UNLOCK = 2;
+    private static final int OP_SET_AND_UNLOCK = 2;
+
+    /** Operation: session get without lock. */
+    private static final int OP_GET = 3;
+
+    /** Operation: session put without lock. */
+    private static final int OP_PUT = 4;
+
+    /** Operation: session remove without lock. */
+    private static final int OP_REMOVE = 5;
 
     /** {@inheritDoc} */
     @Override public int id() {
@@ -50,7 +60,7 @@ public class PlatformDotnetSessionCacheExtension implements PlatformCacheExtensi
     @Override public long processInOutStreamLong(PlatformCache target, int type, BinaryRawReaderEx reader,
         PlatformMemory mem) throws IgniteCheckedException {
         switch (type) {
-            case OP_SESSION_LOCK: {
+            case OP_LOCK: {
                 String key = reader.readString();
                 UUID lockNodeId = reader.readUuid();
                 long lockId = reader.readLong();
@@ -62,7 +72,7 @@ public class PlatformDotnetSessionCacheExtension implements PlatformCacheExtensi
                 return target.writeResult(mem, res);
             }
 
-            case OP_SESSION_SET_AND_UNLOCK:
+            case OP_SET_AND_UNLOCK: {
                 String key = reader.readString();
 
                 PlatformDotnetSessionSetAndUnlockProcessor proc;
@@ -82,6 +92,35 @@ public class PlatformDotnetSessionCacheExtension implements PlatformCacheExtensi
                 target.rawCache().invoke(key, proc);
 
                 return target.writeResult(mem, null);
+            }
+
+            case OP_GET: {
+                String key = reader.readString();
+
+                PlatformDotnetSessionData data = (PlatformDotnetSessionData)target.rawCache().get(key);
+
+                return target.writeResult(mem, data);
+            }
+
+            case OP_PUT: {
+                String key = reader.readString();
+
+                PlatformDotnetSessionData data = new PlatformDotnetSessionData();
+
+                data.readBinary((BinaryReader)reader);
+
+                target.rawCache().put(key, data);
+
+                return target.writeResult(mem, null);
+            }
+
+            case OP_REMOVE: {
+                String key = reader.readString();
+
+                target.rawCache().remove(key);
+
+                return target.writeResult(mem, null);
+            }
         }
 
         throw new IgniteCheckedException("Unsupported operation type: " + type);

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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 cab6a52..40aab55 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
@@ -53,6 +53,9 @@
     <Compile Include="Impl\ExpiryCacheHolder.cs" />
     <Compile Include="Impl\IgniteSessionStateItemCollection.cs" />
     <Compile Include="Impl\IgniteSessionStateStoreData.cs" />
+    <Compile Include="Impl\KeyValueDirtyTrackedCollection.cs" />
+    <Compile Include="Impl\SessionStateData.cs" />
+    <Compile Include="Impl\SessionStateLockResult.cs" />
     <Compile Include="Package-Info.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
index 33e6c35..1f04b5d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
@@ -28,7 +28,6 @@ namespace Apache.Ignite.AspNet
     using Apache.Ignite.Core;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
-    using Apache.Ignite.Core.Impl.AspNet;
     using Apache.Ignite.Core.Impl.Cache;
     using Apache.Ignite.Core.Log;
 
@@ -56,10 +55,19 @@ namespace Apache.Ignite.AspNet
         private enum Op
         {
             /** Lock the session data. */
-            SessionLock = 1,
+            Lock = 1,
 
             /** Update and unlock the session data. */
-            SessionSetAndUnlock = 2
+            SetAndUnlock = 2,
+
+            /** Get the data without lock. */
+            Get = 3,
+
+            /** Put the data without lock. */
+            Put = 4,
+
+            /** Remove the data without lock. */
+            Remove = 5
         }
 
         /** Application id config parameter. */
@@ -170,9 +178,9 @@ namespace Apache.Ignite.AspNet
             locked = false;
 
             var key = GetKey(id);
-            SessionStateData data;
+            var data = GetItem(key);
 
-            if (Cache.TryGet(key, out data))
+            if (data != null)
             {
                 locked = data.LockNodeId != null;
 
@@ -316,7 +324,7 @@ namespace Apache.Ignite.AspNet
         {
             Log("RemoveItem", id, context);
 
-            Cache.Remove(GetKey(id));
+            RemoveItem(GetKey(id));
         }
 
         /// <summary>
@@ -367,7 +375,7 @@ namespace Apache.Ignite.AspNet
 
             var data = new SessionStateData { Timeout = timeout };
 
-            cache[key] = data;
+            PutItem(key, data, cache);
         }
 
         /// <summary>
@@ -446,12 +454,13 @@ namespace Apache.Ignite.AspNet
         /// </summary>
         private SessionStateLockResult LockItem(string key, long lockId)
         {
-            return ((ICacheInternal) Cache).InvokeExtension<SessionStateLockResult>(ExtensionId, (int) Op.SessionLock,
+            return OutInOp(Op.Lock,
                 w =>
                 {
                     w.WriteString(key);
                     WriteLockInfo(w, lockId, true);
-                });
+                }, 
+                r => new SessionStateLockResult(r));
         }
 
         /// <summary>
@@ -459,7 +468,7 @@ namespace Apache.Ignite.AspNet
         /// </summary>
         private void UnlockItem(string key, long lockId)
         {
-            ((ICacheInternal) Cache).InvokeExtension<object>(ExtensionId, (int) Op.SessionSetAndUnlock,
+            OutOp(Op.SetAndUnlock,
                 w =>
                 {
                     w.WriteString(key);
@@ -477,13 +486,60 @@ namespace Apache.Ignite.AspNet
 
             var cache = _expiryCacheHolder.GetCacheWithExpiry(data.Timeout * 60);
 
-            ((ICacheInternal) cache).InvokeExtension<object>(ExtensionId, (int) Op.SessionSetAndUnlock,
-                w =>
-                {
-                    w.WriteString(key);
-                    w.WriteBoolean(true); // Unlock and update.
-                    w.WriteObject(data);
-                });
+            OutOp(Op.SetAndUnlock, w =>
+            {
+                w.WriteString(key);
+                w.WriteBoolean(true); // Unlock and update.
+                data.WriteBinary(w);
+            }, cache);
+        }
+
+        /// <summary>
+        /// Puts the item.
+        /// </summary>
+        private void PutItem(string key, SessionStateData data, ICache<string, SessionStateData> cache)
+        {
+            OutOp(Op.Put, w =>
+            {
+                w.WriteString(key);
+                data.WriteBinary(w);
+            }, cache);
+        }
+
+        /// <summary>
+        /// Gets the item.
+        /// </summary>
+        private SessionStateData GetItem(string key)
+        {
+            return OutInOp(Op.Get, w => w.WriteString(key), r => new SessionStateData(r));
+        }
+
+        /// <summary>
+        /// Removes the item.
+        /// </summary>
+        private void RemoveItem(string key)
+        {
+            OutOp(Op.Remove, w => w.WriteString(key));
+        }
+
+        /// <summary>
+        /// Invokes the extension operation.
+        /// </summary>
+        private void OutOp(Op op, Action<IBinaryRawWriter> writeAction, 
+            ICache<string, SessionStateData> cache = null)
+        {
+            OutInOp<object>(op, writeAction, null, cache);
+        }
+
+        /// <summary>
+        /// Invokes the extension operation.
+        /// </summary>
+        private T OutInOp<T>(Op op, Action<IBinaryRawWriter> writeAction, Func<IBinaryRawReader, T> readFunc, 
+            ICache<string, SessionStateData> cache = null)
+        {
+            cache = cache ?? Cache;
+
+            return ((ICacheInternal) cache).InvokeExtension(ExtensionId, (int) op, writeAction, readFunc);
         }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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 ee436f6..273b6d6 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateItemCollection.cs
@@ -22,7 +22,6 @@ namespace Apache.Ignite.AspNet.Impl
     using System.Collections.Specialized;
     using System.Diagnostics;
     using System.Web.SessionState;
-    using Apache.Ignite.Core.Impl.Collections;
     using Apache.Ignite.Core.Impl.Common;
 
     /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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 11ffb18..5bf58e2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/IgniteSessionStateStoreData.cs
@@ -20,7 +20,6 @@ namespace Apache.Ignite.AspNet.Impl
     using System.IO;
     using System.Web;
     using System.Web.SessionState;
-    using Apache.Ignite.Core.Impl.AspNet;
 
     /// <summary>
     /// Wrapper for <see cref="SessionStateData"/>.

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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
new file mode 100644
index 0000000..8569607
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/KeyValueDirtyTrackedCollection.cs
@@ -0,0 +1,500 @@
+/*
+ * 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.Linq;
+    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 + (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);
+
+                        count++;
+                    }
+                }
+
+                // 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());
+
+                    count++;
+                }
+            }
+        }
+
+        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)
+                    {
+                        // TODO: BinaryFormatter
+                        //_value = _marsh.Unmarshal<object>((byte[]) _value);
+                        _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;
+
+                // TODO: BinaryFormatter
+                //return marsh.Marshal(_value);
+                return null;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateData.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateData.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateData.cs
new file mode 100644
index 0000000..adad69c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateData.cs
@@ -0,0 +1,101 @@
+/*
+ * 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 Apache.Ignite.Core.Binary;
+
+    /// <summary>
+    /// Binarizable SessionStateStoreData. 
+    /// Does not override System.Web.SessionState.SessionStateStoreData to avoid dependency on System.Web.
+    /// </summary>
+    internal class SessionStateData
+    {
+        /** Items. */
+        private readonly KeyValueDirtyTrackedCollection _items;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionStateData"/> class.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        public SessionStateData(IBinaryRawReader reader)
+        {
+            Timeout = reader.ReadInt();
+            LockNodeId = reader.ReadGuid();
+            LockId = reader.ReadLong();
+            LockTime = reader.ReadTimestamp();
+            _items = new KeyValueDirtyTrackedCollection(reader);
+            StaticObjects = reader.ReadByteArray();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionStateData"/> class.
+        /// </summary>
+        public SessionStateData()
+        {
+            _items = new KeyValueDirtyTrackedCollection();
+        }
+
+        /// <summary>
+        /// Gets the items.
+        /// </summary>
+        public KeyValueDirtyTrackedCollection Items
+        {
+            get { return _items; }
+        }
+
+        /// <summary>
+        /// Gets or sets the static objects.
+        /// </summary>
+        public byte[] StaticObjects { get; set; }
+
+        /// <summary>
+        /// Gets or sets the timeout.
+        /// </summary>
+        public int Timeout { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lock node id. Null means not locked.
+        /// </summary>
+        public Guid? LockNodeId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lock id.
+        /// </summary>
+        public long LockId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lock time.
+        /// </summary>
+        public DateTime? LockTime { get; set; }
+
+        /// <summary>
+        /// Writes this object to the given writer.
+        /// </summary>
+        /// <param name="writer">Writer.</param>
+        public void WriteBinary(IBinaryRawWriter writer)
+        {
+            writer.WriteInt(Timeout);
+            writer.WriteGuid(LockNodeId);
+            writer.WriteLong(LockId);
+            writer.WriteTimestamp(LockTime);
+            Items.WriteBinary(writer);
+            writer.WriteByteArray(StaticObjects);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateLockResult.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateLockResult.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateLockResult.cs
new file mode 100644
index 0000000..69cc723
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/SessionStateLockResult.cs
@@ -0,0 +1,85 @@
+/*
+ * 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.Diagnostics;
+    using System.Globalization;
+    using Apache.Ignite.Core.Binary;
+
+    /// <summary>
+    /// Result of the session state lock processor.
+    /// </summary>
+    internal class SessionStateLockResult
+    {
+        /** Success flag. */
+        private readonly bool _success;
+
+        /** Session state data. */
+        private readonly SessionStateData _data;
+
+        /** Lock time. */
+        private readonly DateTime? _lockTime;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionStateLockResult"/> class.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        public SessionStateLockResult(IBinaryRawReader reader)
+        {
+            _success = reader.ReadBoolean();
+            _data = reader.ReadObject<SessionStateData>();
+            _lockTime = reader.ReadTimestamp();
+
+            Debug.Assert(_success ^ (_data == null));
+            Debug.Assert(_success ^ (_lockTime != null));
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether lock succeeded.
+        /// </summary>
+        public bool Success
+        {
+            get { return _success; }
+        }
+
+        /// <summary>
+        /// Gets the data. Null when <see cref="Success"/> is <c>false</c>.
+        /// </summary>
+        public SessionStateData Data
+        {
+            get { return _data; }
+        }
+
+        /// <summary>
+        /// Gets the lock time. Null when <see cref="Success"/> is <c>true</c>.
+        /// </summary>
+        public DateTime? LockTime
+        {
+            get { return _lockTime; }
+        }
+
+        /// <summary>
+        /// Returns a <see cref="string" /> that represents this instance.
+        /// </summary>
+        public override string ToString()
+        {
+            return string.Format(CultureInfo.InvariantCulture, "{0} [Success={1}]", GetType().Name, _success);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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 d74fdf4..bc628b4 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
@@ -72,7 +72,7 @@
     <Compile Include="Cache\Affinity\AffinityTopologyVersionTest.cs" />
     <Compile Include="Cache\CacheResultTest.cs" />
     <Compile Include="Cache\Store\CacheStoreAdapterTest.cs" />
-    <Compile Include="Collections\KeyValueDirtyTrackedCollectionTest.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/491057ac/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/BinarizableSessionStateStoreDataTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/BinarizableSessionStateStoreDataTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/BinarizableSessionStateStoreDataTest.cs
index aaccdfd..0bb711e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/BinarizableSessionStateStoreDataTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/BinarizableSessionStateStoreDataTest.cs
@@ -18,7 +18,7 @@
 namespace Apache.Ignite.Core.Tests.AspNet
 {
     using System;
-    using Apache.Ignite.Core.Impl.AspNet;
+    using Apache.Ignite.AspNet.Impl;
     using NUnit.Framework;
 
     /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/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
new file mode 100644
index 0000000..f3a29b2
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/AspNet/KeyValueDirtyTrackedCollectionTest.cs
@@ -0,0 +1,238 @@
+/*
+ * 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.Linq;
+    using Apache.Ignite.AspNet.Impl;
+    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 = TestUtils.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 = TestUtils.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 = TestUtils.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(TestUtils.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(TestUtils.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(TestUtils.SerializeDeserialize(col0));
+
+            Assert.AreEqual(2, col.Count);
+            Assert.AreEqual(222, col["2"]);
+            Assert.AreEqual(44, col["4"]);
+
+            // Remove all.
+            col0 = TestUtils.SerializeDeserialize(getCol());
+            col0.WriteChangesOnly = true;
+            col0.Clear();
+
+            col = getCol();
+            col.ApplyChanges(TestUtils.SerializeDeserialize(col0));
+
+            Assert.AreEqual(0, col.Count);
+
+            // Add to empty.
+            col0["-1"] = -1;
+            col0["-2"] = -2;
+
+            col = getCol();
+            col.ApplyChanges(TestUtils.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 = TestUtils.SerializeDeserialize(getCol());
+            col0.WriteChangesOnly = true;
+
+            col0.Remove("1");
+            col0.Remove("2");
+            col0["1"] = "111";
+            col0.Remove("1");
+
+            col = getCol();
+            col.ApplyChanges(TestUtils.SerializeDeserialize(col0));
+
+            Assert.AreEqual(1, col.Count);
+            Assert.AreEqual(3, col["3"]);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Collections/KeyValueDirtyTrackedCollectionTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Collections/KeyValueDirtyTrackedCollectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Collections/KeyValueDirtyTrackedCollectionTest.cs
deleted file mode 100644
index 7bdf16f..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Collections/KeyValueDirtyTrackedCollectionTest.cs
+++ /dev/null
@@ -1,238 +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.Collections
-{
-    using System;
-    using System.Linq;
-    using Apache.Ignite.Core.Impl.Collections;
-    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 = TestUtils.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 = TestUtils.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 = TestUtils.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(TestUtils.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(TestUtils.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(TestUtils.SerializeDeserialize(col0));
-
-            Assert.AreEqual(2, col.Count);
-            Assert.AreEqual(222, col["2"]);
-            Assert.AreEqual(44, col["4"]);
-
-            // Remove all.
-            col0 = TestUtils.SerializeDeserialize(getCol());
-            col0.WriteChangesOnly = true;
-            col0.Clear();
-
-            col = getCol();
-            col.ApplyChanges(TestUtils.SerializeDeserialize(col0));
-
-            Assert.AreEqual(0, col.Count);
-
-            // Add to empty.
-            col0["-1"] = -1;
-            col0["-2"] = -2;
-
-            col = getCol();
-            col.ApplyChanges(TestUtils.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 = TestUtils.SerializeDeserialize(getCol());
-            col0.WriteChangesOnly = true;
-
-            col0.Remove("1");
-            col0.Remove("2");
-            col0["1"] = "111";
-            col0.Remove("1");
-
-            col = getCol();
-            col.ApplyChanges(TestUtils.SerializeDeserialize(col0));
-
-            Assert.AreEqual(1, col.Count);
-            Assert.AreEqual(3, col["3"]);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index 4be28ac..953b72f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -90,8 +90,6 @@
     <Compile Include="Discovery\Tcp\Multicast\Package-Info.cs" />
     <Compile Include="Discovery\Tcp\Package-Info.cs" />
     <Compile Include="Discovery\Tcp\Static\Package-Info.cs" />
-    <Compile Include="Impl\AspNet\SessionStateData.cs" />
-    <Compile Include="Impl\AspNet\SessionStateLockResult.cs" />
     <Compile Include="Impl\Binary\BinaryReflectiveSerializerInternal.cs" />
     <Compile Include="Impl\Binary\IBinarySerializerInternal.cs" />
     <Compile Include="Binary\Package-Info.cs" />
@@ -181,7 +179,6 @@
     <Compile Include="Impl\Binary\UserSerializerProxy.cs" />
     <Compile Include="Impl\Cache\Affinity\AffinityFunctionSerializer.cs" />
     <Compile Include="Impl\Cache\Affinity\PlatformAffinityFunction.cs" />
-    <Compile Include="Impl\Collections\KeyValueDirtyTrackedCollection.cs" />
     <Compile Include="Impl\Common\ObjectInfoHolder.cs" />
     <Compile Include="Impl\Common\Platform.cs" />
     <Compile Include="Impl\Cache\Event\JavaCacheEntryEventFilter.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateData.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateData.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateData.cs
deleted file mode 100644
index 761f1cb..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateData.cs
+++ /dev/null
@@ -1,105 +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.Impl.AspNet
-{
-    using System;
-    using Apache.Ignite.Core.Binary;
-    using Apache.Ignite.Core.Impl.Binary;
-    using Apache.Ignite.Core.Impl.Collections;
-
-    /// <summary>
-    /// Binarizable SessionStateStoreData. 
-    /// Does not override System.Web.SessionState.SessionStateStoreData to avoid dependency on System.Web.
-    /// </summary>
-    public class SessionStateData : IBinaryWriteAware
-    {
-        /** Items. */
-        private readonly KeyValueDirtyTrackedCollection _items;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="SessionStateData"/> class.
-        /// </summary>
-        /// <param name="reader">The reader.</param>
-        internal SessionStateData(BinaryReader reader)
-        {
-            Timeout = reader.ReadInt();
-            LockNodeId = reader.ReadGuid();
-            LockId = reader.ReadLong();
-            LockTime = reader.ReadTimestamp();
-            _items = new KeyValueDirtyTrackedCollection(reader);
-            StaticObjects = reader.ReadByteArray();
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="SessionStateData"/> class.
-        /// </summary>
-        public SessionStateData()
-        {
-            _items = new KeyValueDirtyTrackedCollection();
-        }
-
-        /// <summary>
-        /// Gets the items.
-        /// </summary>
-        public KeyValueDirtyTrackedCollection Items
-        {
-            get { return _items; }
-        }
-
-        /// <summary>
-        /// Gets or sets the static objects.
-        /// </summary>
-        public byte[] StaticObjects { get; set; }
-
-        /// <summary>
-        /// Gets or sets the timeout.
-        /// </summary>
-        public int Timeout { get; set; }
-
-        /// <summary>
-        /// Gets or sets the lock node id. Null means not locked.
-        /// </summary>
-        public Guid? LockNodeId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the lock id.
-        /// </summary>
-        public long LockId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the lock time.
-        /// </summary>
-        public DateTime? LockTime { get; set; }
-
-        /// <summary>
-        /// Writes this object to the given writer.
-        /// </summary>
-        /// <param name="writer">Writer.</param>
-        public void WriteBinary(IBinaryWriter writer)
-        {
-            var raw = writer.GetRawWriter();
-
-            raw.WriteInt(Timeout);
-            raw.WriteGuid(LockNodeId);
-            raw.WriteLong(LockId);
-            raw.WriteTimestamp(LockTime);
-            Items.WriteBinary(writer);
-            raw.WriteByteArray(StaticObjects);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateLockResult.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateLockResult.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateLockResult.cs
deleted file mode 100644
index bab66f5..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/AspNet/SessionStateLockResult.cs
+++ /dev/null
@@ -1,93 +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.Impl.AspNet
-{
-    using System;
-    using System.Diagnostics;
-    using Apache.Ignite.Core.Binary;
-    using Apache.Ignite.Core.Impl.Binary;
-
-    /// <summary>
-    /// Result of the session state lock processor.
-    /// </summary>
-    public class SessionStateLockResult : IBinaryWriteAware
-    {
-        /** Success flag. */
-        private readonly bool _success;
-
-        /** Session state data. */
-        private readonly SessionStateData _data;
-
-        /** Lock time. */
-        private readonly DateTime? _lockTime;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="SessionStateLockResult"/> class.
-        /// </summary>
-        /// <param name="reader">The reader.</param>
-        public SessionStateLockResult(IBinaryRawReader reader)
-        {
-            _success = reader.ReadBoolean();
-            _data = reader.ReadObject<SessionStateData>();
-            _lockTime = reader.ReadTimestamp();
-
-            Debug.Assert(_success ^ (_data == null));
-            Debug.Assert(_success ^ (_lockTime != null));
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether lock succeeded.
-        /// </summary>
-        public bool Success
-        {
-            get { return _success; }
-        }
-
-        /// <summary>
-        /// Gets the data. Null when <see cref="Success"/> is <c>false</c>.
-        /// </summary>
-        public SessionStateData Data
-        {
-            get { return _data; }
-        }
-
-        /// <summary>
-        /// Gets the lock time. Null when <see cref="Success"/> is <c>true</c>.
-        /// </summary>
-        public DateTime? LockTime
-        {
-            get { return _lockTime; }
-        }
-
-        /// <summary>
-        /// Returns a <see cref="string" /> that represents this instance.
-        /// </summary>
-        public override string ToString()
-        {
-            return string.Format("{0} [Success={1}]", GetType().Name, _success);
-        }
-
-        /// <summary>
-        /// Writes this object to the given writer.
-        /// </summary>
-        public void WriteBinary(IBinaryWriter writer)
-        {
-            throw new NotSupportedException(GetType() + " is only written from native code.");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
index 2bf6dbb..035b356 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -23,12 +23,10 @@ namespace Apache.Ignite.Core.Impl.Binary
     using System.Linq;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache.Affinity;
-    using Apache.Ignite.Core.Impl.AspNet;
     using Apache.Ignite.Core.Impl.Binary.IO;
     using Apache.Ignite.Core.Impl.Binary.Metadata;
     using Apache.Ignite.Core.Impl.Cache;
     using Apache.Ignite.Core.Impl.Cache.Query.Continuous;
-    using Apache.Ignite.Core.Impl.Collections;
     using Apache.Ignite.Core.Impl.Common;
     using Apache.Ignite.Core.Impl.Compute;
     using Apache.Ignite.Core.Impl.Compute.Closure;
@@ -557,7 +555,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Adds a predefined system type.
         /// </summary>
         private void AddSystemType<T>(int typeId, Func<BinaryReader, T> ctor, string affKeyFldName = null, 
-            IBinarySerializerInternal serializer = null, string typeNameOverride = null)
+            IBinarySerializerInternal serializer = null)
             where T : IBinaryWriteAware
         {
             var type = typeof(T);
@@ -565,7 +563,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             serializer = serializer ?? new BinarySystemTypeSerializer<T>(ctor);
 
             if (typeId == 0)
-                typeId = BinaryUtils.TypeId(typeNameOverride ?? type.Name, null, null);
+                typeId = BinaryUtils.TypeId(type.Name, null, null);
 
             AddType(type, typeId, BinaryUtils.GetTypeName(type), false, false, null, null, serializer, affKeyFldName,
                 false);
@@ -596,8 +594,6 @@ namespace Apache.Ignite.Core.Impl.Binary
             AddSystemType(0, r => new AffinityKey(r), "affKey");
             AddSystemType(BinaryUtils.TypePlatformJavaObjectFactoryProxy, r => new PlatformJavaObjectFactoryProxy());
             AddSystemType(0, r => new ObjectInfoHolder(r));
-            AddSystemType(0, r => new SessionStateData(r), typeNameOverride: "PlatformDotnetSessionData");
-            AddSystemType(0, r => new SessionStateLockResult(r), typeNameOverride: "PlatformDotnetSessionLockResult");
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs
index 859e90d..da5ef2e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs
@@ -873,7 +873,8 @@ namespace Apache.Ignite.Core.Impl.Cache
         }
 
         /** <inheritDoc /> */
-        public T InvokeExtension<T>(int extensionId, int opCode, Action<IBinaryRawWriter> writeAction)
+        public T InvokeExtension<T>(int extensionId, int opCode, Action<IBinaryRawWriter> writeAction, 
+            Func<IBinaryRawReader, T> readFunc)
         {
             return DoOutInOpX((int) CacheOp.Extension, writer =>
                 {
@@ -881,7 +882,9 @@ namespace Apache.Ignite.Core.Impl.Cache
                     writer.WriteInt(opCode);
                     writeAction(writer);
                 },
-                (input, res) => res == True ? Marshaller.Unmarshal<T>(input) : default(T), ReadException);
+                (input, res) => res == True
+                    ? readFunc(Marshaller.StartUnmarshal(input))
+                    : default(T), ReadException);
         }
 
         /** <inheritdoc /> */

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs
index cd4922b..d552d9b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs
@@ -44,9 +44,11 @@ namespace Apache.Ignite.Core.Impl.Cache
         /// <param name="extensionId">The extension identifier.</param>
         /// <param name="opCode">The extension op code.</param>
         /// <param name="writeAction">The write action.</param>
+        /// <param name="readFunc">The read action.</param>
         /// <returns>
         /// Result of the processing.
         /// </returns>
-        T InvokeExtension<T>(int extensionId, int opCode, Action<IBinaryRawWriter> writeAction);
+        T InvokeExtension<T>(int extensionId, int opCode, Action<IBinaryRawWriter> writeAction, 
+            Func<IBinaryRawReader, T> readFunc);
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/491057ac/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Collections/KeyValueDirtyTrackedCollection.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Collections/KeyValueDirtyTrackedCollection.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Collections/KeyValueDirtyTrackedCollection.cs
deleted file mode 100644
index f8c6795..0000000
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Collections/KeyValueDirtyTrackedCollection.cs
+++ /dev/null
@@ -1,509 +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.Impl.Collections
-{
-    using System;
-    using System.Collections.Generic;
-    using System.Diagnostics;
-    using System.IO;
-    using System.Linq;
-    using Apache.Ignite.Core.Binary;
-    using Apache.Ignite.Core.Impl.Binary;
-    using Apache.Ignite.Core.Impl.Common;
-    using BinaryReader = Apache.Ignite.Core.Impl.Binary.BinaryReader;
-    using BinaryWriter = Apache.Ignite.Core.Impl.Binary.BinaryWriter;
-
-    /// <summary>
-    /// Binarizable key-value collection with dirty item tracking.
-    /// </summary>
-    public class KeyValueDirtyTrackedCollection : IBinaryWriteAware
-    {
-        /** */
-        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(BinaryReader 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, reader.Marshaller, 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(IBinaryWriter writer)
-        {
-            IgniteArgumentCheck.NotNull(writer, "writer");
-
-            if (_isDiff)
-                throw new InvalidOperationException(string.Format("Cannot serialize incomplete {0}.", GetType()));
-
-            var raw = writer.GetRawWriter();
-            var wr = (BinaryWriter) raw;
-
-            if (_isNew || _dirtyAll || !WriteChangesOnly || (_removedKeys == null && _list.All(x => x.IsDirty)))
-            {
-                // Write in full mode.
-                raw.WriteBoolean(false);
-                raw.WriteInt(_list.Count);
-
-                foreach (var entry in _list)
-                {
-                    raw.WriteString(entry.Key);
-
-                    // Write as byte array to enable partial deserialization.
-                    raw.WriteByteArray(entry.GetBytes(wr.Marshaller));
-                }
-            }
-            else
-            {
-                // Write in diff mode.
-                raw.WriteBoolean(true);
-
-                var stream = wr.Stream;
-
-                var countPos = stream.Position;
-                var count = 0;
-
-                raw.WriteInt(count);  // reserve count
-
-                // Write removed keys as [key + null].
-                if (_removedKeys != null)
-                {
-                    // Filter out existing keys.
-                    var removed = new HashSet<string>(_removedKeys);
-
-                    foreach (var entry in _list)
-                        removed.Remove(entry.Key);
-
-                    foreach (var removedKey in removed)
-                    {
-                        raw.WriteString(removedKey);
-                        raw.WriteByteArray(null);
-
-                        count++;
-                    }
-                }
-
-                // Write dirty items.
-                foreach (var entry in _list)
-                {
-                    if (!entry.IsDirty)
-                        continue;
-
-                    raw.WriteString(entry.Key);
-
-                    // Write as byte array to enable partial deserialization.
-                    raw.WriteByteArray(entry.GetBytes(wr.Marshaller));
-
-                    count++;
-                }
-
-                // Write item count.
-                var pos = stream.Position;
-
-                stream.Seek(countPos, SeekOrigin.Begin);
-                stream.WriteInt(count);
-                stream.Seek(pos, SeekOrigin.Begin);
-
-            }
-        }
-
-        /// <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, 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 Marshaller _marsh;
-
-            /** */
-            private object _value;
-
-            /** */
-            private bool _isDeserialized;
-
-            /// <summary>
-            /// Initializes a new instance of the <see cref="Entry"/> class.
-            /// </summary>
-            public Entry(string key, bool isInitial, Marshaller marsh, object value)
-            {
-                Debug.Assert(key != null);
-                Debug.Assert(!isInitial || marsh != null);
-
-                Key = key;
-                IsInitial = isInitial;
-                _isDeserialized = !isInitial;
-                _marsh = marsh;
-                _value = value;
-            }
-
-            /// <summary>
-            /// Gets or sets the value.
-            /// </summary>
-            public object Value
-            {
-                get
-                {
-                    if (!_isDeserialized)
-                    {
-                        _value = _marsh.Unmarshal<object>((byte[]) _value);
-                        _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;
-                entry._marsh = _marsh;
-            }
-
-            /// <summary>
-            /// Gets the bytes.
-            /// </summary>
-            public byte[] GetBytes(Marshaller marsh)
-            {
-                if (!_isDeserialized)
-                    return (byte[]) _value;
-
-                return marsh.Marshal(_value);
-            }
-        }
-    }
-}


Mime
View raw message