You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/10/25 09:59:36 UTC

[44/50] [abbrv] ignite git commit: IGNITE-6704 .NET: CacheConfiguration.KeyConfiguration

IGNITE-6704 .NET: CacheConfiguration.KeyConfiguration

This closes #2916


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

Branch: refs/heads/ignite-5937
Commit: 5b3ad97b939bee6f3e272f93c75e920208cb7491
Parents: 1aab9d8
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Tue Oct 24 16:45:57 2017 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Tue Oct 24 16:45:57 2017 +0300

----------------------------------------------------------------------
 .../utils/PlatformConfigurationUtils.java       |  26 ++++
 .../Apache.Ignite.Core.Tests.csproj             |   1 +
 .../ApiParity/CacheConfigurationParityTest.cs   |   2 -
 .../Cache/Affinity/AffinityAttributeTest.cs     | 135 +++++++++++++++++++
 .../Cache/Affinity/AffinityFieldTest.cs         |  35 ++++-
 .../Cache/CacheConfigurationTest.cs             |  12 +-
 .../Config/full-config.xml                      |   3 +
 .../IgniteConfigurationSerializerTest.cs        |  13 +-
 .../Apache.Ignite.Core.Tests/TestUtils.cs       |  49 ++++---
 .../Apache.Ignite.Core.csproj                   |   2 +
 .../Affinity/AffinityKeyMappedAttribute.cs      |  26 +++-
 .../Cache/Configuration/CacheConfiguration.cs   |  44 +++---
 .../Configuration/CacheKeyConfiguration.cs      |  84 ++++++++++++
 .../Cache/Configuration/QueryEntity.cs          |  34 +----
 .../Apache.Ignite.Core/IgniteConfiguration.cs   |  17 +--
 .../IgniteConfigurationSection.xsd              |  26 ++++
 .../Impl/Binary/BinaryReaderExtensions.cs       |  27 ++++
 .../Impl/Binary/BinaryUtils.cs                  |   2 +
 .../Impl/Binary/BinaryWriterExtensions.cs       |  29 ++++
 .../Impl/Binary/IBinaryRawWriteAware.cs         |  42 ++++++
 .../Impl/Binary/Marshaller.cs                   |  23 +---
 .../Impl/Binary/ReflectionUtils.cs              |  27 ++++
 22 files changed, 542 insertions(+), 117 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
index 9711e62..981a231 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
@@ -39,6 +39,7 @@ import org.apache.ignite.binary.BinaryBasicNameMapper;
 import org.apache.ignite.binary.BinaryRawReader;
 import org.apache.ignite.binary.BinaryRawWriter;
 import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheKeyConfiguration;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.CacheRebalanceMode;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
@@ -223,6 +224,18 @@ public class PlatformConfigurationUtils {
         ccfg.setAffinity(readAffinityFunction(in));
         ccfg.setExpiryPolicyFactory(readExpiryPolicyFactory(in));
 
+        int keyCnt = in.readInt();
+
+        if (keyCnt > 0) {
+            CacheKeyConfiguration[] keys = new CacheKeyConfiguration[keyCnt];
+
+            for (int i = 0; i < keyCnt; i++) {
+                keys[i] = new CacheKeyConfiguration(in.readString(), in.readString());
+            }
+
+            ccfg.setKeyConfiguration(keys);
+        }
+
         int pluginCnt = in.readInt();
 
         if (pluginCnt > 0) {
@@ -916,6 +929,19 @@ public class PlatformConfigurationUtils {
         writeAffinityFunction(writer, ccfg.getAffinity());
         writeExpiryPolicyFactory(writer, ccfg.getExpiryPolicyFactory());
 
+        CacheKeyConfiguration[] keys = ccfg.getKeyConfiguration();
+
+        if (keys != null) {
+            writer.writeInt(keys.length);
+
+            for (CacheKeyConfiguration key : keys) {
+                writer.writeString(key.getTypeName());
+                writer.writeString(key.getAffinityKeyFieldName());
+            }
+        } else {
+            writer.writeInt(0);
+        }
+
         CachePluginConfiguration[] plugins = ccfg.getPluginConfigurations();
         if (plugins != null) {
             int cnt = 0;

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index 7e1753c..e7302bb 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
@@ -86,6 +86,7 @@
     <Compile Include="Binary\BinarySelfTestSimpleName.cs" />
     <Compile Include="Binary\EnumsTestOnline.cs" />
     <Compile Include="Binary\Serializable\GenericCollectionsTest.cs" />
+    <Compile Include="Cache\Affinity\AffinityAttributeTest.cs" />
     <Compile Include="Cache\DataRegionMetricsTest.cs" />
     <Compile Include="Cache\DataStorageMetricsTest.cs" />
     <Compile Include="Cache\PersistenceTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ApiParity/CacheConfigurationParityTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ApiParity/CacheConfigurationParityTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ApiParity/CacheConfigurationParityTest.cs
index 617eb83..6bc5472 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ApiParity/CacheConfigurationParityTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ApiParity/CacheConfigurationParityTest.cs
@@ -58,8 +58,6 @@ namespace Apache.Ignite.Core.Tests.ApiParity
         {
             "NodeFilter",  // IGNITE-2890
 
-            "KeyConfiguration",  // IGNITE-6704
-
             // IGNITE-6705
             "IsOnheapCacheEnabled",
             "StoreConcurrentLoadAllThreshold",

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityAttributeTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityAttributeTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityAttributeTest.cs
new file mode 100644
index 0000000..d99501a
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityAttributeTest.cs
@@ -0,0 +1,135 @@
+/*
+ * 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 169
+#pragma warning disable 649
+namespace Apache.Ignite.Core.Tests.Cache.Affinity
+{
+    using System.Diagnostics.CodeAnalysis;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Affinity;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests the <see cref="AffinityKeyMappedAttribute"/>.
+    /// </summary>
+    [SuppressMessage("ReSharper", "UnusedMember.Local")]
+    public class AffinityAttributeTest
+    {
+        /// <summary>
+        /// Tests the property attribute.
+        /// </summary>
+        [Test]
+        public void TestPropertyAttribute()
+        {
+            Assert.IsNull(AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(NoAttr)));
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(PublicProperty)));
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(InheritPublicProperty)));
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(PrivateProperty)));
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(InheritPrivateProperty)));
+        }
+
+        /// <summary>
+        /// Tests the field attribute.
+        /// </summary>
+        [Test]
+        public void TestFieldAttribute()
+        {
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(PublicField)));
+            Assert.AreEqual("_abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(PrivateField)));
+            Assert.AreEqual("Abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(InheritPublicField)));
+            Assert.AreEqual("_abc", AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(InheritPrivateField)));
+        }
+
+        /// <summary>
+        /// Tests multiple attributes per class.
+        /// </summary>
+        [Test]
+        public void TestMultipleAttributes()
+        {
+            var ex = Assert.Throws<BinaryObjectException>(() =>
+                AffinityKeyMappedAttribute.GetFieldNameFromAttribute(typeof(MultipleAttributes)));
+
+            Assert.AreEqual(string.Format(
+                "Multiple 'AffinityKeyMappedAttribute' attributes found on type '{0}'. There can be only one " +
+                "affinity field.", typeof(MultipleAttributes).FullName), ex.Message);
+        }
+
+        private class NoAttr
+        {
+            public string Abc { get; set; }
+        }
+
+        private class PublicProperty
+        {
+            public string Foo { get; set; }
+
+            [AffinityKeyMapped]
+            public string Abc { get; set; }
+        }
+
+        private class PrivateProperty
+        {
+            private string Foo { get; set; }
+
+            [AffinityKeyMapped]
+            private string Abc { get; set; }
+        }
+
+        private class PublicField
+        {
+            public string Foo;
+
+            [AffinityKeyMapped]
+            public string Abc;
+        }
+
+        private class PrivateField
+        {
+            private string _foo;
+
+            [AffinityKeyMapped]
+            private string _abc;
+        }
+
+        private class InheritPublicProperty : PublicProperty
+        {
+            // No-op.
+        }
+
+        private class InheritPrivateProperty : PrivateProperty
+        {
+            // No-op.
+        }
+
+        private class InheritPublicField : PublicField
+        {
+            // No-op.
+        }
+
+        private class InheritPrivateField : PrivateField
+        {
+            // No-op.
+        }
+
+        private class MultipleAttributes : PublicProperty
+        {
+            [AffinityKeyMapped]
+            public int Baz { get; set; }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityFieldTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityFieldTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityFieldTest.cs
index c3482bb..d10f1a1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityFieldTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityFieldTest.cs
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
 {
     using System;
     using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
     using System.Linq;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
@@ -50,7 +51,12 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
 
             _cache1 = grid1.CreateCache<object, string>(new CacheConfiguration("default")
             {
-                CacheMode = CacheMode.Partitioned
+                CacheMode = CacheMode.Partitioned,
+                KeyConfiguration = new[]
+                {
+                    new CacheKeyConfiguration(typeof(CacheKey2)) {AffinityKeyFieldName = "AffinityKey"},
+                    new CacheKeyConfiguration{TypeName = "Baz", AffinityKeyFieldName = "Bar"}
+                }
             });
             _cache2 = grid2.GetCache<object, string>("default");
         }
@@ -74,6 +80,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
             _cache1.Put(new CacheKey(), string.Empty);
             _cache1.Put(new CacheKeyAttr(), string.Empty);
             _cache1.Put(new CacheKeyAttrOverride(), string.Empty);
+            _cache1.Put(new CacheKey2(), string.Empty);
 
             // Verify
             foreach (var type in new[] { typeof(CacheKey), typeof(CacheKeyAttr), 
@@ -85,6 +92,25 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
         }
 
         /// <summary>
+        /// Tests the cache configuration.
+        /// </summary>
+        [Test]
+        public void TestConfiguration()
+        {
+            var cfg = _cache1.GetConfiguration();
+            var keys = cfg.KeyConfiguration;
+            
+            Assert.IsNotNull(keys);
+            Assert.AreEqual(2, keys.Count);
+
+            Assert.AreEqual(typeof(CacheKey2).FullName, keys.First().TypeName);
+            Assert.AreEqual("AffinityKey", keys.First().AffinityKeyFieldName);
+
+            Assert.AreEqual("Baz", keys.Last().TypeName);
+            Assert.AreEqual("Bar", keys.Last().AffinityKeyFieldName);
+        }
+
+        /// <summary>
         /// Tests that keys are located properly in cache partitions.
         /// </summary>
         [Test]
@@ -203,5 +229,12 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
             [AffinityKeyMapped] public int Key { get; set; }
             public int AffinityKey { get; set; }
         }
+
+        [SuppressMessage("ReSharper", "UnusedMember.Local")]
+        private class CacheKey2
+        {
+            public int Key { get; set; }
+            public int AffinityKey2 { get; set; }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
index 4f13172..5556807 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
@@ -329,6 +329,8 @@ namespace Apache.Ignite.Core.Tests.Cache
                 Assert.AreEqual(x.PluginConfigurations.Select(p => p.GetType()),
                     y.PluginConfigurations.Select(p => p.GetType()));
             }
+
+            TestUtils.AssertReflectionEqual(x.KeyConfiguration, y.KeyConfiguration);
         }
 
         /// <summary>
@@ -635,7 +637,15 @@ namespace Apache.Ignite.Core.Tests.Cache
 #pragma warning restore 618
                 PartitionLossPolicy = PartitionLossPolicy.ReadOnlySafe,
                 PluginConfigurations = new[] { new MyPluginConfiguration() },
-                SqlIndexMaxInlineSize = 10000
+                SqlIndexMaxInlineSize = 10000,
+                KeyConfiguration = new[]
+                {
+                    new CacheKeyConfiguration
+                    {
+                        TypeName = "foobar",
+                        AffinityKeyFieldName = "barbaz"
+                    }
+                }
             };
         }
         /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
index 1e17752..7ad5d48 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/full-config.xml
@@ -78,6 +78,9 @@
             <pluginConfigurations>
                 <iCachePluginConfiguration type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+MyPluginConfiguration, Apache.Ignite.Core.Tests' />
             </pluginConfigurations>
+            <keyConfiguration>
+                <cacheKeyConfiguration typeName='foo' affinityKeyFieldName='bar' />
+            </keyConfiguration>
         </cacheConfiguration>
         <cacheConfiguration name='secondCache' />
     </cacheConfiguration>

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
index 72c73e4..4be34c7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
@@ -111,6 +111,9 @@ namespace Apache.Ignite.Core.Tests
             Assert.IsFalse(cacheCfg.WriteBehindCoalescing);
             Assert.AreEqual(PartitionLossPolicy.ReadWriteAll, cacheCfg.PartitionLossPolicy);
             Assert.AreEqual("fooGroup", cacheCfg.GroupName);
+            
+            Assert.AreEqual("bar", cacheCfg.KeyConfiguration.Single().AffinityKeyFieldName);
+            Assert.AreEqual("foo", cacheCfg.KeyConfiguration.Single().TypeName);
 
             var queryEntity = cacheCfg.QueryEntities.Single();
             Assert.AreEqual(typeof(int), queryEntity.KeyType);
@@ -722,7 +725,15 @@ namespace Apache.Ignite.Core.Tests
                         MemoryPolicyName = "somePolicy",
                         PartitionLossPolicy = PartitionLossPolicy.ReadOnlyAll,
                         GroupName = "abc",
-                        SqlIndexMaxInlineSize = 24
+                        SqlIndexMaxInlineSize = 24,
+                        KeyConfiguration = new[]
+                        {
+                            new CacheKeyConfiguration
+                            {
+                                AffinityKeyFieldName = "abc",
+                                TypeName = "def"
+                            }, 
+                        }
                     }
                 },
                 ClientMode = true,

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
index 34e356b..28e7ae8 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestUtils.cs
@@ -410,8 +410,31 @@ namespace Apache.Ignite.Core.Tests
         public static void AssertReflectionEqual(object x, object y, string propertyPath = null,
             HashSet<string> ignoredProperties = null)
         {
+            if (x == null && y == null)
+            {
+                return;
+            }
+
+            Assert.IsNotNull(x, propertyPath);
+            Assert.IsNotNull(y, propertyPath);
+
             var type = x.GetType();
 
+            if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
+            {
+                var xCol = ((IEnumerable)x).OfType<object>().ToList();
+                var yCol = ((IEnumerable)y).OfType<object>().ToList();
+
+                Assert.AreEqual(xCol.Count, yCol.Count, propertyPath);
+
+                for (var i = 0; i < xCol.Count; i++)
+                {
+                    AssertReflectionEqual(xCol[i], yCol[i], propertyPath, ignoredProperties);
+                }
+
+                return;
+            }
+
             Assert.AreEqual(type, y.GetType());
 
             propertyPath = propertyPath ?? type.Name;
@@ -426,8 +449,6 @@ namespace Apache.Ignite.Core.Tests
 
             foreach (var propInfo in props)
             {
-                var propType = propInfo.PropertyType;
-
                 if (ignoredProperties != null && ignoredProperties.Contains(propInfo.Name))
                 {
                     continue;
@@ -438,29 +459,7 @@ namespace Apache.Ignite.Core.Tests
                 var xVal = propInfo.GetValue(x, null);
                 var yVal = propInfo.GetValue(y, null);
 
-                if (xVal == null || yVal == null)
-                {
-                    Assert.IsNull(xVal, propName);
-                    Assert.IsNull(yVal, propName);
-                }
-                else if (propType != typeof(string) && propType.IsGenericType &&
-                         (propType.GetGenericTypeDefinition() == typeof(ICollection<>) ||
-                          propType.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
-                {
-                    var xCol = ((IEnumerable)xVal).OfType<object>().ToList();
-                    var yCol = ((IEnumerable)yVal).OfType<object>().ToList();
-
-                    Assert.AreEqual(xCol.Count, yCol.Count, propName);
-
-                    for (var i = 0; i < xCol.Count; i++)
-                    {
-                        AssertReflectionEqual(xCol[i], yCol[i], propName, ignoredProperties);
-                    }
-                }
-                else
-                {
-                    AssertReflectionEqual(xVal, yVal, propName, ignoredProperties);
-                }
+                AssertReflectionEqual(xVal, yVal, propName, ignoredProperties);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index 20a54d0..4e73d15 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -93,6 +93,7 @@
   <ItemGroup>
     <Compile Include="Binary\BinaryBasicNameMapper.cs" />
     <Compile Include="Binary\TimestampAttribute.cs" />
+    <Compile Include="Cache\Configuration\CacheKeyConfiguration.cs" />
     <Compile Include="Cache\Configuration\DataPageEvictionMode.cs" />
     <Compile Include="Configuration\CheckpointWriteOrder.cs" />
     <Compile Include="Configuration\DataPageEvictionMode.cs" />
@@ -113,6 +114,7 @@
     <Compile Include="IDataStorageMetrics.cs" />
     <Compile Include="Configuration\WalMode.cs" />
     <Compile Include="Impl\Binary\BinaryTypeId.cs" />
+    <Compile Include="Impl\Binary\IBinaryRawWriteAware.cs" />
     <Compile Include="Impl\Client\Cache\CacheFlags.cs" />
     <Compile Include="Impl\Client\Cache\Query\ClientQueryCursor.cs" />
     <Compile Include="Impl\Cache\Query\PlatformQueryQursorBase.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/AffinityKeyMappedAttribute.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/AffinityKeyMappedAttribute.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/AffinityKeyMappedAttribute.cs
index 1bfda9a..3b58471 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/AffinityKeyMappedAttribute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Affinity/AffinityKeyMappedAttribute.cs
@@ -18,7 +18,11 @@
 namespace Apache.Ignite.Core.Cache.Affinity
 {
     using System;
+    using System.Diagnostics;
+    using System.Linq;
+    using System.Reflection;
     using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Impl.Binary;
 
     /// <summary>
     /// Specifies cache key field to be used to determine a node on which given cache key will be stored.
@@ -41,6 +45,26 @@ namespace Apache.Ignite.Core.Cache.Affinity
     [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
     public sealed class AffinityKeyMappedAttribute : Attribute
     {
-        // No-op.
+        /// <summary>
+        /// Gets the affinity key field name from attribute.
+        /// </summary>
+        public static string GetFieldNameFromAttribute(Type type)
+        {
+            Debug.Assert(type != null);
+
+            var res = ReflectionUtils.GetFieldsAndProperties(type)
+                .Select(x => x.Key)
+                .Where(x => x.GetCustomAttributes(false).OfType<AffinityKeyMappedAttribute>().Any())
+                .Select(x => x.Name).ToArray();
+
+            if (res.Length > 1)
+            {
+                throw new BinaryObjectException(string.Format(
+                    "Multiple '{0}' attributes found on type '{1}'. There can be only one affinity field.",
+                    typeof(AffinityKeyMappedAttribute).Name, type));
+            }
+
+            return res.SingleOrDefault();
+        }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
index e7252b2..f4af778 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheConfiguration.cs
@@ -28,6 +28,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
     using System.IO;
     using System.Linq;
     using System.Xml.Serialization;
+    using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
     using Apache.Ignite.Core.Cache.Affinity;
     using Apache.Ignite.Core.Cache.Affinity.Rendezvous;
@@ -48,7 +49,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
     /// <summary>
     /// Defines grid cache configuration.
     /// </summary>
-    public class CacheConfiguration
+    public class CacheConfiguration : IBinaryRawWriteAware<BinaryWriter>
     {
         /// <summary> Default size of rebalance thread pool. </summary>
         public const int DefaultRebalanceThreadPoolSize = 2;
@@ -294,10 +295,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
             CacheStoreFactory = reader.ReadObject<IFactory<ICacheStore>>();
             SqlIndexMaxInlineSize = reader.ReadInt();
 
-            var count = reader.ReadInt();
-            QueryEntities = count == 0
-                ? null
-                : Enumerable.Range(0, count).Select(x => new QueryEntity(reader)).ToList();
+            QueryEntities = reader.ReadCollectionRaw(r => new QueryEntity(r));
 
             NearConfiguration = reader.ReadBoolean() ? new NearCacheConfiguration(reader) : null;
 
@@ -305,7 +303,9 @@ namespace Apache.Ignite.Core.Cache.Configuration
             AffinityFunction = AffinityFunctionSerializer.Read(reader);
             ExpiryPolicyFactory = ExpiryPolicySerializer.ReadPolicyFactory(reader);
 
-            count = reader.ReadInt();
+            KeyConfiguration = reader.ReadCollectionRaw(r => new CacheKeyConfiguration(r));
+
+            var count = reader.ReadInt();
 
             if (count > 0)
             {
@@ -332,6 +332,15 @@ namespace Apache.Ignite.Core.Cache.Configuration
         /// Writes this instance to the specified writer.
         /// </summary>
         /// <param name="writer">The writer.</param>
+        void IBinaryRawWriteAware<BinaryWriter>.Write(BinaryWriter writer)
+        {
+            Write(writer);
+        }
+
+        /// <summary>
+        /// Writes this instance to the specified writer.
+        /// </summary>
+        /// <param name="writer">The writer.</param>
         internal void Write(BinaryWriter writer)
         {
             // Make sure system marshaller is used.
@@ -374,20 +383,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
             writer.WriteObject(CacheStoreFactory);
             writer.WriteInt(SqlIndexMaxInlineSize);
 
-            if (QueryEntities != null)
-            {
-                writer.WriteInt(QueryEntities.Count);
-
-                foreach (var entity in QueryEntities)
-                {
-                    if (entity == null)
-                        throw new InvalidOperationException("Invalid cache configuration: QueryEntity can't be null.");
-
-                    entity.Write(writer);
-                }
-            }
-            else
-                writer.WriteInt(0);
+            writer.WriteCollectionRaw(QueryEntities);
 
             if (NearConfiguration != null)
             {
@@ -401,6 +397,8 @@ namespace Apache.Ignite.Core.Cache.Configuration
             AffinityFunctionSerializer.Write(writer, AffinityFunction);
             ExpiryPolicySerializer.WritePolicyFactory(writer, ExpiryPolicyFactory);
 
+            writer.WriteCollectionRaw(KeyConfiguration);
+
             if (PluginConfigurations != null)
             {
                 writer.WriteInt(PluginConfigurations.Count);
@@ -795,5 +793,11 @@ namespace Apache.Ignite.Core.Cache.Configuration
         /// </summary>
         [DefaultValue(DefaultSqlIndexMaxInlineSize)]
         public int SqlIndexMaxInlineSize { get; set; }
+
+        /// <summary>
+        /// Gets or sets the key configuration.
+        /// </summary>
+        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
+        public ICollection<CacheKeyConfiguration> KeyConfiguration { get; set; }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheKeyConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheKeyConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheKeyConfiguration.cs
new file mode 100644
index 0000000..efe31d9
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/CacheKeyConfiguration.cs
@@ -0,0 +1,84 @@
+/*
+ * 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.Cache.Configuration
+{
+    using System;
+    using System.Diagnostics;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Affinity;
+    using Apache.Ignite.Core.Impl.Binary;
+    using Apache.Ignite.Core.Impl.Common;
+
+    /// <summary>
+    /// Configuration defining various aspects of cache keys without explicit usage of annotations on user classes.
+    /// </summary>
+    public sealed class CacheKeyConfiguration : IBinaryRawWriteAware
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CacheKeyConfiguration"/> class.
+        /// </summary>
+        public CacheKeyConfiguration()
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CacheKeyConfiguration"/> class, using specified type to look
+        /// for <see cref="AffinityKeyMappedAttribute"/>.
+        /// </summary>
+        public CacheKeyConfiguration(Type keyType)
+        {
+            IgniteArgumentCheck.NotNull(keyType, "keyType");
+
+            TypeName = keyType.FullName;
+            AffinityKeyFieldName = AffinityKeyMappedAttribute.GetFieldNameFromAttribute(keyType);
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the key type.
+        /// </summary>
+        public string TypeName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the affinity key field.
+        /// See also <see cref="AffinityKeyMappedAttribute"/>, 
+        /// <see cref="BinaryTypeConfiguration.AffinityKeyFieldName"/>.
+        /// </summary>
+        public string AffinityKeyFieldName { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CacheKeyConfiguration"/> class.
+        /// </summary>
+        internal CacheKeyConfiguration(IBinaryRawReader reader)
+        {
+            Debug.Assert(reader != null);
+
+            TypeName = reader.ReadString();
+            AffinityKeyFieldName = reader.ReadString();
+        }
+
+        /// <summary>
+        /// Writes this object to the given writer.
+        /// </summary>
+        void IBinaryRawWriteAware<IBinaryRawWriter>.Write(IBinaryRawWriter writer)
+        {
+            writer.WriteString(TypeName);
+            writer.WriteString(AffinityKeyFieldName);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
index e8d0c91..eb509a0 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
@@ -24,7 +24,6 @@ namespace Apache.Ignite.Core.Cache.Configuration
     using System.Diagnostics;
     using System.Diagnostics.CodeAnalysis;
     using System.Linq;
-    using System.Reflection;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Cache;
@@ -34,7 +33,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
     /// Query entity is a description of cache entry (composed of key and value) 
     /// in a way of how it must be indexed and can be queried.
     /// </summary>
-    public sealed class QueryEntity : IQueryEntityInternal
+    public sealed class QueryEntity : IQueryEntityInternal, IBinaryRawWriteAware
     {
         /** */
         private Type _keyType;
@@ -258,7 +257,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
         /// <summary>
         /// Writes this instance.
         /// </summary>
-        internal void Write(IBinaryRawWriter writer)
+        void IBinaryRawWriteAware<IBinaryRawWriter>.Write(IBinaryRawWriter writer)
         {
             writer.WriteString(KeyTypeName);
             writer.WriteString(ValueTypeName);
@@ -434,7 +433,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
 
             visitedTypes.Add(type);
 
-            foreach (var memberInfo in GetFieldsAndProperties(type))
+            foreach (var memberInfo in ReflectionUtils.GetFieldsAndProperties(type))
             {
                 var customAttributes = memberInfo.Key.GetCustomAttributes(true);
 
@@ -487,33 +486,6 @@ namespace Apache.Ignite.Core.Cache.Configuration
         }
 
         /// <summary>
-        /// Gets the fields and properties.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns></returns>
-        private static IEnumerable<KeyValuePair<MemberInfo, Type>> GetFieldsAndProperties(Type type)
-        {
-            Debug.Assert(type != null);
-
-            if (type.IsPrimitive)
-                yield break;
-
-            const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
-                                              BindingFlags.DeclaredOnly;
-
-            while (type != typeof (object) && type != null)
-            {
-                foreach (var fieldInfo in type.GetFields(bindingFlags))
-                    yield return new KeyValuePair<MemberInfo, Type>(fieldInfo, fieldInfo.FieldType);
-
-                foreach (var propertyInfo in type.GetProperties(bindingFlags))
-                    yield return new KeyValuePair<MemberInfo, Type>(propertyInfo, propertyInfo.PropertyType);
-
-                type = type.BaseType;
-            }
-        }
-
-        /// <summary>
         /// Extended index with group names.
         /// </summary>
         private class QueryIndexEx : QueryIndex

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
index a6ff324..e890577 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs
@@ -302,17 +302,7 @@ namespace Apache.Ignite.Core
             writer.WriteIntNullable(_queryThreadPoolSize);
 
             // Cache config
-            var caches = CacheConfiguration;
-
-            if (caches == null)
-                writer.WriteInt(0);
-            else
-            {
-                writer.WriteInt(caches.Count);
-
-                foreach (var cache in caches)
-                    cache.Write(writer);
-            }
+            writer.WriteCollectionRaw(CacheConfiguration);
 
             // Discovery config
             var disco = DiscoverySpi;
@@ -623,10 +613,7 @@ namespace Apache.Ignite.Core
             _queryThreadPoolSize = r.ReadIntNullable();
 
             // Cache config
-            var cacheCfgCount = r.ReadInt();
-            CacheConfiguration = new List<CacheConfiguration>(cacheCfgCount);
-            for (int i = 0; i < cacheCfgCount; i++)
-                CacheConfiguration.Add(new CacheConfiguration(r));
+            CacheConfiguration = r.ReadCollectionRaw(x => new CacheConfiguration(x));
 
             // Discovery config
             DiscoverySpi = r.ReadBoolean() ? new TcpDiscoverySpi(r) : null;

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
index 6ede267..82eb465 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
@@ -575,6 +575,32 @@
                                                 </xs:attribute>
                                             </xs:complexType>
                                         </xs:element>
+                                        <xs:element name="keyConfiguration" minOccurs="0">
+                                            <xs:annotation>
+                                                <xs:documentation>Cache key configuration collection.</xs:documentation>
+                                            </xs:annotation>
+                                            <xs:complexType>
+                                                <xs:sequence>
+                                                    <xs:element name="cacheKeyConfiguration" maxOccurs="unbounded">
+                                                        <xs:annotation>
+                                                            <xs:documentation>Cache key configuration.</xs:documentation>
+                                                        </xs:annotation>
+                                                        <xs:complexType>
+                                                            <xs:attribute name="typeName" type="xs:string" use="required">
+                                                                <xs:annotation>
+                                                                    <xs:documentation>Key type name.</xs:documentation>
+                                                                </xs:annotation>
+                                                            </xs:attribute>
+                                                            <xs:attribute name="affinityKeyFieldName" type="xs:string" use="required">
+                                                                <xs:annotation>
+                                                                    <xs:documentation>Affinity key field name.</xs:documentation>
+                                                                </xs:annotation>
+                                                            </xs:attribute>
+                                                        </xs:complexType>
+                                                    </xs:element>
+                                                </xs:sequence>
+                                            </xs:complexType>
+                                        </xs:element>
                                         <xs:element name="pluginConfigurations" minOccurs="0">
                                             <xs:annotation>
                                                 <xs:documentation>Cache plugin configurations.</xs:documentation>

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderExtensions.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderExtensions.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderExtensions.cs
index da87d21..9be06c5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderExtensions.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 {
     using System;
     using System.Collections.Generic;
+    using System.Diagnostics;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Impl.Common;
 
@@ -97,5 +98,31 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             return obj is T ? (T) obj : ((ObjectInfoHolder) obj).CreateInstance<T>();
         }
+
+        /// <summary>
+        /// Reads the collection.
+        /// </summary>
+        public static ICollection<T> ReadCollectionRaw<T, TReader>(this TReader reader,
+            Func<TReader, T> factory) where TReader : IBinaryRawReader
+        {
+            Debug.Assert(reader != null);
+            Debug.Assert(factory != null);
+
+            int count = reader.ReadInt();
+
+            if (count <= 0)
+            {
+                return null;
+            }
+
+            var res = new List<T>(count);
+
+            for (var i = 0; i < count; i++)
+            {
+                res.Add(factory(reader));
+            }
+
+            return res;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
index 139783d..5233db8 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
@@ -23,10 +23,12 @@ namespace Apache.Ignite.Core.Impl.Binary
     using System.Diagnostics;
     using System.Diagnostics.CodeAnalysis;
     using System.IO;
+    using System.Linq;
     using System.Reflection;
     using System.Runtime.InteropServices;
     using System.Text;
     using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Affinity;
     using Apache.Ignite.Core.Impl.Binary.IO;
     using Apache.Ignite.Core.Impl.Common;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriterExtensions.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriterExtensions.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriterExtensions.cs
index f75fcf8..d87d217 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriterExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriterExtensions.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 {
     using System;
     using System.Collections.Generic;
+    using System.Diagnostics;
     using System.IO;
     using Apache.Ignite.Core.Binary;
 
@@ -182,5 +183,33 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             writer.Stream.WriteInt(pos, cnt);
         }
+
+        /// <summary>
+        /// Writes the collection of write-aware items.
+        /// </summary>
+        public static void WriteCollectionRaw<T, TWriter>(this TWriter writer, ICollection<T> collection)
+            where T : IBinaryRawWriteAware<TWriter> where TWriter: IBinaryRawWriter
+        {
+            Debug.Assert(writer != null);
+
+            if (collection != null)
+            {
+                writer.WriteInt(collection.Count);
+
+                foreach (var x in collection)
+                {
+                    if (x == null)
+                    {
+                        throw new ArgumentNullException(string.Format("{0} can not be null", typeof(T).Name));
+                    }
+
+                    x.Write(writer);
+                }
+            }
+            else
+            {
+                writer.WriteInt(0);
+            }
+        }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryRawWriteAware.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryRawWriteAware.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryRawWriteAware.cs
new file mode 100644
index 0000000..9b191e4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/IBinaryRawWriteAware.cs
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Binary
+{
+    using Apache.Ignite.Core.Binary;
+
+    /// <summary>
+    /// Represents an object that can write itself to a raw binary writer.
+    /// </summary>
+    internal interface IBinaryRawWriteAware<in T> where T : IBinaryRawWriter
+    {
+        /// <summary>
+        /// Writes this object to the given writer.
+        /// </summary> 
+        /// <param name="writer">Writer.</param>
+        /// <exception cref="System.IO.IOException">If write failed.</exception>
+        void Write(T writer);
+    }
+
+    /// <summary>
+    /// Represents an object that can write itself to a raw binary writer.
+    /// </summary>
+    internal interface IBinaryRawWriteAware : IBinaryRawWriteAware<IBinaryRawWriter>
+    {
+        // No-op.
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
index e68aa0b..55b6121 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -505,7 +505,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             desc = desc == null
                 ? new BinaryFullTypeDescriptor(type, typeId, typeName, true, _cfg.NameMapper,
-                    _cfg.IdMapper, ser, false, GetAffinityKeyFieldNameFromAttribute(type), 
+                    _cfg.IdMapper, ser, false, AffinityKeyMappedAttribute.GetFieldNameFromAttribute(type), 
                     BinaryUtils.IsIgniteEnum(type), registered)
                 : new BinaryFullTypeDescriptor(desc, type, ser, registered);
 
@@ -576,7 +576,8 @@ namespace Apache.Ignite.Core.Impl.Binary
                 // Type is found.
                 var typeName = GetTypeName(type, nameMapper);
                 int typeId = GetTypeId(typeName, idMapper);
-                var affKeyFld = typeCfg.AffinityKeyFieldName ?? GetAffinityKeyFieldNameFromAttribute(type);
+                var affKeyFld = typeCfg.AffinityKeyFieldName 
+                    ?? AffinityKeyMappedAttribute.GetFieldNameFromAttribute(type);
                 var serializer = GetSerializer(_cfg, typeCfg, type, typeId, nameMapper, idMapper, _log);
 
                 return AddType(type, typeId, typeName, true, keepDeserialized, nameMapper, idMapper, serializer,
@@ -627,24 +628,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
-        /// Gets the affinity key field name from attribute.
-        /// </summary>
-        private static string GetAffinityKeyFieldNameFromAttribute(Type type)
-        {
-            var res = type.GetMembers()
-                .Where(x => x.GetCustomAttributes(false).OfType<AffinityKeyMappedAttribute>().Any())
-                .Select(x => x.Name).ToArray();
-
-            if (res.Length > 1)
-            {
-                throw new BinaryObjectException(string.Format("Multiple '{0}' attributes found on type '{1}'. " +
-                    "There can be only one affinity field.", typeof (AffinityKeyMappedAttribute).Name, type));
-            }
-
-            return res.SingleOrDefault();
-        }
-
-        /// <summary>
         /// Add type.
         /// </summary>
         /// <param name="type">Type.</param>

http://git-wip-us.apache.org/repos/asf/ignite/blob/5b3ad97b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs
index 50c51a7..9f004ae 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 {
     using System;
     using System.Collections.Generic;
+    using System.Diagnostics;
     using System.Reflection;
 
     /// <summary>
@@ -46,5 +47,31 @@ namespace Apache.Ignite.Core.Impl.Binary
                 curType = curType.BaseType;
             }
         }
+
+        /// <summary>
+        /// Gets all fields and properties, including base classes.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        public static IEnumerable<KeyValuePair<MemberInfo, Type>> GetFieldsAndProperties(Type type)
+        {
+            Debug.Assert(type != null);
+
+            if (type.IsPrimitive)
+                yield break;
+
+            const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
+                                              BindingFlags.DeclaredOnly;
+
+            while (type != typeof(object) && type != null)
+            {
+                foreach (var fieldInfo in type.GetFields(bindingFlags))
+                    yield return new KeyValuePair<MemberInfo, Type>(fieldInfo, fieldInfo.FieldType);
+
+                foreach (var propertyInfo in type.GetProperties(bindingFlags))
+                    yield return new KeyValuePair<MemberInfo, Type>(propertyInfo, propertyInfo.PropertyType);
+
+                type = type.BaseType;
+            }
+        }
     }
 }