You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2020/01/22 07:18:49 UTC

[ignite] branch master updated: IGNITE-12555 .NET: Fix SerializableSerializer performance

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 274c443  IGNITE-12555 .NET: Fix SerializableSerializer performance
274c443 is described below

commit 274c4432e365bed105d53f8a284532432da24d96
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Wed Jan 22 10:18:35 2020 +0300

    IGNITE-12555 .NET: Fix SerializableSerializer performance
    
    Use cached binary metadata when possible: route all metadata through `Marshaller`, handle caching there.
    
    Before the fix, we had a `IBinaryProcessor.GetBinaryType` call for every deserialized object that implements `ISerializable`, which caused huge performance drop, especially for thin client where every call is a network operation. The most common `ISerializable` type is `DateTime`.
    
    Perf measurements:
    * `GetAllEmployeesBenchmark` +56%
    * `ThinClientGetAllEmployeesBenchmark` +2000%
    * `GetBenchmark` +30%
    * `ThinClientGetBenchmark` +20%
---
 .../processors/platform/PlatformContext.java       |   3 +-
 .../processors/platform/PlatformContextImpl.java   |  10 +-
 .../platform/binary/PlatformBinaryProcessor.java   |  15 +-
 .../Apache.Ignite.Benchmarks.DotNetCore.csproj     |  18 ++
 .../Apache.Ignite.Benchmarks.csproj                |   2 +
 .../Apache.Ignite.Benchmarks/BenchmarkRunner.cs    |  26 +-
 ...GetBenchmark.cs => GetAllEmployeesBenchmark.cs} |  38 +--
 .../Interop/GetBenchmark.cs                        |   8 +
 .../Apache.Ignite.Benchmarks/Model/Employee.cs     |  18 ++
 ...rk.cs => ThinClientGetAllEmployeesBenchmark.cs} |  39 +--
 .../ThinClient/ThinClientGetBenchmark.cs           |   8 +
 .../Apache.Ignite.Core.Tests.DotNetCore.csproj     |  26 +-
 .../Apache.Ignite.Core.Tests.csproj                |   6 +
 .../Serializable/DynamicFieldSetSerializable.cs    |  81 ++++++
 .../Binary/Serializable/DynamicFieldSetTest.cs     | 120 ++++++++
 .../Client/Cache/CacheTest.cs                      |  21 +-
 .../Client/Cache/CacheTestNoMeta.cs                |   6 -
 .../Client/Cache/DynamicFieldSetTest.cs            | 141 ++++++++++
 .../Client/Cache/PartitionAwarenessTest.cs         |  65 +----
 .../Client/Cache/SerializableObjectsTest.cs        | 136 ++++++++++
 .../Client/ClientServerCacheAdapter.cs             | 302 +++++++++++++++++++++
 .../Client/ClientServerCacheAdapterExtensions.cs   |  36 +++
 .../Client/ClientTestBase.cs                       |  80 +++++-
 .../Client/IgniteClientConfigurationTest.cs        |   2 +-
 .../platforms/dotnet/Apache.Ignite.Core/IIgnite.cs |   2 +-
 .../Impl/Binary/BinaryObjectSchemaSerializer.cs    |  61 +++--
 .../Impl/Binary/BinaryProcessor.cs                 |  21 +-
 .../Impl/Binary/BinaryProcessorClient.cs           |   6 -
 .../Apache.Ignite.Core/Impl/Binary/BinaryReader.cs |  10 +-
 .../Impl/Binary/IBinaryProcessor.cs                |   5 -
 .../Apache.Ignite.Core/Impl/Binary/Marshaller.cs   |  97 +++++--
 .../Impl/Binary/Metadata/BinaryType.cs             |   9 +-
 .../Impl/Binary/Metadata/BinaryTypeHolder.cs       |  69 +++--
 .../Impl/Binary/SerializableSerializer.cs          |  35 ++-
 .../platforms/dotnet/Apache.Ignite.DotNetCore.sln  |   6 +
 modules/platforms/dotnet/README.md                 |   6 +-
 36 files changed, 1298 insertions(+), 236 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
index da92c6c..6de8c51 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
@@ -132,8 +132,9 @@ public interface PlatformContext {
      *
      * @param writer Writer.
      * @param typeId Type ID.
+     * @param includeSchemas Whether to include binary object schemas into the result.
      */
-    public void writeMetadata(BinaryRawWriterEx writer, int typeId);
+    public void writeMetadata(BinaryRawWriterEx writer, int typeId, boolean includeSchemas);
 
     /**
      * Write all available metadata.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
index 0f6f252..55145aa 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
@@ -338,8 +338,8 @@ public class PlatformContextImpl implements PlatformContext {
     }
 
     /** {@inheritDoc} */
-    @Override public void writeMetadata(BinaryRawWriterEx writer, int typeId) {
-        writeMetadata0(writer, cacheObjProc.metadata(typeId));
+    @Override public void writeMetadata(BinaryRawWriterEx writer, int typeId, boolean includeSchemas) {
+        writeMetadata0(writer, cacheObjProc.metadata(typeId), includeSchemas);
     }
 
     /** {@inheritDoc} */
@@ -349,7 +349,7 @@ public class PlatformContextImpl implements PlatformContext {
         writer.writeInt(metas.size());
 
         for (BinaryType m : metas)
-            writeMetadata0(writer, m);
+            writeMetadata0(writer, m, false);
     }
 
     /** {@inheritDoc} */
@@ -363,7 +363,7 @@ public class PlatformContextImpl implements PlatformContext {
      * @param writer Writer.
      * @param meta Metadata.
      */
-    private void writeMetadata0(BinaryRawWriterEx writer, BinaryType meta) {
+    private void writeMetadata0(BinaryRawWriterEx writer, BinaryType meta, boolean includeSchemas) {
         if (meta == null)
             writer.writeBoolean(false);
         else {
@@ -371,7 +371,7 @@ public class PlatformContextImpl implements PlatformContext {
 
             BinaryMetadata meta0 = ((BinaryTypeImpl) meta).metadata();
 
-            PlatformUtils.writeBinaryMetadata(writer, meta0, false);
+            PlatformUtils.writeBinaryMetadata(writer, meta0, includeSchemas);
         }
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/binary/PlatformBinaryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/binary/PlatformBinaryProcessor.java
index 76df6a2..23d2cb5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/binary/PlatformBinaryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/binary/PlatformBinaryProcessor.java
@@ -54,6 +54,9 @@ public class PlatformBinaryProcessor extends PlatformAbstractTarget {
     /** */
     private static final int OP_REGISTER_ENUM = 7;
 
+    /** */
+    private static final int OP_GET_META_WITH_SCHEMAS = 8;
+
     /**
      * Constructor.
      *
@@ -99,7 +102,15 @@ public class PlatformBinaryProcessor extends PlatformAbstractTarget {
             case OP_GET_META: {
                 int typeId = reader.readInt();
 
-                platformCtx.writeMetadata(writer, typeId);
+                platformCtx.writeMetadata(writer, typeId, false);
+
+                break;
+            }
+
+            case OP_GET_META_WITH_SCHEMAS: {
+                int typeId = reader.readInt();
+
+                platformCtx.writeMetadata(writer, typeId, true);
 
                 break;
             }
@@ -142,7 +153,7 @@ public class PlatformBinaryProcessor extends PlatformAbstractTarget {
 
                 BinaryType binaryType = platformCtx.kernalContext().grid().binary().registerEnum(name, vals);
 
-                platformCtx.writeMetadata(writer, binaryType.typeId());
+                platformCtx.writeMetadata(writer, binaryType.typeId(), false);
 
                 break;
             }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.DotNetCore.csproj b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.DotNetCore.csproj
new file mode 100644
index 0000000..b4123b6
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.DotNetCore.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <AssemblyName>Apache.Ignite.Benchmarks</AssemblyName>
+    <RootNamespace>Apache.Ignite.Benchmarks</RootNamespace>
+    <AssemblyOriginatorKeyFile>Apache.Ignite.Benchmarks.snk</AssemblyOriginatorKeyFile>
+    <SignAssembly>true</SignAssembly>
+    <DelaySign>false</DelaySign>
+    <OutputType>Exe</OutputType>
+    <ServerGarbageCollection>true</ServerGarbageCollection>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Apache.Ignite.Core\Apache.Ignite.Core.DotNetCore.csproj" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.csproj b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.csproj
index 1c200e6..ea9b88e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Apache.Ignite.Benchmarks.csproj
@@ -51,6 +51,7 @@
     <Compile Include="BenchmarkUtils.cs" />
     <Compile Include="Interop\GetAllBinaryBenchmark.cs" />
     <Compile Include="Interop\GetAllBenchmark.cs" />
+    <Compile Include="Interop\GetAllEmployeesBenchmark.cs" />
     <Compile Include="Interop\PlatformBenchmarkBase.cs" />
     <Compile Include="Interop\ClosureBenchmark.cs" />
     <Compile Include="Interop\GetAsyncBenchmark.cs" />
@@ -73,6 +74,7 @@
     <Compile Include="Result\BenchmarkFileResultWriter.cs" />
     <Compile Include="Result\IBenchmarkResultWriter.cs" />
     <Compile Include="ThinClient\ThinClientGetAllBinaryBenchmark.cs" />
+    <Compile Include="ThinClient\ThinClientGetAllEmployeesBenchmark.cs" />
     <Compile Include="ThinClient\ThinClientGetAsyncBenchmark.cs" />
     <Compile Include="ThinClient\ThinClientGetAllBenchmark.cs" />
     <Compile Include="ThinClient\ThinClientGetBenchmark.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/BenchmarkRunner.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/BenchmarkRunner.cs
index fb2fbd2..6d34bb5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/BenchmarkRunner.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/BenchmarkRunner.cs
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Benchmarks
     using System;
     using System.Diagnostics;
     using System.IO;
+    using System.Reflection;
     using System.Text;
     using Apache.Ignite.Benchmarks.Interop;
 
@@ -40,7 +41,7 @@ namespace Apache.Ignite.Benchmarks
                 typeof(GetAllBinaryBenchmark).FullName,
                 //typeof(ThinClientGetAllBenchmark).FullName,
                 //typeof(ThinClientGetAllBinaryBenchmark).FullName,
-                "-ConfigPath", Directory.GetCurrentDirectory() + @"\..\..\Config\benchmark.xml",
+                "-ConfigPath", GetConfigPath(),
                 "-Threads", "1",
                 "-Warmup", "0",
                 "-Duration", "60",
@@ -52,7 +53,7 @@ namespace Apache.Ignite.Benchmarks
             Console.WriteLine("GC Server: " + gcSrv);
 
             if (!gcSrv)
-                Console.WriteLine("WARNING! GC server mode is disabled. This could yield in bad preformance.");
+                Console.WriteLine("WARNING! GC server mode is disabled. This could yield in bad performance.");
 
             Console.WriteLine("DotNet benchmark process started: " + Process.GetCurrentProcess().Id);
 
@@ -94,5 +95,26 @@ namespace Apache.Ignite.Benchmarks
             Console.ReadLine();
 #endif
         }
+
+        /// <summary>
+        /// Gets the config path.
+        /// </summary>
+        private static string GetConfigPath()
+        {
+            var dir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
+
+            while (dir != null)
+            {
+                var configPath = Path.Combine(dir.FullName, "Config", "benchmark.xml");
+                if (File.Exists(configPath))
+                {
+                    return configPath;
+                }
+                
+                dir = dir.Parent;
+            }
+
+            throw new InvalidOperationException("Could not locate benchmark config.");
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetAllEmployeesBenchmark.cs
similarity index 57%
copy from modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs
copy to modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetAllEmployeesBenchmark.cs
index 5d69669..3f4d6a9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetAllEmployeesBenchmark.cs
@@ -18,45 +18,29 @@
 namespace Apache.Ignite.Benchmarks.Interop
 {
     using System.Collections.Generic;
-    using Apache.Ignite.Benchmarks.Model;
-    using Apache.Ignite.Core.Cache;
+    using System.Linq;
 
     /// <summary>
-    /// Cache Get benchmark.
+    /// Cache GetAll benchmark.
     /// </summary>
-    internal class GetBenchmark : PlatformBenchmarkBase
+    internal class GetAllEmployeesBenchmark : GetBenchmark
     {
-        /** Cache name. */
-        private const string CacheName = "cache";
-
-        /** Native cache wrapper. */
-        private ICache<int, Employee> _cache;
-
-        /** <inheritDoc /> */
-        protected override void OnStarted()
-        {
-            base.OnStarted();
-
-            _cache = Node.GetCache<int, Employee>(CacheName);
-
-            for (int i = 0; i < Emps.Length; i++)
-                _cache.Put(i, Emps[i]);
-        }
-
         /** <inheritDoc /> */
         protected override void GetDescriptors(ICollection<BenchmarkOperationDescriptor> descs)
         {
-            descs.Add(BenchmarkOperationDescriptor.Create("Get", Get, 1));
+            descs.Add(BenchmarkOperationDescriptor.Create("GetAllEmployees", GetAll, 1));
         }
-        
+
         /// <summary>
-        /// Cache get.
+        /// Cache GetAll.
         /// </summary>
-        private void Get(BenchmarkState state)
+        private void GetAll(BenchmarkState state)
         {
-            var idx = BenchmarkUtils.GetRandomInt(Dataset);
+            var batchSize = Dataset / 10;
+            var idx = BenchmarkUtils.GetRandomInt(Dataset - batchSize);
+            var keys = Enumerable.Range(idx, batchSize);
 
-            _cache.Get(idx);
+            Cache.GetAll(keys);
         }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs
index 5d69669..cb52808 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Interop/GetBenchmark.cs
@@ -32,6 +32,14 @@ namespace Apache.Ignite.Benchmarks.Interop
         /** Native cache wrapper. */
         private ICache<int, Employee> _cache;
 
+        /// <summary>
+        /// Gets the cache.
+        /// </summary>
+        protected ICache<int, Employee> Cache
+        {
+            get { return _cache; }
+        }
+
         /** <inheritDoc /> */
         protected override void OnStarted()
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Model/Employee.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Model/Employee.cs
index 7f082f3..95de9bb 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Model/Employee.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Model/Employee.cs
@@ -17,6 +17,7 @@
 
 namespace Apache.Ignite.Benchmarks.Model
 {
+    using System;
     using Apache.Ignite.Core.Binary;
 
     /// <summary>
@@ -73,6 +74,16 @@ namespace Apache.Ignite.Benchmarks.Model
         /// Points.
         /// </summary>
         public int Points { get; set; }
+        
+        /// <summary>
+        /// Birthday.
+        /// </summary>
+        public DateTime Birthday { get; set; }
+        
+        /// <summary>
+        /// Timespan.
+        /// </summary>
+        public DateTime Timestamp { get; set; }
 
         /// <summary>
         /// Constructor.
@@ -101,6 +112,9 @@ namespace Apache.Ignite.Benchmarks.Model
             Payload = new byte[payloadSize];
 
             Points = 100;
+
+            Birthday = new DateTime(2005, 5, 5, 1, 1, 1, DateTimeKind.Local).AddHours(id);
+            Timestamp = new DateTime(2005, 5, 5, 1, 1, 1, DateTimeKind.Utc).AddMinutes(id);
         }
 
         /** <inheritDoc /> */
@@ -116,6 +130,8 @@ namespace Apache.Ignite.Benchmarks.Model
             writer.WriteByteArray("payload", Payload);
             writer.WriteString("name", Name);
             writer.WriteObject("address", Address);
+            writer.WriteObject("birthday", Birthday);
+            writer.WriteTimestamp("timestamp", Timestamp);
         }
 
         /** <inheritDoc /> */
@@ -131,6 +147,8 @@ namespace Apache.Ignite.Benchmarks.Model
             Payload = reader.ReadByteArray("payload");
             Name = reader.ReadString("name");
             Address = reader.ReadObject<Address>("address");
+            Birthday = reader.ReadObject<DateTime>("birthday");
+            Timestamp = reader.ReadTimestamp("timestamp").GetValueOrDefault();
         }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetAllEmployeesBenchmark.cs
similarity index 55%
copy from modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs
copy to modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetAllEmployeesBenchmark.cs
index 4af5f1f..006d631 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetAllEmployeesBenchmark.cs
@@ -18,46 +18,29 @@
 namespace Apache.Ignite.Benchmarks.ThinClient
 {
     using System.Collections.Generic;
-    using Apache.Ignite.Benchmarks.Interop;
-    using Apache.Ignite.Benchmarks.Model;
-    using Apache.Ignite.Core.Client.Cache;
+    using System.Linq;
 
     /// <summary>
-    /// Cache get benchmark.
+    /// Cache GetAll benchmark.
     /// </summary>
-    internal class ThinClientGetBenchmark : PlatformBenchmarkBase
+    internal class ThinClientGetAllEmployeesBenchmark : ThinClientGetBenchmark
     {
-        /** Cache name. */
-        private const string CacheName = "cache";
-
-        /** Native cache wrapper. */
-        private ICacheClient<int, Employee> _cache;
-
-        /** <inheritDoc /> */
-        protected override void OnStarted()
-        {
-            base.OnStarted();
-
-            _cache = GetClient().GetCache<int, Employee>(CacheName);
-
-            for (int i = 0; i < Emps.Length; i++)
-                _cache.Put(i, Emps[i]);
-        }
-
         /** <inheritDoc /> */
         protected override void GetDescriptors(ICollection<BenchmarkOperationDescriptor> descs)
         {
-            descs.Add(BenchmarkOperationDescriptor.Create("ThinClientGet", Get, 1));
+            descs.Add(BenchmarkOperationDescriptor.Create("GetAllEmployees", GetAll, 1));
         }
 
         /// <summary>
-        /// Cache get.
+        /// Cache GetAll.
         /// </summary>
-        private void Get(BenchmarkState state)
+        private void GetAll(BenchmarkState state)
         {
-            var idx = BenchmarkUtils.GetRandomInt(Dataset);
+            var batchSize = Dataset / 10;
+            var idx = BenchmarkUtils.GetRandomInt(Dataset - batchSize);
+            var keys = Enumerable.Range(idx, batchSize);
 
-            _cache.Get(idx);
+            Cache.GetAll(keys);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs
index 4af5f1f..3c2329e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/ThinClient/ThinClientGetBenchmark.cs
@@ -33,6 +33,14 @@ namespace Apache.Ignite.Benchmarks.ThinClient
         /** Native cache wrapper. */
         private ICacheClient<int, Employee> _cache;
 
+        /// <summary>
+        /// Gets the cache.
+        /// </summary>
+        public ICacheClient<int, Employee> Cache
+        {
+            get { return _cache; }
+        }
+
         /** <inheritDoc /> */
         protected override void OnStarted()
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
index d9bcd17..ca23c78 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj
@@ -19,7 +19,7 @@
     <DelaySign>false</DelaySign>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+  <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
@@ -59,7 +59,19 @@
     <Compile Include="..\Apache.Ignite.Core.Tests\Binary\JavaBinaryInteropTest.cs" Link="Binary\JavaBinaryInteropTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\AdvancedSerializationTest.cs" Link="Binary\Serializable\AdvancedSerializationTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\CallbacksTest.cs" Link="Binary\Serializable\CallbacksTest.cs" />
+    <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\DynamicFieldSetSerializable.cs">
+      <Link>Binary\Serializable\DynamicFieldSetSerializable.cs</Link>
+    </Compile>
+    <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\DynamicFieldSetTest.cs">
+      <Link>Binary\Serializable\DynamicFieldSetTest.cs</Link>
+    </Compile>
     <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\GenericCollectionsTest.cs" Link="Binary\Serializable\GenericCollectionsTest.cs" />
+    <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\ObjectReferenceTests.cs">
+      <Link>Binary\Serializable\ObjectReferenceTests.cs</Link>
+    </Compile>
+    <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\PrimitivesTest.cs">
+      <Link>Binary\Serializable\PrimitivesTest.cs</Link>
+    </Compile>
     <Compile Include="..\Apache.Ignite.Core.Tests\Binary\Serializable\SqlDmlTest.cs" Link="Binary\Serializable\SqlDmlTest.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Cache\AddArgCacheEntryProcessor.cs" Link="Cache\AddArgCacheEntryProcessor.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Cache\BinarizableAddArgCacheEntryProcessor.cs" Link="Cache\BinarizableAddArgCacheEntryProcessor.cs" />
@@ -96,6 +108,12 @@
     <Compile Include="..\Apache.Ignite.Core.Tests\Cache\Query\Linq\CacheLinqTestSimpleName.cs" Link="Cache\Query\Linq\CacheLinqTestSimpleName.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Cache\Query\Linq\CacheLinqTestSqlEscapeAll.cs" Link="Cache\Query\Linq\CacheLinqTestSqlEscapeAll.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Cache\TestReferenceObject.cs" Link="Cache\TestReferenceObject.cs" />
+    <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cache\DynamicFieldSetTest.cs">
+      <Link>ThinClient\Cache\DynamicFieldSetTest.cs</Link>
+    </Compile>
+    <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cache\SerializableObjectsTest.cs">
+      <Link>ThinClient\Cache\SerializableObjectsTest.cs</Link>
+    </Compile>
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cache\PartitionAwarenessTest.cs">
       <Link>ThinClient\Cache\PartitionAwarenessTest.cs</Link>
     </Compile>
@@ -137,6 +155,12 @@
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientProtocolVersionTest.cs">
       <Link>ThinClient\ClientProtocolVersionTest.cs</Link>
     </Compile>
+    <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientServerCacheAdapter.cs">
+      <Link>ThinClient\ClientServerCacheAdapter.cs</Link>
+    </Compile>
+    <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientServerCacheAdapterExtensions.cs">
+      <Link>ThinClient\ClientServerCacheAdapterExtensions.cs</Link>
+    </Compile>
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\ClientTestBase.cs" Link="ThinClient\ClientTestBase.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cluster\ClientClusterGroupTests.cs" Link="ThinClient\Cluster\ClientClusterGroupTests.cs" />
     <Compile Include="..\Apache.Ignite.Core.Tests\Client\Cluster\ClientClusterTests.cs" Link="ThinClient\Cluster\ClientClusterTests.cs" />
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 49d94f7..a824a18 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
@@ -107,7 +107,9 @@
     <Compile Include="Binary\BinaryReaderWriterTest.cs" />
     <Compile Include="Binary\BinarySelfTestSimpleName.cs" />
     <Compile Include="Binary\EnumsTestOnline.cs" />
+    <Compile Include="Binary\Serializable\DynamicFieldSetSerializable.cs" />
     <Compile Include="Binary\Serializable\GenericCollectionsTest.cs" />
+    <Compile Include="Binary\Serializable\DynamicFieldSetTest.cs" />
     <Compile Include="Cache\Affinity\AffinityAttributeTest.cs" />
     <Compile Include="Cache\CacheCreateTest.cs" />
     <Compile Include="Cache\CacheQueryMetricsTest.cs" />
@@ -132,6 +134,8 @@
     <Compile Include="Cache\Query\Linq\CacheLinqTest.Contains.cs" />
     <Compile Include="Cache\Store\CacheStoreSessionTestCodeConfig.cs" />
     <Compile Include="Cache\Store\CacheStoreSessionTestSharedFactory.cs" />
+    <Compile Include="Client\Cache\DynamicFieldSetTest.cs" />
+    <Compile Include="Client\Cache\SerializableObjectsTest.cs" />
     <Compile Include="Client\Cache\PartitionAwarenessTest.cs" />
     <Compile Include="Client\Cache\BinaryBuilderTest.cs" />
     <Compile Include="Client\Cache\CacheClientAsyncWrapper.cs" />
@@ -155,6 +159,8 @@
     <Compile Include="Client\ClientOpExtensionsTest.cs" />
     <Compile Include="Client\ClientProtocolVersionTest.cs" />
     <Compile Include="Client\ClientReconnectCompatibilityTest.cs" />
+    <Compile Include="Client\ClientServerCacheAdapter.cs" />
+    <Compile Include="Client\ClientServerCacheAdapterExtensions.cs" />
     <Compile Include="Client\ClientServerCompatibilityTest.cs" />
     <Compile Include="Client\ClientTestBase.cs" />
     <Compile Include="Client\Cluster\ClientClusterGroupTests.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetSerializable.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetSerializable.cs
new file mode 100644
index 0000000..6d127d2
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetSerializable.cs
@@ -0,0 +1,81 @@
+/*
+ * 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.Binary.Serializable
+{
+    using System.Runtime.Serialization;
+    using Apache.Ignite.Core.Tests.Client.Cache;
+
+    /// <summary>
+    /// Serializable class with dynamic field set: some fields are serialized based on a condition.
+    /// </summary>
+    public class DynamicFieldSetSerializable : ISerializable
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EmptyObject"/> class.
+        /// </summary>
+        public DynamicFieldSetSerializable()
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EmptyObject"/> class.
+        /// </summary>
+        // ReSharper disable once UnusedMember.Global
+        // ReSharper disable once UnusedParameter.Local
+        public DynamicFieldSetSerializable(SerializationInfo info, StreamingContext context)
+        {
+            WriteFoo = info.GetBoolean("WriteFoo");
+            if (WriteFoo)
+            {
+                Foo = info.GetInt32("Foo");
+            }
+            
+            WriteBar = info.GetBoolean("WriteBar");
+            if (WriteBar)
+            {
+                Bar = info.GetString("Bar");
+            }
+        }
+        
+        /** <inheritdoc /> */
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            info.AddValue("WriteFoo", WriteFoo);
+            info.AddValue("WriteBar", WriteBar);
+
+            if (WriteFoo)
+            {
+                info.AddValue("Foo", Foo);
+            }
+            
+            if (WriteBar)
+            {
+                info.AddValue("Bar", Bar);
+            }
+        }
+
+        public int Foo { get; set; }
+        
+        public string Bar { get; set; }
+        
+        public bool WriteFoo { get; set; }
+        
+        public bool WriteBar { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetTest.cs
new file mode 100644
index 0000000..9a6d156
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/DynamicFieldSetTest.cs
@@ -0,0 +1,120 @@
+/*
+ * 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.Binary.Serializable
+{
+    using System.Collections.Generic;
+    using System.Linq;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests <see cref="DynamicFieldSetSerializable"/> serialization.
+    /// </summary>
+    public class DynamicFieldSetTest
+    {
+        /** */
+        private IIgnite _node1;
+        
+        /** */
+        private IIgnite _node2;
+        
+        /// <summary>
+        /// Sets up the test.
+        /// </summary>
+        [SetUp]
+        public void SetUp()
+        {
+            _node1 = Ignition.Start(TestUtils.GetTestConfiguration());
+            
+            _node2 = Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                AutoGenerateIgniteInstanceName = true
+            });
+        }
+        
+        /// <summary>
+        /// Tears down the test.
+        /// </summary>
+        [TearDown]
+        public void TearDown()
+        {
+            Ignition.StopAll(true);
+        }
+        
+        /// <summary>
+        /// Tests that dynamically added and removed fields are deserialized correctly.
+        /// This verifies proper metadata and schema handling.
+        /// </summary>
+        [Test]
+        public void TestAddRemoveFieldsDynamically()
+        {
+            var cache1 = _node1.CreateCache<int, DynamicFieldSetSerializable>("c");
+            var cache2 = _node2.GetCache<int, DynamicFieldSetSerializable>("c");
+            
+            // Put/get without optional fields.
+            var noFields = new DynamicFieldSetSerializable();
+            cache1[1] = noFields;
+            
+            AssertExtensions.ReflectionEqual(noFields, cache1[1]);
+            AssertExtensions.ReflectionEqual(noFields, cache2[1]);
+
+            Assert.AreEqual(new[] {"WriteBar", "WriteFoo"}, GetFields(0));
+            Assert.AreEqual(new[] {"WriteBar", "WriteFoo"}, GetFields(1));
+            
+            // Put/get with one optional field.
+            var oneField = new DynamicFieldSetSerializable
+            {
+                Bar = "abc",
+                WriteBar = true
+            };
+            cache1[2] = oneField;
+            
+            AssertExtensions.ReflectionEqual(oneField, cache1[2]);
+            AssertExtensions.ReflectionEqual(oneField, cache2[2]);
+            Assert.AreEqual(new[] {"Bar", "WriteBar", "WriteFoo"}, GetFields(0));
+            Assert.AreEqual(new[] {"Bar", "WriteBar", "WriteFoo"}, GetFields(1));
+            
+            // Put/get with another optional field.
+            var oneField2 = new DynamicFieldSetSerializable
+            {
+                Foo = 25,
+                WriteFoo = true
+            };
+            cache1[3] = oneField2;
+            
+            AssertExtensions.ReflectionEqual(oneField2, cache1[3]);
+            AssertExtensions.ReflectionEqual(oneField2, cache2[3]);
+            
+            Assert.AreEqual(new[] {"Bar", "Foo", "WriteBar", "WriteFoo"}, GetFields(0));
+            Assert.AreEqual(new[] {"Bar", "Foo", "WriteBar", "WriteFoo"}, GetFields(1));
+        }
+
+        /// <summary>
+        /// Gets the fields.
+        /// </summary>
+        private IEnumerable<string> GetFields(int nodeIndex)
+        {
+            var node = nodeIndex == 0 ? _node1 : _node2;
+            
+            return node
+                .GetBinary()
+                .GetBinaryType(typeof(DynamicFieldSetSerializable))
+                .Fields
+                .OrderBy(f => f);
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
index f61850c..eb8c985 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
@@ -846,17 +846,22 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
         [Test]
         public void TestAsyncCompletionOrder()
         {
-            var cache = GetClientCache<int>();
-            var cache2 = Client.GetOrCreateCache<int, int>("TestAsyncCompletionOrder");
+            var config = GetClientConfiguration();
+            config.SocketTimeout = TimeSpan.FromMinutes(2);
+            using (var client = Ignition.StartClient(config))
+            {
+                var cache = client.GetCache<int, int>(CacheName);
+                var cache2 = Client.GetOrCreateCache<int, int>("TestAsyncCompletionOrder");
 
-            cache.PutAll(Enumerable.Range(1, 500000).Select(x => new KeyValuePair<int, int>(x, x)));
-            var t1 = cache.RemoveAllAsync();
-            var t2 = cache2.PutAsync(1, 1);
+                cache.PutAll(Enumerable.Range(1, 50000).Select(x => new KeyValuePair<int, int>(x, x)));
+                var t1 = cache.RemoveAllAsync();
+                var t2 = cache2.PutAsync(1, 1);
 
-            t2.Wait();
-            Assert.IsFalse(t1.IsCompleted);
+                t2.Wait();
+                Assert.IsFalse(t1.IsCompleted);
 
-            t1.Wait();
+                t1.Wait();
+            }
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
index 0c72010..98c69f6 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTestNoMeta.cs
@@ -98,12 +98,6 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             }
 
             /** <inheritdoc /> */
-            public int[] GetSchema(int typeId, int schemaId)
-            {
-                return null;
-            }
-
-            /** <inheritdoc /> */
             public void PutBinaryTypes(ICollection<BinaryType> types)
             {
                 // No-op.
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/DynamicFieldSetTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/DynamicFieldSetTest.cs
new file mode 100644
index 0000000..a781434
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/DynamicFieldSetTest.cs
@@ -0,0 +1,141 @@
+/*
+ * 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.Client.Cache
+{
+    using System.Collections.Generic;
+    using System.Linq;
+    using Apache.Ignite.Core.Tests.Binary.Serializable;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests <see cref="DynamicFieldSetSerializable"/> serialization in thin client.
+    /// </summary>
+    [TestFixture(true)]
+    [TestFixture(false)]
+    public class DynamicFieldSetTest : ClientTestBase
+    {
+        /** */
+        private readonly bool _clientToServer;
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="DynamicFieldSetTest"/>. 
+        /// </summary>
+        public DynamicFieldSetTest(bool clientToServer)
+        {
+            _clientToServer = clientToServer;
+        }
+
+        /// <summary>
+        /// Tests that dynamically added and removed fields are deserialized correctly.
+        /// This verifies proper metadata and schema handling.
+        /// </summary>
+        [Test]
+        public void TestAddRemoveFieldsDynamically()
+        {
+            var cache1 = Ignition.GetIgnite().GetOrCreateCache<int, DynamicFieldSetSerializable>("c").AsCacheClient();
+            var cache2 = Client.GetCache<int, DynamicFieldSetSerializable>("c");
+
+            if (_clientToServer)
+            {
+                // Swap caches to verify that metadata propagates both ways.
+                var tmp = cache1;
+                cache1 = cache2;
+                cache2 = tmp;
+            }
+
+            // Put/get without optional fields.
+            var noFields = new DynamicFieldSetSerializable();
+            cache1[1] = noFields;
+
+            AssertExtensions.ReflectionEqual(noFields, cache1[1]);
+            AssertExtensions.ReflectionEqual(noFields, cache2[1]);
+
+            Assert.AreEqual(new[] {"WriteBar", "WriteFoo"}, GetFieldsServer());
+            Assert.AreEqual(new[] {"WriteBar", "WriteFoo"}, GetFieldsClient());
+
+            // Put/get with one optional field.
+            var oneField = new DynamicFieldSetSerializable
+            {
+                Bar = "abc",
+                WriteBar = true
+            };
+            cache1[2] = oneField;
+
+            AssertExtensions.ReflectionEqual(oneField, cache1[2]);
+            AssertExtensions.ReflectionEqual(oneField, cache2[2]);
+            Assert.AreEqual(new[] {"Bar", "WriteBar", "WriteFoo"}, GetFieldsServer());
+            Assert.AreEqual(new[] {"Bar", "WriteBar", "WriteFoo"}, GetFieldsClient());
+
+            // Put/get with another optional field.
+            var oneField2 = new DynamicFieldSetSerializable
+            {
+                Foo = 25,
+                WriteFoo = true
+            };
+            cache1[3] = oneField2;
+
+            AssertExtensions.ReflectionEqual(oneField2, cache1[3]);
+            AssertExtensions.ReflectionEqual(oneField2, cache2[3]);
+
+            Assert.AreEqual(new[] {"Bar", "Foo", "WriteBar", "WriteFoo"}, GetFieldsServer());
+            Assert.AreEqual(new[] {"Bar", "Foo", "WriteBar", "WriteFoo"}, GetFieldsClient());
+            
+            // Put/get with both optional fields.
+            var twoField = new DynamicFieldSetSerializable
+            {
+                Bar = "x",
+                Foo = 42,
+                WriteBar = true,
+                WriteFoo = true
+            };
+            cache1[4] = twoField;
+
+            AssertExtensions.ReflectionEqual(twoField, cache1[4]);
+            AssertExtensions.ReflectionEqual(twoField, cache2[4]);
+            
+            // Re-check initial object without optional fields.
+            AssertExtensions.ReflectionEqual(noFields, cache1[1]);
+            AssertExtensions.ReflectionEqual(noFields, cache2[1]);
+        }
+
+        /// <summary>
+        /// Gets the fields.
+        /// </summary>
+        private IEnumerable<string> GetFieldsServer()
+        {
+            return Ignition
+                .GetIgnite()
+                .GetBinary()
+                .GetBinaryType(typeof(DynamicFieldSetSerializable))
+                .Fields
+                .OrderBy(f => f);
+        }
+
+        /// <summary>
+        /// Gets the fields.
+        /// </summary>
+        private IEnumerable<string> GetFieldsClient()
+        {
+            return Client
+                .GetBinary()
+                .GetBinaryType(typeof(DynamicFieldSetSerializable))
+                .Fields
+                .OrderBy(f => f);
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
index f48afe3..6ffb473 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
@@ -23,7 +23,6 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
     using System.Globalization;
     using System.Linq;
     using System.Net;
-    using System.Text.RegularExpressions;
     using System.Threading.Tasks;
     using Apache.Ignite.Core.Cache.Configuration;
     using Apache.Ignite.Core.Client;
@@ -38,18 +37,16 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
     public class PartitionAwarenessTest : ClientTestBase
     {
         /** */
-        private const string NodeIndexAttr = "test-node-idx";
-
-        /** */
-        private readonly List<ListLogger> _loggers = new List<ListLogger>();
-
+        private const int ServerCount = 3;
+        
         /** */
         private ICacheClient<int, int> _cache;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="PartitionAwarenessTest"/> class.
         /// </summary>
-        public PartitionAwarenessTest() : base(3)
+        public PartitionAwarenessTest() 
+            : base(ServerCount)
         {
             // No-op.
         }
@@ -262,7 +259,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             _cache.Get(1);
             _cache.Get(1);
 
-            var requests = _loggers.SelectMany(GetCacheRequestNames).ToArray();
+            var requests = GetAllServerRequestNames(RequestNamePrefixCache);
 
             var expectedRequests = new[]
             {
@@ -291,11 +288,11 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             cache.Get(2);
             cache.Get(3);
 
-            var reqs = _loggers
-                .Select(l => new {Logger = l, Requests = GetCacheRequestNames(l).ToArray()})
-                .Where(r => r.Requests.Length > 0)
+            var reqs = GetLoggers()
+                .Select(l => GetServerRequestNames(l, RequestNamePrefixCache).ToArray())
+                .Where(x => x.Length > 0)
                 .ToArray();
-
+            
             // All requests should go to a single (default) node, because partition awareness is not applicable.
             Assert.AreEqual(1, reqs.Length);
 
@@ -308,7 +305,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
                 "Get"
             };
 
-            Assert.AreEqual(expectedRequests, reqs[0].Requests);
+            Assert.AreEqual(expectedRequests, reqs[0]);
         }
 
         [Test]
@@ -432,20 +429,6 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             Assert.AreEqual(gridIdx, GetPrimaryNodeIdx(key));
         }
 
-        protected override IgniteConfiguration GetIgniteConfiguration()
-        {
-            var cfg = base.GetIgniteConfiguration();
-
-            var index = _loggers.Count;
-            cfg.UserAttributes = new Dictionary<string, object> {{NodeIndexAttr, index}};
-
-            var logger = new ListLogger();
-            cfg.Logger = logger;
-            _loggers.Add(logger);
-
-            return cfg;
-        }
-
         protected override IgniteClientConfiguration GetClientConfiguration()
         {
             var cfg = base.GetClientConfiguration();
@@ -463,9 +446,9 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
 
             try
             {
-                for (var i = 0; i < _loggers.Count; i++)
+                for (var i = 0; i < ServerCount; i++)
                 {
-                    var requests = GetCacheRequestNames(_loggers[i]);
+                    var requests = GetServerRequestNames(i, RequestNamePrefixCache);
 
                     if (requests.Contains(message))
                     {
@@ -481,27 +464,6 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             }
         }
 
-        private static IEnumerable<string> GetCacheRequestNames(ListLogger logger)
-        {
-            var messageRegex = new Regex(
-                @"Client request received \[reqId=\d+, addr=/127.0.0.1:\d+, " +
-                @"req=org.apache.ignite.internal.processors.platform.client.cache.ClientCache(\w+)Request@");
-
-            return logger.Entries
-                .Select(m => messageRegex.Match(m.Message))
-                .Where(m => m.Success)
-                .Select(m => m.Groups[1].Value);
-        }
-
-
-        private void ClearLoggers()
-        {
-            foreach (var logger in _loggers)
-            {
-                logger.Clear();
-            }
-        }
-
         private void TestOperation(Action action, int expectedGridIdx, string message = null)
         {
             InitTestData();
@@ -528,8 +490,7 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
             var idx = 0;
 
             // GetAll is not ordered - sort the same way as _loggers.
-            var ignites = Ignition.GetAll()
-                .OrderBy(n => n.GetCluster().GetLocalNode().GetAttribute<int>(NodeIndexAttr));
+            var ignites = Ignition.GetAll().OrderBy(i => i.Name);
 
             foreach (var ignite in ignites)
             {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SerializableObjectsTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SerializableObjectsTest.cs
new file mode 100644
index 0000000..c3782a4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/SerializableObjectsTest.cs
@@ -0,0 +1,136 @@
+/*
+ * 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.Client.Cache
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Runtime.Serialization;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Client.Cache;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests <see cref="ISerializable"/> object handling in Thin Client.
+    /// </summary>
+    public class SerializableObjectsTest : ClientTestBase
+    {
+        /** */
+        private const int EntryCount = 50;
+
+        /** */
+        private static readonly int[] Keys = Enumerable.Range(0, EntryCount).ToArray();
+        
+        /// <summary>
+        /// Tests DateTime metadata caching.
+        /// </summary>
+        [Test]
+        public void TestDateTimeMetaCachingOnPut([Values(true, false)] bool scanQuery)
+        {
+            var requestName = scanQuery ? "ClientCacheScanQuery" : "ClientCacheGetAll";
+            var cache = GetPopulatedCache();
+            
+            var res = scanQuery
+                ? cache.Query(new ScanQuery<int, DateTimeTest>()).GetAll()
+                : cache.GetAll(Keys);
+
+            var requests = GetAllServerRequestNames().ToArray();
+
+            // Verify that only one request is sent to the server:
+            // metadata is already cached and should not be requested.
+            Assert.AreEqual(new[] {requestName}, requests);
+
+            // Verify results.
+            Assert.AreEqual(EntryCount, res.Count);
+            Assert.AreEqual(DateTimeTest.DefaultDateTime, res.Min(x => x.Value.Date));
+        }
+
+        /// <summary>
+        /// Tests DateTime metadata caching.
+        /// </summary>
+        [Test]
+        public void TestDateTimeMetaCachingOnGet([Values(true, false)] bool scanQuery)
+        {
+            // Retrieve data from a different client which does not yet have cached meta.
+            var requestName = scanQuery ? "ClientCacheScanQuery" : "ClientCacheGetAll";
+            var cache = GetClient().GetCache<int, DateTimeTest>(GetPopulatedCache().Name);
+            
+            var res = scanQuery
+                ? cache.Query(new ScanQuery<int, DateTimeTest>()).GetAll()
+                : cache.GetAll(Keys);
+            
+            var requests = GetAllServerRequestNames().ToArray();
+
+            // Verify that only one BinaryTypeGet request per type is sent to the server.
+            var expectedRequests = new[]
+            {
+                requestName, 
+                "ClientBinaryTypeNameGet",
+                "ClientBinaryTypeGet",
+                "ClientBinaryTypeNameGet",
+                "ClientBinaryTypeGet"
+            };
+            Assert.AreEqual(expectedRequests, requests);
+            
+            // Verify results.
+            Assert.AreEqual(EntryCount, res.Count);
+            Assert.AreEqual(DateTimeTest.DefaultDateTime, res.Min(x => x.Value.Date));
+        }
+        
+        /// <summary>
+        /// Gets the populated cache.
+        /// </summary>
+        private ICacheClient<int, DateTimeTest> GetPopulatedCache()
+        {
+            var cacheName = TestContext.CurrentContext.Test.Name;
+            var cache = Client.GetOrCreateCache<int, DateTimeTest>(cacheName);
+            cache.PutAll(GetData().Select(x => new KeyValuePair<int, DateTimeTest>(x.Id, x)));
+
+            ClearLoggers();
+            return cache;
+        }
+
+        /// <summary>
+        /// Gets the data.
+        /// </summary>
+        private static IEnumerable<DateTimeTest> GetData()
+        {
+            return Keys
+                .Select(x => new DateTimeTest
+                {
+                    Id = x,
+                    Date = DateTimeTest.DefaultDateTime.AddHours(x),
+                });
+        }
+
+        /// <summary>
+        /// Test class with DateTime field.
+        /// </summary>
+        private class DateTimeTest
+        {
+            /** */
+            public static readonly DateTime DefaultDateTime = new DateTime(2002, 2, 2);
+            
+            /** */
+            public int Id { get; set; }
+            
+            /** */
+            public DateTime Date { get; set; }
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapter.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapter.cs
new file mode 100644
index 0000000..7e101dd
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapter.cs
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ */
+
+#pragma warning disable 618
+namespace Apache.Ignite.Core.Tests.Client
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Threading.Tasks;
+    using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Cache.Expiry;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Client.Cache;
+
+    /// <summary>
+    /// Adapts <see cref="ICache{TK,TV}"/> to <see cref="ICacheClient{TK,TV}"/>
+    /// </summary>
+    public class ClientServerCacheAdapter<TK, TV> : ICacheClient<TK, TV>
+    {
+        private readonly ICache<TK, TV> _cache;
+
+        public ClientServerCacheAdapter(ICache<TK, TV> cache)
+        {
+            _cache = cache;
+        }
+
+        public string Name
+        {
+            get { return _cache.Name; }
+        }
+
+        public void Put(TK key, TV val)
+        {
+            _cache.Put(key, val);
+        }
+
+        public Task PutAsync(TK key, TV val)
+        {
+            return _cache.PutAsync(key, val);
+        }
+
+        public TV Get(TK key)
+        {
+            return _cache.Get(key);
+        }
+
+        public Task<TV> GetAsync(TK key)
+        {
+            return _cache.GetAsync(key);
+        }
+
+        public bool TryGet(TK key, out TV value)
+        {
+            return _cache.TryGet(key, out value);
+        }
+
+        public Task<CacheResult<TV>> TryGetAsync(TK key)
+        {
+            return _cache.TryGetAsync(key);
+        }
+
+        public ICollection<ICacheEntry<TK, TV>> GetAll(IEnumerable<TK> keys)
+        {
+            return _cache.GetAll(keys);
+        }
+
+        public Task<ICollection<ICacheEntry<TK, TV>>> GetAllAsync(IEnumerable<TK> keys)
+        {
+            return _cache.GetAllAsync(keys);
+        }
+
+        public TV this[TK key]
+        {
+            get { return _cache[key]; }
+            set { _cache[key] = value; }
+        }
+
+        public bool ContainsKey(TK key)
+        {
+            return _cache.ContainsKey(key);
+        }
+
+        public Task<bool> ContainsKeyAsync(TK key)
+        {
+            return _cache.ContainsKeyAsync(key);
+        }
+
+        public bool ContainsKeys(IEnumerable<TK> keys)
+        {
+            return _cache.ContainsKeys(keys);
+        }
+
+        public Task<bool> ContainsKeysAsync(IEnumerable<TK> keys)
+        {
+            return _cache.ContainsKeysAsync(keys);
+        }
+
+        public IQueryCursor<ICacheEntry<TK, TV>> Query(ScanQuery<TK, TV> scanQuery)
+        {
+            return _cache.Query(scanQuery);
+        }
+
+        public IQueryCursor<ICacheEntry<TK, TV>> Query(SqlQuery sqlQuery)
+        {
+            return _cache.Query(sqlQuery);
+        }
+
+        public IFieldsQueryCursor Query(SqlFieldsQuery sqlFieldsQuery)
+        {
+            return _cache.Query(sqlFieldsQuery);
+        }
+
+        public CacheResult<TV> GetAndPut(TK key, TV val)
+        {
+            return _cache.GetAndPut(key, val);
+        }
+
+        public Task<CacheResult<TV>> GetAndPutAsync(TK key, TV val)
+        {
+            return _cache.GetAndPutAsync(key, val);
+        }
+
+        public CacheResult<TV> GetAndReplace(TK key, TV val)
+        {
+            return _cache.GetAndReplace(key, val);
+        }
+
+        public Task<CacheResult<TV>> GetAndReplaceAsync(TK key, TV val)
+        {
+            return _cache.GetAndReplaceAsync(key, val);
+        }
+
+        public CacheResult<TV> GetAndRemove(TK key)
+        {
+            return _cache.GetAndRemove(key);
+        }
+
+        public Task<CacheResult<TV>> GetAndRemoveAsync(TK key)
+        {
+            return _cache.GetAndRemoveAsync(key);
+        }
+
+        public bool PutIfAbsent(TK key, TV val)
+        {
+            return _cache.PutIfAbsent(key, val);
+        }
+
+        public Task<bool> PutIfAbsentAsync(TK key, TV val)
+        {
+            return _cache.PutIfAbsentAsync(key, val);
+        }
+
+        public CacheResult<TV> GetAndPutIfAbsent(TK key, TV val)
+        {
+            return _cache.GetAndPutIfAbsent(key, val);
+        }
+
+        public Task<CacheResult<TV>> GetAndPutIfAbsentAsync(TK key, TV val)
+        {
+            return _cache.GetAndPutIfAbsentAsync(key, val);
+        }
+
+        public bool Replace(TK key, TV val)
+        {
+            return _cache.Replace(key, val);
+        }
+
+        public Task<bool> ReplaceAsync(TK key, TV val)
+        {
+            return _cache.ReplaceAsync(key, val);
+        }
+
+        public bool Replace(TK key, TV oldVal, TV newVal)
+        {
+            return _cache.Replace(key, oldVal, newVal);
+        }
+
+        public Task<bool> ReplaceAsync(TK key, TV oldVal, TV newVal)
+        {
+            return _cache.ReplaceAsync(key, oldVal, newVal);
+        }
+
+        public void PutAll(IEnumerable<KeyValuePair<TK, TV>> vals)
+        {
+            _cache.PutAll(vals);
+        }
+
+        public Task PutAllAsync(IEnumerable<KeyValuePair<TK, TV>> vals)
+        {
+            return _cache.PutAllAsync(vals);
+        }
+
+        public void Clear()
+        {
+            _cache.Clear();
+        }
+
+        public Task ClearAsync()
+        {
+            return _cache.ClearAsync();
+        }
+
+        public void Clear(TK key)
+        {
+            _cache.Clear(key);
+        }
+
+        public Task ClearAsync(TK key)
+        {
+            return _cache.ClearAsync(key);
+        }
+
+        public void ClearAll(IEnumerable<TK> keys)
+        {
+            _cache.ClearAll(keys);
+        }
+
+        public Task ClearAllAsync(IEnumerable<TK> keys)
+        {
+            return _cache.ClearAllAsync(keys);
+        }
+
+        public bool Remove(TK key)
+        {
+            return _cache.Remove(key);
+        }
+
+        public Task<bool> RemoveAsync(TK key)
+        {
+            return _cache.RemoveAsync(key);
+        }
+
+        public bool Remove(TK key, TV val)
+        {
+            return _cache.Remove(key, val);
+        }
+
+        public Task<bool> RemoveAsync(TK key, TV val)
+        {
+            return _cache.RemoveAsync(key, val);
+        }
+
+        public void RemoveAll(IEnumerable<TK> keys)
+        {
+            _cache.RemoveAll(keys);
+        }
+
+        public Task RemoveAllAsync(IEnumerable<TK> keys)
+        {
+            return _cache.RemoveAllAsync(keys);
+        }
+
+        public void RemoveAll()
+        {
+            _cache.RemoveAll();
+        }
+
+        public Task RemoveAllAsync()
+        {
+            return _cache.RemoveAllAsync();
+        }
+
+        public long GetSize(params CachePeekMode[] modes)
+        {
+            return _cache.GetSize(modes);
+        }
+
+        public Task<long> GetSizeAsync(params CachePeekMode[] modes)
+        {
+            throw new NotSupportedException();
+        }
+
+        public CacheClientConfiguration GetConfiguration()
+        {
+            throw new NotSupportedException();
+        }
+
+        public ICacheClient<TK1, TV1> WithKeepBinary<TK1, TV1>()
+        {
+            throw new NotSupportedException();
+        }
+
+        public ICacheClient<TK, TV> WithExpiryPolicy(IExpiryPolicy plc)
+        {
+            throw new NotSupportedException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapterExtensions.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapterExtensions.cs
new file mode 100644
index 0000000..3cf52de
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientServerCacheAdapterExtensions.cs
@@ -0,0 +1,36 @@
+/*
+ * 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.Client
+{
+    using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Client.Cache;
+
+    /// <summary>
+    /// Extension methods related to <see cref="ClientServerCacheAdapter{TK, TV}"/>.
+    /// </summary>
+    public static class ClientServerCacheAdapterExtensions
+    {
+        /// <summary>
+        /// Returns given <see cref="ICache{TK,TV}"/> wrapped as <see cref="ICacheClient{TK,TV}"/>.
+        /// </summary>
+        public static ICacheClient<TK, TV> AsCacheClient<TK, TV>(this ICache<TK, TV> cache)
+        {
+            return new ClientServerCacheAdapter<TK, TV>(cache);
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
index d5d3ea3..c94eb54 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientTestBase.cs
@@ -21,6 +21,7 @@ namespace Apache.Ignite.Core.Tests.Client
     using System.Collections.Generic;
     using System.Linq;
     using System.Net;
+    using System.Text.RegularExpressions;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
     using Apache.Ignite.Core.Client;
@@ -38,6 +39,12 @@ namespace Apache.Ignite.Core.Tests.Client
         /** Cache name. */
         protected const string CacheName = "cache";
 
+        /** */
+        protected const string RequestNamePrefixCache = "cache.ClientCache";
+
+        /** */
+        protected const string RequestNamePrefixBinary = "binary.ClientBinary";
+
         /** Grid count. */
         private readonly int _gridCount = 1;
 
@@ -69,7 +76,7 @@ namespace Apache.Ignite.Core.Tests.Client
             for (var i = 1; i < _gridCount; i++)
             {
                 cfg = GetIgniteConfiguration();
-                cfg.AutoGenerateIgniteInstanceName = true;
+                cfg.IgniteInstanceName = i.ToString();
 
                 Ignition.Start(cfg);
             }
@@ -99,6 +106,8 @@ namespace Apache.Ignite.Core.Tests.Client
 
             Assert.AreEqual(0, cache.GetSize(CachePeekMode.All));
             Assert.AreEqual(0, GetClientCache<int>().GetSize(CachePeekMode.All));
+            
+            ClearLoggers();
         }
 
         /// <summary>
@@ -156,7 +165,10 @@ namespace Apache.Ignite.Core.Tests.Client
         /// </summary>
         protected virtual IgniteConfiguration GetIgniteConfiguration()
         {
-            return TestUtils.GetTestConfiguration();
+            return new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                Logger = new ListLogger()
+            };
         }
 
         /// <summary>
@@ -202,7 +214,7 @@ namespace Apache.Ignite.Core.Tests.Client
         /// <summary>
         /// Gets the logs.
         /// </summary>
-        protected List<ListLogger.Entry> GetLogs(IIgniteClient client)
+        protected static List<ListLogger.Entry> GetLogs(IIgniteClient client)
         {
             var igniteClient = (IgniteClient) client;
             var logger = igniteClient.GetConfiguration().Logger;
@@ -211,6 +223,68 @@ namespace Apache.Ignite.Core.Tests.Client
         }
         
         /// <summary>
+        /// Gets client request names for a given server node.
+        /// </summary>
+        protected static IEnumerable<string> GetServerRequestNames(int serverIndex = 0, string prefix = null)
+        {
+            var instanceName = serverIndex == 0 ? null : serverIndex.ToString();
+            var grid = Ignition.GetIgnite(instanceName);
+            var logger = (ListLogger) grid.Logger;
+         
+            return GetServerRequestNames(logger, prefix);
+        }
+
+        /// <summary>
+        /// Gets client request names from a given logger.
+        /// </summary>
+        protected static IEnumerable<string> GetServerRequestNames(ListLogger logger, string prefix = null)
+        {
+            // Full request class name examples:
+            // org.apache.ignite.internal.processors.platform.client.binary.ClientBinaryTypeGetRequest
+            // org.apache.ignite.internal.processors.platform.client.cache.ClientCacheGetRequest
+            var messageRegex = new Regex(
+                @"Client request received \[reqId=\d+, addr=/127.0.0.1:\d+, " +
+                @"req=org\.apache\.ignite\.internal\.processors\.platform\.client\..*?" +
+                prefix +
+                @"(\w+)Request@");
+
+            return logger.Entries
+                .Select(m => messageRegex.Match(m.Message))
+                .Where(m => m.Success)
+                .Select(m => m.Groups[1].Value);
+        }
+
+        /// <summary>
+        /// Gets client request names from all server nodes.
+        /// </summary>
+        protected static IEnumerable<string> GetAllServerRequestNames(string prefix = null)
+        {
+            return GetLoggers().SelectMany(l => GetServerRequestNames(l, prefix));
+        }
+
+        /// <summary>
+        /// Gets loggers from all server nodes.
+        /// </summary>
+        protected static IEnumerable<ListLogger> GetLoggers()
+        {
+            return Ignition.GetAll()
+                .OrderBy(i => i.Name)
+                .Select(i => i.Logger)
+                .Cast<ListLogger>();
+        }
+
+        /// <summary>
+        /// Clears loggers of all server nodes.
+        /// </summary>
+        protected static void ClearLoggers()
+        {
+            foreach (var logger in GetLoggers())
+            {
+                logger.Clear();
+            }
+        }
+
+        /// <summary>
         /// Asserts the client configs are equal.
         /// </summary>
         public static void AssertClientConfigsAreEqual(CacheClientConfiguration cfg, CacheClientConfiguration cfg2)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
index 9802489..17830d8 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/IgniteClientConfigurationTest.cs
@@ -298,7 +298,7 @@ namespace Apache.Ignite.Core.Tests.Client
         [Test]
         public void TestSchemaValidation()
         {
-            var xml = File.ReadAllText("Config\\Client\\IgniteClientConfiguration.xml");
+            var xml = File.ReadAllText(Path.Combine("Config", "Client", "IgniteClientConfiguration.xml"));
             var xmlns = "http://ignite.apache.org/schema/dotnet/IgniteClientConfigurationSection";
             var schemaFile = "IgniteClientConfigurationSection.xsd";
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IIgnite.cs b/modules/platforms/dotnet/Apache.Ignite.Core/IIgnite.cs
index b1930cf..1b4deb2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IIgnite.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IIgnite.cs
@@ -411,7 +411,7 @@ namespace Apache.Ignite.Core
 
         /// <summary>
         /// Adds cache configuration template. Name should contain *.
-        /// Template settins are applied to a cache created with <see cref="CreateCache{K,V}(string)"/> if specified
+        /// Template settings are applied to a cache created with <see cref="CreateCache{K,V}(string)"/> if specified
         /// name matches the template name.
         /// </summary>
         /// <param name="configuration">Configuration.</param>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectSchemaSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectSchemaSerializer.cs
index 0a1b2d1..1a767be 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectSchemaSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryObjectSchemaSerializer.cs
@@ -248,30 +248,6 @@ namespace Apache.Ignite.Core.Impl.Binary
                 return BinaryObjectHeader.Flag.None;
             }
         }
-
-        /// <summary>
-        /// Gets the field ids.
-        /// </summary>
-        private static int[] GetFieldIds(BinaryObjectHeader hdr, IIgniteInternal ignite)
-        {
-            Debug.Assert(hdr.TypeId != BinaryTypeId.Unregistered);
-
-            int[] fieldIds = null;
-
-            if (ignite != null)
-            {
-                fieldIds = ignite.BinaryProcessor.GetSchema(hdr.TypeId, hdr.SchemaId);
-            }
-
-            if (fieldIds == null)
-            {
-                throw new BinaryObjectException("Cannot find schema for object with compact footer [" +
-                                                "typeId=" + hdr.TypeId + ", schemaId=" + hdr.SchemaId + ']');
-            }
-
-            return fieldIds;
-        }
-
         /// <summary>
         /// Reads the schema, maintains stream position.
         /// </summary>
@@ -307,6 +283,29 @@ namespace Apache.Ignite.Core.Impl.Binary
             return res;
         }
 
+        /// <summary>
+        /// Gets the field ids.
+        /// </summary>
+        private static int[] GetFieldIds(BinaryObjectHeader hdr, IIgniteInternal ignite)
+        {
+            Debug.Assert(hdr.TypeId != BinaryTypeId.Unregistered);
+
+            int[] fieldIds = null;
+
+            if (ignite != null)
+            {
+                fieldIds = GetCachedSchema(hdr, ignite) ??
+                           ignite.Marshaller.GetBinaryType(hdr.TypeId).Schema.Get(hdr.SchemaId);
+            }
+
+            if (fieldIds == null)
+            {
+                throw new BinaryObjectException("Cannot find schema for object with compact footer [" +
+                                                "typeId=" + hdr.TypeId + ", schemaId=" + hdr.SchemaId + ']');
+            }
+
+            return fieldIds;
+        }
 
         /// <summary>
         /// Gets the field ids.
@@ -315,5 +314,19 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             return schema.Get(hdr.SchemaId) ?? GetFieldIds(hdr, ignite);
         }
+
+        /// <summary>
+        /// Gets the cached schema.
+        /// </summary>
+        private static int[] GetCachedSchema(BinaryObjectHeader hdr, IIgniteInternal ignite)
+        {
+            var cachedHolder = ignite.Marshaller.GetCachedBinaryTypeHolder(hdr.TypeId);
+            if (cachedHolder == null || cachedHolder.BinaryType == null || cachedHolder.BinaryType.Schema == null)
+            {
+                return null;
+            }
+            
+            return cachedHolder.BinaryType.Schema.Get(hdr.SchemaId);
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessor.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessor.cs
index 318fd07..b62977a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessor.cs
@@ -32,13 +32,16 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private enum Op
         {
+            // ReSharper disable UnusedMember.Local
             GetMeta = 1,
             GetAllMeta = 2,
             PutMeta = 3,
             GetSchema = 4,
             RegisterType = 5,
             GetType = 6,
-            RegisterEnum = 7
+            RegisterEnum = 7,
+            GetMetaWithSchemas = 8
+            // ReSharper restore UnusedMember.Local
         }
 
         /// <summary>
@@ -55,13 +58,13 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         public BinaryType GetBinaryType(int typeId)
         {
-            return DoOutInOp((int) Op.GetMeta,
+            return DoOutInOp((int) Op.GetMetaWithSchemas,
                 writer => writer.WriteInt(typeId),
                 stream =>
                 {
                     var reader = Marshaller.StartUnmarshal(stream, false);
 
-                    return reader.ReadBoolean() ? new BinaryType(reader) : null;
+                    return reader.ReadBoolean() ? new BinaryType(reader, true) : null;
                 }
             );
         }
@@ -87,18 +90,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
-        /// Gets the schema.
-        /// </summary>
-        public int[] GetSchema(int typeId, int schemaId)
-        {
-            return DoOutInOp<int[]>((int) Op.GetSchema, writer =>
-            {
-                writer.WriteInt(typeId);
-                writer.WriteInt(schemaId);
-            });
-        }
-
-        /// <summary>
         /// Put binary types to Grid.
         /// </summary>
         /// <param name="types">Binary types.</param>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
index 9e0ce75..8ef84c1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryProcessorClient.cs
@@ -59,12 +59,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /** <inheritdoc /> */
-        public int[] GetSchema(int typeId, int schemaId)
-        {
-            return GetBinaryType(typeId).Schema.Get(schemaId);
-        }
-
-        /** <inheritdoc /> */
         public void PutBinaryTypes(ICollection<BinaryType> types)
         {
             Debug.Assert(types != null);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
index 2d8604a..9e7d8ef 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
@@ -732,7 +732,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                             "No matching type found for object [typeId={0}, typeName={1}]. " +
                             "This usually indicates that assembly with specified type is not loaded on a node. " +
                             "When using Apache.Ignite.exe, make sure to load assemblies with -assembly parameter. " +
-                            "Alternatively, set IgniteConfiguration.PeerAssemblyLoadingEnabled to true.",
+                            "Alternatively, set IgniteConfiguration.PeerAssemblyLoadingMode to CurrentAppDomain.",
                             desc.TypeId, desc.TypeName));
                     }
 
@@ -867,6 +867,14 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Gets the schema for the current object, if any.
+        /// </summary>
+        public int[] Schema
+        {
+            get { return _frame.Schema; }
+        }
+
+        /// <summary>
         /// Seeks to raw data.
         /// </summary>
         internal void SeekToRaw()
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryProcessor.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryProcessor.cs
index c626c76..fed78c3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryProcessor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryProcessor.cs
@@ -37,11 +37,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         List<IBinaryType> GetBinaryTypes();
 
         /// <summary>
-        /// Gets the schema.
-        /// </summary>
-        int[] GetSchema(int typeId, int schemaId);
-
-        /// <summary>
         /// Put binary types to Grid.
         /// </summary>
         /// <param name="types">Binary types.</param>
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 e1cc98f..be7d61b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -276,12 +276,16 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <returns>Metadata or null.</returns>
         public BinaryType GetBinaryType(int typeId)
         {
+            // NOTE: This method results can't (easily) be cached because binary metadata is changing on the fly:
+            // New fields and enum values can be added.
             if (Ignite != null)
             {
                 var meta = Ignite.BinaryProcessor.GetBinaryType(typeId);
 
                 if (meta != null)
                 {
+                    UpdateOrCreateBinaryTypeHolder(meta);
+
                     return meta;
                 }
             }
@@ -290,6 +294,20 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Gets cached metadata for the given type ID.
+        /// NOTE: Returned value is potentially stale.
+        /// Caller is responsible for refreshing the value as needed by invoking <see cref="GetBinaryType"/>.
+        /// </summary>
+        /// <param name="typeId">Type ID.</param>
+        /// <returns>Metadata or null.</returns>
+        public BinaryTypeHolder GetCachedBinaryTypeHolder(int typeId)
+        {
+            BinaryTypeHolder holder;
+            _metas.TryGetValue(typeId, out holder);
+            return holder;
+        }
+
+        /// <summary>
         /// Puts the binary type metadata to Ignite.
         /// </summary>
         /// <param name="desc">Descriptor.</param>
@@ -314,37 +332,80 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <returns>Binary type handler.</returns>
         public IBinaryTypeHandler GetBinaryTypeHandler(IBinaryTypeDescriptor desc)
         {
-            BinaryTypeHolder holder;
+            var holder = GetBinaryTypeHolder(desc);
+
+            if (holder != null)
+            {
+                ICollection<int> ids = holder.GetFieldIds();
+
+                bool newType = ids.Count == 0 && !holder.IsSaved;
+
+                return new BinaryTypeHashsetHandler(ids, newType);
+            }
 
-            if (!_metas.TryGetValue(desc.TypeId, out holder))
+            return null;
+        }
+
+        /// <summary>
+        /// Gets the binary type holder.
+        /// </summary>
+        /// <param name="desc">Descriptor.</param>
+        /// <returns>Holder</returns>
+        private BinaryTypeHolder GetBinaryTypeHolder(IBinaryTypeDescriptor desc)
+        {
+            BinaryTypeHolder holder;
+            if (_metas.TryGetValue(desc.TypeId, out holder))
+            {
+                return holder;
+            }
+            
+            lock (this)
             {
-                lock (this)
+                if (!_metas.TryGetValue(desc.TypeId, out holder))
                 {
-                    if (!_metas.TryGetValue(desc.TypeId, out holder))
-                    {
-                        IDictionary<int, BinaryTypeHolder> metas0 =
-                            new Dictionary<int, BinaryTypeHolder>(_metas);
+                    var metas0 = new Dictionary<int, BinaryTypeHolder>(_metas);
 
-                        holder = new BinaryTypeHolder(desc.TypeId, desc.TypeName, desc.AffinityKeyFieldName,
-                            desc.IsEnum, this);
+                    holder = new BinaryTypeHolder(desc.TypeId, desc.TypeName, desc.AffinityKeyFieldName,
+                        desc.IsEnum, this);
 
-                        metas0[desc.TypeId] = holder;
+                    metas0[desc.TypeId] = holder;
 
-                        _metas = metas0;
-                    }
+                    _metas = metas0;
                 }
             }
 
-            if (holder != null)
+            return holder;
+        }
+        
+        /// <summary>
+        /// Updates or creates cached binary type holder. 
+        /// </summary>
+        private void UpdateOrCreateBinaryTypeHolder(BinaryType meta)
+        {
+            BinaryTypeHolder holder;
+            if (_metas.TryGetValue(meta.TypeId, out holder))
             {
-                ICollection<int> ids = holder.GetFieldIds();
+                holder.Merge(meta);
+                return;
+            }
+            
+            lock (this)
+            {
+                if (_metas.TryGetValue(meta.TypeId, out holder))
+                {
+                    holder.Merge(meta);
+                    return;
+                }
+                
+                var metas0 = new Dictionary<int, BinaryTypeHolder>(_metas);
 
-                bool newType = ids.Count == 0 && !holder.Saved();
+                holder = new BinaryTypeHolder(meta.TypeId, meta.TypeName, meta.AffinityKeyFieldName, meta.IsEnum, this);
+                holder.Merge(meta);
 
-                return new BinaryTypeHashsetHandler(ids, newType);
-            }
+                metas0[meta.TypeId] = holder;
 
-            return null;
+                _metas = metas0;
+            }
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryType.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryType.cs
index 06794b5..dae0171 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryType.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryType.cs
@@ -32,7 +32,7 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
     {
         /** Empty metadata. */
         public static readonly BinaryType Empty =
-            new BinaryType(BinaryTypeId.Object, BinaryTypeNames.TypeNameObject, null, null, false, null, null);
+            new BinaryType(BinaryTypeId.Object, BinaryTypeNames.TypeNameObject, null, null, false, null, null, null);
 
         /** Empty dictionary. */
         private static readonly IDictionary<string, BinaryField> EmptyDict = new Dictionary<string, BinaryField>();
@@ -196,7 +196,7 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
         public BinaryType(IBinaryTypeDescriptor desc, Marshaller marshaller, 
             IDictionary<string, BinaryField> fields = null) 
             : this (desc.TypeId, desc.TypeName, fields, desc.AffinityKeyFieldName, desc.IsEnum, 
-                  GetEnumValues(desc), marshaller)
+                  GetEnumValues(desc), marshaller, null)
         {
             _descriptor = desc;
         }
@@ -211,8 +211,10 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
         /// <param name="isEnum">Enum flag.</param>
         /// <param name="enumValues">Enum values.</param>
         /// <param name="marshaller">Marshaller.</param>
+        /// <param name="schema"></param>
         public BinaryType(int typeId, string typeName, IDictionary<string, BinaryField> fields,
-            string affKeyFieldName, bool isEnum, IDictionary<string, int> enumValues, Marshaller marshaller)
+            string affKeyFieldName, bool isEnum, IDictionary<string, int> enumValues, Marshaller marshaller,
+            BinaryObjectSchema schema)
         {
             _typeId = typeId;
             _typeName = typeName;
@@ -227,6 +229,7 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
             }
 
             _marshaller = marshaller;
+            _schema = schema;
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryTypeHolder.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryTypeHolder.cs
index 7e1e970..d9426b9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryTypeHolder.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Metadata/BinaryTypeHolder.cs
@@ -68,19 +68,27 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
         }
 
         /// <summary>
-        /// Get saved flag.
+        /// Gets saved flag.
         /// </summary>
-        /// <returns>True if type metadata was saved at least once.</returns>
-        public bool Saved()
+        /// <value>True if type metadata was saved at least once.</value>
+        public bool IsSaved
         {
-            return _saved;
+            get { return _saved; }
+        }
+
+        /// <summary>
+        /// Gets the cached binary type metadata.
+        /// </summary>
+        public BinaryType BinaryType
+        {
+            get { return _meta; }
         }
 
         /// <summary>
         /// Currently cached field IDs.
         /// </summary>
         /// <returns>Cached field IDs.</returns>
-        public ICollection<int> GetFieldIds()
+        public HashSet<int> GetFieldIds()
         {
             var ids0 = _ids;
 
@@ -134,24 +142,51 @@ namespace Apache.Ignite.Core.Impl.Binary.Metadata
                 // 2. Add new fields.
                 foreach (var fieldMeta in fieldsMap)
                 {
-                    int fieldId = BinaryUtils.FieldId(meta.TypeId, fieldMeta.Key, null, null);
-
-                    if (!newIds.Contains(fieldId))
-                    {
-                        newIds.Add(fieldId);
-                    }
+                    var fieldId = fieldMeta.Value.FieldId;
 
-                    if (!newFields.ContainsKey(fieldMeta.Key))
-                    {
-                        newFields[fieldMeta.Key] = fieldMeta.Value;
-                    }
+                    newIds.Add(fieldId);
+                    newFields[fieldMeta.Key] = fieldMeta.Value;
                 }
+                
+                // 3. Merge schema.
+                var schema = MergeSchemas(meta0, meta); 
 
-                // 3. Assign new meta. Order is important here: meta must be assigned before field IDs.
+                // 4. Assign new meta. Order is important here: meta must be assigned before field IDs.
                 _meta = new BinaryType(_typeId, _typeName, newFields, _affKeyFieldName, _isEnum, 
-                    meta.EnumValuesMap, _marshaller);
+                    meta.EnumValuesMap, _marshaller, schema);
+                
                 _ids = newIds;
             }
         }
+
+        /// <summary>
+        /// Merges schemas from two binary types.
+        /// </summary>
+        private static BinaryObjectSchema MergeSchemas(BinaryType a, BinaryType b)
+        {
+            if (a == null || a.Schema == null)
+            {
+                return b.Schema;
+            }
+
+            if (b == null || b.Schema == null)
+            {
+                return a.Schema;
+            }
+
+            var res = new BinaryObjectSchema();
+            
+            foreach (var schema in a.Schema.GetAll())
+            {
+                res.Add(schema.Key, schema.Value);
+            }
+            
+            foreach (var schema in b.Schema.GetAll())
+            {
+                res.Add(schema.Key, schema.Value);
+            }
+
+            return res;
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
index e597382..2d87a1f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
@@ -251,15 +251,40 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private static IEnumerable<string> GetBinaryTypeFields(BinaryReader reader, IBinaryTypeDescriptor desc)
         {
-            var binaryType = reader.Marshaller.GetBinaryType(desc.TypeId);
-
-            if (binaryType == BinaryType.Empty)
+            var schema = reader.Schema;
+            if (schema == null || schema.Length == 0)
             {
-                // Object without fields.
                 return Enumerable.Empty<string>();
             }
 
-            return binaryType.Fields;
+            // Try using cached metadata: if all fields from current schema are present there, we are good.
+            // Any extra fields that may have been added to the binary type are not present in the current object.
+            var binaryTypeHolder = reader.Marshaller.GetCachedBinaryTypeHolder(desc.TypeId);
+            if (binaryTypeHolder != null && HasAllFields(binaryTypeHolder, schema))
+            {
+                return binaryTypeHolder.BinaryType.Fields ?? Enumerable.Empty<string>();
+            }
+
+            // Cached metadata is not present or out of date: request latest (expensive operation).
+            return reader.Marshaller.GetBinaryType(desc.TypeId).Fields ?? Enumerable.Empty<string>();
+        }
+
+        /// <summary>
+        /// Checks whether all field ids from provided schema are present in the given binary type.
+        /// </summary>
+        private static bool HasAllFields(BinaryTypeHolder binaryTypeHolder, int[] schema)
+        {
+            var fieldIds = binaryTypeHolder.GetFieldIds();
+            
+            foreach (var fieldId in schema)
+            {
+                if (!fieldIds.Contains(fieldId))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.DotNetCore.sln b/modules/platforms/dotnet/Apache.Ignite.DotNetCore.sln
index 3724eab..9f3e788 100644
--- a/modules/platforms/dotnet/Apache.Ignite.DotNetCore.sln
+++ b/modules/platforms/dotnet/Apache.Ignite.DotNetCore.sln
@@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Ignite.Core.Tests.Do
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.DotNetCore", "Apache.Ignite\Apache.Ignite.DotNetCore.csproj", "{A28AA3F7-BA2B-49C2-B5DA-02707D4470B1}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Ignite.Benchmarks.DotNetCore", "Apache.Ignite.Benchmarks\Apache.Ignite.Benchmarks.DotNetCore.csproj", "{4344AEB8-6772-4625-9445-2C4A224B7716}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
 		{A28AA3F7-BA2B-49C2-B5DA-02707D4470B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A28AA3F7-BA2B-49C2-B5DA-02707D4470B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A28AA3F7-BA2B-49C2-B5DA-02707D4470B1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4344AEB8-6772-4625-9445-2C4A224B7716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4344AEB8-6772-4625-9445-2C4A224B7716}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4344AEB8-6772-4625-9445-2C4A224B7716}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4344AEB8-6772-4625-9445-2C4A224B7716}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/modules/platforms/dotnet/README.md b/modules/platforms/dotnet/README.md
index 4830940..319ccb5 100644
--- a/modules/platforms/dotnet/README.md
+++ b/modules/platforms/dotnet/README.md
@@ -2,11 +2,7 @@
 
 <a href="https://ignite.apache.org/"><img src="https://ignite.apache.org/images/logo3.png" hspace="20"/></a><img src="https://ptupitsyn.github.io/images/net-framework.png" hspace="20" />
 
-<a href="https://www.nuget.org/packages?q=Apache.Ignite"><img src="https://img.shields.io/nuget/v/Apache.Ignite.svg" /></a>
-
-<a href="https://www.myget.org/gallery/apache-ignite-net-nightly"><img src="https://img.shields.io/myget/apache-ignite-net-nightly/vpre/Apache.Ignite.svg" /></a>
-
-<a href="https://ci.ignite.apache.org/viewType.html?buildTypeId=IgniteTests24Java8_IgnitePlatformNet&branch_IgniteTests24Java8=<default>"><img src="http://ci.ignite.apache.org/app/rest/builds/buildType:(id:IgniteTests24Java8_IgnitePlatformNet)/statusIcon" /></a>
+<a href="https://www.nuget.org/packages?q=GridGain.Ignite"><img src="https://img.shields.io/nuget/v/GridGain.Ignite.svg" /></a>
 
 ## Getting Started