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 2017/01/16 14:48:24 UTC

[2/2] ignite git commit: IGNITE-4045 .NET: Support DML API

IGNITE-4045 .NET: Support DML API

This closes #1309


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

Branch: refs/heads/master
Commit: b7908d7a65f07615f2ff183e107c5002658bd511
Parents: d6d42c2
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Mon Jan 16 17:48:08 2017 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Mon Jan 16 17:48:08 2017 +0300

----------------------------------------------------------------------
 .../utils/PlatformConfigurationUtils.java       | 128 +++++++-
 .../Apache.Ignite.Core.Tests.csproj             |   3 +
 .../Binary/BinaryBuilderSelfTest.cs             | 159 ++++++----
 .../BinaryBuilderSelfTestArrayIdentity.cs       |  34 +++
 .../Binary/BinaryEqualityComparerTest.cs        | 279 +++++++++++++++++
 .../Binary/IO/BinaryStreamsTest.cs              |  19 ++
 .../Cache/CacheConfigurationTest.cs             |   5 +-
 .../Cache/Query/CacheDmlQueriesTest.cs          | 296 +++++++++++++++++++
 .../IgniteConfigurationSerializerTest.cs        |  46 ++-
 .../IgniteConfigurationTest.cs                  |  28 ++
 .../Apache.Ignite.Core.csproj                   |   4 +
 .../Binary/BinaryArrayEqualityComparer.cs       | 149 ++++++++++
 .../Binary/BinaryConfiguration.cs               |  24 ++
 .../Binary/BinaryTypeConfiguration.cs           |  14 +
 .../Cache/Configuration/QueryEntity.cs          |  33 ++-
 .../Cache/Configuration/QueryField.cs           |   6 +
 .../Apache.Ignite.Core/IgniteConfiguration.cs   |  85 ++++--
 .../IgniteConfigurationSection.xsd              |  19 ++
 .../Apache.Ignite.Core/Impl/Binary/Binary.cs    |  28 +-
 .../Binary/BinaryEqualityComparerSerializer.cs  |  99 +++++++
 .../Impl/Binary/BinaryFieldEqualityComparer.cs  | 138 +++++++++
 .../Impl/Binary/BinaryFullTypeDescriptor.cs     |  21 +-
 .../Impl/Binary/BinaryObject.cs                 |  31 +-
 .../Impl/Binary/BinaryObjectBuilder.cs          |  62 +++-
 .../Impl/Binary/BinaryObjectHeader.cs           |  21 +-
 .../Impl/Binary/BinaryObjectSchemaHolder.cs     |  22 ++
 .../Binary/BinarySurrogateTypeDescriptor.cs     |   6 +
 .../Impl/Binary/BinarySystemHandlers.cs         |   6 +-
 .../Impl/Binary/BinaryWriter.cs                 |  11 +-
 .../Impl/Binary/DateTimeHolder.cs               |  15 +-
 .../Impl/Binary/IBinaryEqualityComparer.cs      |  53 ++++
 .../Impl/Binary/IBinaryTypeDescriptor.cs        |   5 +
 .../Impl/Binary/Io/BinaryHeapStream.cs          |   9 +
 .../Impl/Binary/Io/BinaryStreamBase.cs          |  13 +
 .../Impl/Binary/Io/IBinaryStream.cs             |  25 ++
 .../Impl/Binary/Marshaller.cs                   |  22 +-
 .../Impl/Binary/SerializableObjectHolder.cs     |  16 +
 .../Common/IgniteConfigurationXmlSerializer.cs  |   5 +-
 .../Impl/Memory/PlatformMemoryStream.cs         |  16 +
 39 files changed, 1803 insertions(+), 152 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 f845675..c0fde97 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
@@ -17,8 +17,12 @@
 
 package org.apache.ignite.internal.processors.platform.utils;
 
+import org.apache.ignite.binary.BinaryArrayIdentityResolver;
+import org.apache.ignite.binary.BinaryFieldIdentityResolver;
+import org.apache.ignite.binary.BinaryIdentityResolver;
 import org.apache.ignite.binary.BinaryRawReader;
 import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryTypeConfiguration;
 import org.apache.ignite.cache.CacheAtomicWriteOrderMode;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.CacheMemoryMode;
@@ -68,6 +72,8 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
 
 /**
  * Configuration utils.
@@ -428,14 +434,25 @@ public class PlatformConfigurationUtils {
 
         // Fields
         int cnt = in.readInt();
+        Set<String> keyFields = new HashSet<>(cnt);
 
         if (cnt > 0) {
             LinkedHashMap<String, String> fields = new LinkedHashMap<>(cnt);
 
-            for (int i = 0; i < cnt; i++)
-                fields.put(in.readString(), in.readString());
+            for (int i = 0; i < cnt; i++) {
+                String fieldName = in.readString();
+                String fieldType = in.readString();
+
+                fields.put(fieldName, fieldType);
+
+                if (in.readBoolean())
+                    keyFields.add(fieldName);
+            }
 
             res.setFields(fields);
+
+            if (!keyFields.isEmpty())
+                res.setKeyFields(keyFields);
         }
 
         // Aliases
@@ -539,11 +556,29 @@ public class PlatformConfigurationUtils {
             cfg.setCommunicationSpi(comm);
         }
 
-        if (in.readBoolean()) {
+        if (in.readBoolean()) {  // binary config is present
             if (cfg.getBinaryConfiguration() == null)
                 cfg.setBinaryConfiguration(new BinaryConfiguration());
 
-            cfg.getBinaryConfiguration().setCompactFooter(in.readBoolean());
+            if (in.readBoolean())  // compact footer is set
+                cfg.getBinaryConfiguration().setCompactFooter(in.readBoolean());
+
+            int typeCnt = in.readInt();
+
+            if (typeCnt > 0) {
+                Collection<BinaryTypeConfiguration> types = new ArrayList<>(typeCnt);
+
+                for (int i = 0; i < typeCnt; i++) {
+                    BinaryTypeConfiguration type = new BinaryTypeConfiguration(in.readString());
+
+                    type.setEnum(in.readBoolean());
+                    type.setIdentityResolver(readBinaryIdentityResolver(in));
+
+                    types.add(type);
+                }
+
+                cfg.getBinaryConfiguration().setTypeConfigurations(types);
+            }
         }
 
         int attrCnt = in.readInt();
@@ -812,11 +847,14 @@ public class PlatformConfigurationUtils {
         LinkedHashMap<String, String> fields = queryEntity.getFields();
 
         if (fields != null) {
+            Set<String> keyFields = queryEntity.getKeyFields();
+
             writer.writeInt(fields.size());
 
             for (Map.Entry<String, String> field : fields.entrySet()) {
                 writer.writeString(field.getKey());
                 writer.writeString(field.getValue());
+                writer.writeBoolean(keyFields != null && keyFields.contains(field.getKey()));
             }
         }
         else
@@ -941,11 +979,29 @@ public class PlatformConfigurationUtils {
             w.writeBoolean(false);
 
         BinaryConfiguration bc = cfg.getBinaryConfiguration();
-        w.writeBoolean(bc != null);
 
-        if (bc != null)
+        if (bc != null) {
+            w.writeBoolean(true);  // binary config exists
+            w.writeBoolean(true);  // compact footer is set
             w.writeBoolean(bc.isCompactFooter());
 
+            Collection<BinaryTypeConfiguration> types = bc.getTypeConfigurations();
+
+            if (types != null) {
+                w.writeInt(types.size());
+
+                for (BinaryTypeConfiguration type : types) {
+                    w.writeString(type.getTypeName());
+                    w.writeBoolean(type.isEnum());
+                    writeBinaryIdentityResolver(w, type.getIdentityResolver());
+                }
+            }
+            else
+                w.writeInt(0);
+        }
+        else
+            w.writeBoolean(false);
+
         Map<String, ?> attrs = cfg.getUserAttributes();
 
         if (attrs != null) {
@@ -1117,6 +1173,66 @@ public class PlatformConfigurationUtils {
     }
 
     /**
+     * Reads resolver
+     *
+     * @param r Reader.
+     * @return Resolver.
+     */
+    private static BinaryIdentityResolver readBinaryIdentityResolver(BinaryRawReader r) {
+        int type = r.readByte();
+
+        switch (type) {
+            case 0:
+                return null;
+
+            case 1:
+                return new BinaryArrayIdentityResolver();
+
+            case 2:
+                int cnt = r.readInt();
+
+                String[] fields = new String[cnt];
+
+                for (int i = 0; i < cnt; i++)
+                    fields[i] = r.readString();
+
+                return new BinaryFieldIdentityResolver().setFieldNames(fields);
+
+            default:
+                assert false;
+                return null;
+        }
+    }
+
+    /**
+     * Writes the resolver.
+     *
+     * @param w Writer.
+     * @param resolver Resolver.
+     */
+    private static void writeBinaryIdentityResolver(BinaryRawWriter w, BinaryIdentityResolver resolver) {
+        if (resolver instanceof BinaryArrayIdentityResolver)
+            w.writeByte((byte)1);
+        else if (resolver instanceof BinaryFieldIdentityResolver) {
+            w.writeByte((byte)2);
+
+            String[] fields = ((BinaryFieldIdentityResolver)resolver).getFieldNames();
+
+            if (fields != null) {
+                w.writeInt(fields.length);
+
+                for (String field : fields)
+                    w.writeString(field);
+            }
+            else
+                w.writeInt(0);
+        }
+        else {
+            w.writeByte((byte)0);
+        }
+    }
+
+    /**
      * Private constructor.
      */
     private PlatformConfigurationUtils() {

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 78a08d2..e09c682 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
@@ -67,6 +67,8 @@
     <Reference Include="System.Xml.Linq" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Binary\BinaryBuilderSelfTestArrayIdentity.cs" />
+    <Compile Include="Binary\BinaryEqualityComparerTest.cs" />
     <Compile Include="Binary\BinaryReaderWriterTest.cs" />
     <Compile Include="Binary\IO\BinaryStreamsTest.cs" />
     <Compile Include="Binary\JavaTypeMappingTest.cs" />
@@ -76,6 +78,7 @@
     <Compile Include="Cache\CacheMetricsTest.cs" />
     <Compile Include="Cache\CacheResultTest.cs" />
     <Compile Include="Cache\CacheSwapSpaceTest.cs" />
+    <Compile Include="Cache\Query\CacheDmlQueriesTest.cs" />
     <Compile Include="Cache\CacheAbstractTransactionalTest.cs" />
     <Compile Include="Cache\Store\CacheStoreAdapterTest.cs" />
     <Compile Include="Collections\MultiValueDictionaryTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTest.cs
index c280255..d6551b5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTest.cs
@@ -58,33 +58,57 @@ namespace Apache.Ignite.Core.Tests.Binary
                 {
                     TypeConfigurations = new List<BinaryTypeConfiguration>
                     {
-                        new BinaryTypeConfiguration(typeof (Empty)),
-                        new BinaryTypeConfiguration(typeof (Primitives)),
-                        new BinaryTypeConfiguration(typeof (PrimitiveArrays)),
-                        new BinaryTypeConfiguration(typeof (StringDateGuidEnum)),
-                        new BinaryTypeConfiguration(typeof (WithRaw)),
-                        new BinaryTypeConfiguration(typeof (MetaOverwrite)),
-                        new BinaryTypeConfiguration(typeof (NestedOuter)),
-                        new BinaryTypeConfiguration(typeof (NestedInner)),
-                        new BinaryTypeConfiguration(typeof (MigrationOuter)),
-                        new BinaryTypeConfiguration(typeof (MigrationInner)),
-                        new BinaryTypeConfiguration(typeof (InversionOuter)),
-                        new BinaryTypeConfiguration(typeof (InversionInner)),
-                        new BinaryTypeConfiguration(typeof (CompositeOuter)),
-                        new BinaryTypeConfiguration(typeof (CompositeInner)),
-                        new BinaryTypeConfiguration(typeof (CompositeArray)),
-                        new BinaryTypeConfiguration(typeof (CompositeContainer)),
-                        new BinaryTypeConfiguration(typeof (ToBinary)),
-                        new BinaryTypeConfiguration(typeof (Remove)),
-                        new BinaryTypeConfiguration(typeof (RemoveInner)),
-                        new BinaryTypeConfiguration(typeof (BuilderInBuilderOuter)),
-                        new BinaryTypeConfiguration(typeof (BuilderInBuilderInner)),
-                        new BinaryTypeConfiguration(typeof (BuilderCollection)),
-                        new BinaryTypeConfiguration(typeof (BuilderCollectionItem)),
-                        new BinaryTypeConfiguration(typeof (DecimalHolder)),
-                        new BinaryTypeConfiguration(TypeEmpty),
-                        new BinaryTypeConfiguration(typeof(TestEnumRegistered)),
+                        new BinaryTypeConfiguration(typeof(Empty)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(Primitives)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(PrimitiveArrays)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(StringDateGuidEnum))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(WithRaw)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(MetaOverwrite)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(NestedOuter)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(NestedInner)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(MigrationOuter)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(MigrationInner)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(InversionOuter)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(InversionInner)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(CompositeOuter)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(CompositeInner)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(CompositeArray)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(CompositeContainer))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(ToBinary)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(Remove)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(RemoveInner)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(BuilderInBuilderOuter))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(BuilderInBuilderInner))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(BuilderCollection))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(BuilderCollectionItem))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
+                        new BinaryTypeConfiguration(typeof(DecimalHolder)) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(TypeEmpty) {EqualityComparer = GetIdentityResolver()},
+                        new BinaryTypeConfiguration(typeof(TestEnumRegistered))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        },
                         new BinaryTypeConfiguration(typeof(NameMapperTestType))
+                        {
+                            EqualityComparer = GetIdentityResolver()
+                        }
                     },
                     DefaultIdMapper = new IdMapper(),
                     DefaultNameMapper = new NameMapper(),
@@ -106,6 +130,14 @@ namespace Apache.Ignite.Core.Tests.Binary
         }
 
         /// <summary>
+        /// Gets the identity resolver.
+        /// </summary>
+        protected virtual IEqualityComparer<IBinaryObject> GetIdentityResolver()
+        {
+            return null;
+        }
+
+        /// <summary>
         /// Tear down routine.
         /// </summary>
         [TestFixtureTearDown]
@@ -535,7 +567,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             IBinaryObject binObj = _grid.GetBinary().GetBuilder(typeof(Empty)).Build();
 
             Assert.IsNotNull(binObj);
-            Assert.AreEqual(0, binObj.GetHashCode());
+            Assert.AreEqual(GetIdentityResolver() == null ? 0 : 1, binObj.GetHashCode());
 
             IBinaryType meta = binObj.GetBinaryType();
 
@@ -557,7 +589,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             IBinaryObject binObj = _grid.GetBinary().GetBuilder(TypeEmpty).Build();
 
             Assert.IsNotNull(binObj);
-            Assert.AreEqual(0, binObj.GetHashCode());
+            Assert.AreEqual(GetIdentityResolver() == null ? 0 : 1, binObj.GetHashCode());
 
             IBinaryType meta = binObj.GetBinaryType();
 
@@ -602,7 +634,9 @@ namespace Apache.Ignite.Core.Tests.Binary
             var obj2 = bin.GetBuilder("myType").SetStringField("str", "foo").SetIntField("int", 1).Build();
 
             Assert.AreEqual(obj1, obj2);
-            Assert.AreEqual(obj1.GetHashCode(), obj2.GetHashCode());
+
+            Assert.AreEqual(0, obj1.GetHashCode());
+            Assert.AreEqual(0, obj2.GetHashCode());
 
             Assert.IsTrue(Regex.IsMatch(obj1.ToString(), @"myType \[idHash=[0-9]+, str=foo, int=1\]"));
         }
@@ -630,7 +664,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             CheckPrimitiveFields1(binObj);
 
             // Specific setter methods.
-            binObj = _grid.GetBinary().GetBuilder(typeof(Primitives))
+            var binObj2 = _grid.GetBinary().GetBuilder(typeof(Primitives))
                 .SetByteField("fByte", 1)
                 .SetBooleanField("fBool", true)
                 .SetShortField("fShort", 2)
@@ -643,7 +677,11 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetHashCode(100)
                 .Build();
 
-            CheckPrimitiveFields1(binObj);
+            CheckPrimitiveFields1(binObj2);
+
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
 
             // Overwrite with generic methods.
             binObj = binObj.ToBuilder()
@@ -656,13 +694,12 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetField<float>("fFloat", 11)
                 .SetField<double>("fDouble", 12)
                 .SetField("fDecimal", 13.13m)
-                .SetHashCode(200)
                 .Build();
 
             CheckPrimitiveFields2(binObj);
 
             // Overwrite with specific methods.
-            binObj = binObj.ToBuilder()
+            binObj2 = binObj.ToBuilder()
                 .SetByteField("fByte", 7)
                 .SetBooleanField("fBool", false)
                 .SetShortField("fShort", 8)
@@ -672,10 +709,13 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetFloatField("fFloat", 11)
                 .SetDoubleField("fDouble", 12)
                 .SetDecimalField("fDecimal", 13.13m)
-                .SetHashCode(200)
                 .Build();
 
             CheckPrimitiveFields2(binObj);
+
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
         }
 
         /// <summary>
@@ -729,8 +769,6 @@ namespace Apache.Ignite.Core.Tests.Binary
         /// </summary>
         private static void CheckPrimitiveFields2(IBinaryObject binObj)
         {
-            Assert.AreEqual(200, binObj.GetHashCode());
-
             Assert.AreEqual(7, binObj.GetField<byte>("fByte"));
             Assert.AreEqual(false, binObj.GetField<bool>("fBool"));
             Assert.AreEqual(8, binObj.GetField<short>("fShort"));
@@ -777,7 +815,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             CheckPrimitiveArrayFields1(binObj);
 
             // Specific setters.
-            binObj = _grid.GetBinary().GetBuilder(typeof(PrimitiveArrays))
+            var binObj2 = _grid.GetBinary().GetBuilder(typeof(PrimitiveArrays))
                 .SetByteArrayField("fByte", new byte[] {1})
                 .SetBooleanArrayField("fBool", new[] {true})
                 .SetShortArrayField("fShort", new short[] {2})
@@ -789,7 +827,13 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetDecimalArrayField("fDecimal", new decimal?[] {7.7m})
                 .SetHashCode(100)
                 .Build();
-            
+
+            CheckPrimitiveArrayFields1(binObj2);
+
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
+
             // Overwrite with generic setter.
             binObj = _grid.GetBinary().GetBuilder(binObj)
                 .SetField("fByte", new byte[] { 7 })
@@ -801,13 +845,12 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetField("fFloat", new float[] { 11 })
                 .SetField("fDouble", new double[] { 12 })
                 .SetField("fDecimal", new decimal?[] { 13.13m })
-                .SetHashCode(200)
                 .Build();
 
             CheckPrimitiveArrayFields2(binObj);
 
             // Overwrite with specific setters.
-            binObj = _grid.GetBinary().GetBuilder(binObj)
+            binObj2 = _grid.GetBinary().GetBuilder(binObj)
                 .SetByteArrayField("fByte", new byte[] { 7 })
                 .SetBooleanArrayField("fBool", new[] { false })
                 .SetShortArrayField("fShort", new short[] { 8 })
@@ -817,10 +860,13 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetFloatArrayField("fFloat", new float[] { 11 })
                 .SetDoubleArrayField("fDouble", new double[] { 12 })
                 .SetDecimalArrayField("fDecimal", new decimal?[] { 13.13m })
-                .SetHashCode(200)
                 .Build();
 
             CheckPrimitiveArrayFields2(binObj);
+            
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
         }
 
         /// <summary>
@@ -874,8 +920,6 @@ namespace Apache.Ignite.Core.Tests.Binary
         /// </summary>
         private static void CheckPrimitiveArrayFields2(IBinaryObject binObj)
         {
-            Assert.AreEqual(200, binObj.GetHashCode());
-
             Assert.AreEqual(new byte[] { 7 }, binObj.GetField<byte[]>("fByte"));
             Assert.AreEqual(new[] { false }, binObj.GetField<bool[]>("fBool"));
             Assert.AreEqual(new short[] { 8 }, binObj.GetField<short[]>("fShort"));
@@ -910,7 +954,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             Guid? nGuid = Guid.NewGuid();
 
             // Generic setters.
-            IBinaryObject binObj = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
+            var binObj = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
                 .SetField("fStr", "str")
                 .SetField("fNDate", nDate)
                 .SetTimestampField("fNTimestamp", nDate)
@@ -927,7 +971,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             CheckStringDateGuidEnum1(binObj, nDate, nGuid);
 
             // Specific setters.
-            binObj = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
+            var binObj2 = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
                 .SetStringField("fStr", "str")
                 .SetField("fNDate", nDate)
                 .SetTimestampField("fNTimestamp", nDate)
@@ -941,7 +985,11 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetHashCode(100)
                 .Build();
 
-            CheckStringDateGuidEnum1(binObj, nDate, nGuid);
+            CheckStringDateGuidEnum1(binObj2, nDate, nGuid);
+
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
 
             // Overwrite.
             nDate = DateTime.Now.ToUniversalTime();
@@ -958,13 +1006,12 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetTimestampArrayField("fTimestampArr", new[] { nDate })
                 .SetField("fGuidArr", new[] { nGuid })
                 .SetField("fEnumArr", new[] { TestEnum.Two })
-                .SetHashCode(200)
                 .Build();
 
             CheckStringDateGuidEnum2(binObj, nDate, nGuid);
 
             // Overwrite with specific setters
-            binObj = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
+            binObj2 = _grid.GetBinary().GetBuilder(typeof(StringDateGuidEnum))
                 .SetStringField("fStr", "str2")
                 .SetField("fNDate", nDate)
                 .SetTimestampField("fNTimestamp", nDate)
@@ -975,10 +1022,13 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .SetTimestampArrayField("fTimestampArr", new[] { nDate })
                 .SetGuidArrayField("fGuidArr", new[] { nGuid })
                 .SetEnumArrayField("fEnumArr", new[] { TestEnum.Two })
-                .SetHashCode(200)
                 .Build();
 
-            CheckStringDateGuidEnum2(binObj, nDate, nGuid);
+            CheckStringDateGuidEnum2(binObj2, nDate, nGuid);
+
+            // Check equality.
+            Assert.AreEqual(binObj, binObj2);
+            Assert.AreEqual(binObj.GetHashCode(), binObj2.GetHashCode());
         }
 
         /// <summary>
@@ -1074,8 +1124,6 @@ namespace Apache.Ignite.Core.Tests.Binary
         /// </summary>
         private static void CheckStringDateGuidEnum2(IBinaryObject binObj, DateTime? nDate, Guid? nGuid)
         {
-            Assert.AreEqual(200, binObj.GetHashCode());
-
             Assert.AreEqual("str2", binObj.GetField<string>("fStr"));
             Assert.AreEqual(nDate, binObj.GetField<DateTime?>("fNDate"));
             Assert.AreEqual(nDate, binObj.GetField<DateTime?>("fNTimestamp"));
@@ -1432,7 +1480,8 @@ namespace Apache.Ignite.Core.Tests.Binary
 
             IBinaryObjectBuilder builder = _grid.GetBinary().GetBuilder(typeof(MigrationOuter));
 
-            builder.SetHashCode(outer.GetHashCode());
+            if (GetIdentityResolver() == null)
+                builder.SetHashCode(outer.GetHashCode());
 
             builder.SetField<object>("inner1", inner);
             builder.SetField<object>("inner2", inner);
@@ -1642,6 +1691,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             }
 
             Assert.AreEqual(binEnums[0], binEnums[1]);
+            Assert.AreEqual(binEnums[0].GetHashCode(), binEnums[1].GetHashCode());
         }
 
         /// <summary>
@@ -1659,6 +1709,9 @@ namespace Apache.Ignite.Core.Tests.Binary
         [Test]
         public void TestRemoteBinaryMode()
         {
+            if (GetIdentityResolver() != null)
+                return;  // When identity resolver is set, it is required to have the same config on all nodes.
+
             var cfg = new IgniteConfiguration(TestUtils.GetTestConfiguration())
             {
                 GridName = "grid2",

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTestArrayIdentity.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTestArrayIdentity.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTestArrayIdentity.cs
new file mode 100644
index 0000000..b5e767c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryBuilderSelfTestArrayIdentity.cs
@@ -0,0 +1,34 @@
+/*
+ * 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
+{
+    using System.Collections.Generic;
+    using Apache.Ignite.Core.Binary;
+
+    /// <summary>
+    /// Tests with array equality comparer (identity resolver).
+    /// </summary>
+    public class BinaryBuilderSelfTestArrayIdentity : BinaryBuilderSelfTest
+    {
+        /** <inheritdoc /> */
+        protected override IEqualityComparer<IBinaryObject> GetIdentityResolver()
+        {
+            return new BinaryArrayEqualityComparer();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryEqualityComparerTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryEqualityComparerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryEqualityComparerTest.cs
new file mode 100644
index 0000000..f0550a8
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryEqualityComparerTest.cs
@@ -0,0 +1,279 @@
+\ufeff/*
+ * 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
+{
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Impl.Binary;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Equality comparers test.
+    /// </summary>
+    public class BinaryEqualityComparerTest
+    {
+        /// <summary>
+        /// Tests common public methods logic.
+        /// </summary>
+        [Test]
+        [SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")]
+        public void TestPublicMethods()
+        {
+            var cmps = new IEqualityComparer<IBinaryObject>[]
+            {
+                new BinaryArrayEqualityComparer()
+                //new BinaryFieldEqualityComparer()
+            };
+
+            var obj = GetBinaryObject(1, "x", 0);
+
+            foreach (var cmp in cmps)
+            {
+                Assert.IsTrue(cmp.Equals(null, null));
+                Assert.IsTrue(cmp.Equals(obj, obj));
+
+                Assert.IsFalse(cmp.Equals(obj, null));
+                Assert.IsFalse(cmp.Equals(null, obj));
+
+                Assert.AreEqual(0, cmp.GetHashCode(null));
+                Assert.AreNotEqual(0, cmp.GetHashCode(obj));
+            }
+        }
+
+        /// <summary>
+        /// Tests the custom comparer.
+        /// </summary>
+        [Test]
+        public void TestCustomComparer()
+        {
+            var ex = Assert.Throws<IgniteException>(() => Ignition.Start(
+                new IgniteConfiguration(TestUtils.GetTestConfiguration())
+                {
+                    BinaryConfiguration = new BinaryConfiguration
+                    {
+                        TypeConfigurations = new[]
+                        {
+                            new BinaryTypeConfiguration(typeof(Foo))
+                            {
+                                EqualityComparer = new MyComparer()
+                            }
+                        }
+                    }
+                }));
+
+            Assert.AreEqual("Unsupported IEqualityComparer<IBinaryObject> implementation: " +
+                            "Apache.Ignite.Core.Tests.Binary.BinaryEqualityComparerTest+MyComparer. " +
+                            "Only predefined implementations are supported.", ex.Message);
+        }
+
+        /// <summary>
+        /// Tests the array comparer.
+        /// </summary>
+        [Test]
+        public void TestArrayComparer()
+        {
+            var cmp = (IBinaryEqualityComparer) new BinaryArrayEqualityComparer();
+
+            var ms = new BinaryHeapStream(10);
+
+            Assert.AreEqual(1, cmp.GetHashCode(ms, 0, 0, null, 0, null, null));
+
+            ms.WriteByte(1);
+            Assert.AreEqual(31 + 1, cmp.GetHashCode(ms, 0, 1, null, 0, null, null));
+
+            ms.WriteByte(3);
+            Assert.AreEqual((31 + 1) * 31 + 3, cmp.GetHashCode(ms, 0, 2, null, 0, null, null));
+        }
+
+        /// <summary>
+        /// Tests public methods of array comparer.
+        /// </summary>
+        [Test]
+        public void TestArrayComparerPublic()
+        {
+            var cmp = new BinaryArrayEqualityComparer();
+
+            var obj1 = GetBinaryObject(1, "foo", 11);
+            var obj2 = GetBinaryObject(1, "bar", 11);
+            var obj3 = GetBinaryObject(2, "foo", 11);
+            var obj4 = GetBinaryObject(2, "bar", 11);
+            var obj5 = GetBinaryObject(1, "foo", 11);
+            var obj6 = GetBinaryObject(1, "foo", 12);
+
+            // Equals.
+            Assert.IsTrue(cmp.Equals(obj1, obj1));
+            Assert.IsTrue(cmp.Equals(obj1, obj5));
+            Assert.IsFalse(cmp.Equals(obj1, obj2));
+            Assert.IsFalse(cmp.Equals(obj1, obj3));
+            Assert.IsFalse(cmp.Equals(obj1, obj4));
+            Assert.IsFalse(cmp.Equals(obj1, obj6));
+
+            Assert.IsTrue(cmp.Equals(obj2, obj2));
+            Assert.IsFalse(cmp.Equals(obj2, obj5));
+            Assert.IsFalse(cmp.Equals(obj2, obj3));
+            Assert.IsFalse(cmp.Equals(obj2, obj4));
+            Assert.IsFalse(cmp.Equals(obj2, obj6));
+
+            Assert.IsTrue(cmp.Equals(obj3, obj3));
+            Assert.IsFalse(cmp.Equals(obj3, obj5));
+            Assert.IsFalse(cmp.Equals(obj3, obj4));
+            Assert.IsFalse(cmp.Equals(obj3, obj6));
+
+            Assert.IsTrue(cmp.Equals(obj4, obj4));
+            Assert.IsFalse(cmp.Equals(obj4, obj5));
+            Assert.IsFalse(cmp.Equals(obj4, obj6));
+
+            Assert.IsTrue(cmp.Equals(obj5, obj5));
+            Assert.IsFalse(cmp.Equals(obj5, obj6));
+
+            // BinaryObject.GetHashCode.
+            Assert.AreEqual(1934949494, obj1.GetHashCode());
+            Assert.AreEqual(-2013102781, obj2.GetHashCode());
+            Assert.AreEqual(1424415317, obj3.GetHashCode());
+            Assert.AreEqual(1771330338, obj4.GetHashCode());
+            Assert.AreEqual(obj1.GetHashCode(), obj5.GetHashCode());
+            Assert.AreEqual(1934979285, cmp.GetHashCode(obj6));
+
+            // Comparer.GetHashCode.
+            Assert.AreEqual(2001751043, cmp.GetHashCode(GetBinaryObject(0, null, 0)));
+            Assert.AreEqual(194296580, cmp.GetHashCode(GetBinaryObject(1, null, 0)));
+            Assert.AreEqual(1934949494, cmp.GetHashCode(obj1));
+            Assert.AreEqual(-2013102781, cmp.GetHashCode(obj2));
+            Assert.AreEqual(1424415317, cmp.GetHashCode(obj3));
+            Assert.AreEqual(1771330338, cmp.GetHashCode(obj4));
+            Assert.AreEqual(cmp.GetHashCode(obj1), cmp.GetHashCode(obj5));
+            Assert.AreEqual(1934979285, cmp.GetHashCode(obj6));
+
+            // GetHashCode consistency.
+            foreach (var obj in new[] {obj1, obj2, obj3, obj4, obj5, obj6})
+                Assert.AreEqual(obj.GetHashCode(), cmp.GetHashCode(obj));
+        }
+
+        /// <summary>
+        /// Tests the field comparer.
+        /// </summary>
+        [Test]
+        public void TestFieldComparer()
+        {
+            var marsh = new Marshaller(new BinaryConfiguration
+            {
+                TypeConfigurations = new[]
+                {
+                    new BinaryTypeConfiguration(typeof(Foo))
+                    {
+                        EqualityComparer = new BinaryFieldEqualityComparer("Name", "Id")
+                    }
+                }
+            });
+
+            var val = new Foo {Id = 58, Name = "John"};
+            var binObj = marsh.Unmarshal<IBinaryObject>(marsh.Marshal(val), BinaryMode.ForceBinary);
+            var expHash = val.Name.GetHashCode() * 31 + val.Id.GetHashCode();
+            Assert.AreEqual(expHash, binObj.GetHashCode());
+
+            val = new Foo {Id = 95};
+            binObj = marsh.Unmarshal<IBinaryObject>(marsh.Marshal(val), BinaryMode.ForceBinary);
+            expHash = val.Id.GetHashCode();
+            Assert.AreEqual(expHash, binObj.GetHashCode());
+        }
+
+        /// <summary>
+        /// Tests the field comparer validation.
+        /// </summary>
+        [Test]
+        public void TestFieldComparerValidation()
+        {
+            var ex = Assert.Throws<IgniteException>(() => Ignition.Start(
+                new IgniteConfiguration(TestUtils.GetTestConfiguration())
+                {
+                    BinaryConfiguration = new BinaryConfiguration
+                    {
+                        TypeConfigurations = new[]
+                        {
+                            new BinaryTypeConfiguration(typeof(Foo))
+                            {
+                                EqualityComparer = new BinaryFieldEqualityComparer()
+                            }
+                        }
+                    }
+                }));
+
+            Assert.AreEqual("BinaryFieldEqualityComparer.FieldNames can not be null or empty.", ex.Message);
+        }
+
+        /// <summary>
+        /// Gets the binary object.
+        /// </summary>
+        private static IBinaryObject GetBinaryObject(int id, string name, int raw)
+        {
+            var marsh = new Marshaller(new BinaryConfiguration
+            {
+                TypeConfigurations = new[]
+                {
+                    new BinaryTypeConfiguration(typeof(Foo))
+                    {
+                        EqualityComparer = new BinaryArrayEqualityComparer()
+                    }
+                }
+            });
+
+            var bytes = marsh.Marshal(new Foo {Id = id, Name = name, Raw = raw});
+
+            return marsh.Unmarshal<IBinaryObject>(bytes, BinaryMode.ForceBinary);
+        }
+
+        private class Foo : IBinarizable
+        {
+            public int Id { get; set; }
+            public string Name { get; set; }
+            public int Raw { get; set; }
+
+            public void WriteBinary(IBinaryWriter writer)
+            {
+                writer.WriteInt("id", Id);
+                writer.WriteString("name", Name);
+
+                writer.GetRawWriter().WriteInt(Raw);
+            }
+
+            public void ReadBinary(IBinaryReader reader)
+            {
+                Id = reader.ReadInt("id");
+                Name = reader.ReadString("name");
+
+                Raw = reader.GetRawReader().ReadInt();
+            }
+        }
+
+        private class MyComparer : IEqualityComparer<IBinaryObject>
+        {
+            public bool Equals(IBinaryObject x, IBinaryObject y)
+            {
+                return true;
+            }
+
+            public int GetHashCode(IBinaryObject obj)
+            {
+                return 0;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/IO/BinaryStreamsTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/IO/BinaryStreamsTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/IO/BinaryStreamsTest.cs
index ad5358d..1ebe906 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/IO/BinaryStreamsTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/IO/BinaryStreamsTest.cs
@@ -94,6 +94,12 @@ namespace Apache.Ignite.Core.Tests.Binary.IO
 
             stream.Write(bytes, 2);
             Assert.AreEqual(2, stream.Position);
+
+            var proc = new SumStreamProcessor();
+            Assert.AreEqual(0, stream.Apply(proc, 0));
+            Assert.AreEqual(1, stream.Apply(proc, 1));
+            Assert.AreEqual(3, stream.Apply(proc, 2));
+
             flush();
 
             seek();
@@ -147,5 +153,18 @@ namespace Apache.Ignite.Core.Tests.Binary.IO
             check(() => stream.WriteShort(4), () => stream.ReadShort(), (short)4);
             check(() => stream.WriteShortArray(new short[] {4}), () => stream.ReadShortArray(1), new short[] {4});
         }
+
+        private class SumStreamProcessor : IBinaryStreamProcessor<int, int>
+        {
+            public unsafe int Invoke(byte* data, int arg)
+            {
+                int res = 0;
+
+                for (var i = 0; i < arg; i++)
+                    res += *(data + i);
+
+                return res;
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 9d55160..fb8725c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs
@@ -447,6 +447,7 @@ namespace Apache.Ignite.Core.Tests.Cache
 
             Assert.AreEqual(x.Name, y.Name);
             Assert.AreEqual(x.FieldTypeName, y.FieldTypeName);
+            Assert.AreEqual(x.IsKeyField, y.IsKeyField);
         }
 
         /// <summary>
@@ -528,7 +529,7 @@ namespace Apache.Ignite.Core.Tests.Cache
                         Fields = new[]
                         {
                             new QueryField("length", typeof(int)), 
-                            new QueryField("name", typeof(string)), 
+                            new QueryField("name", typeof(string)) {IsKeyField = true}, 
                             new QueryField("location", typeof(string)),
                         },
                         Aliases = new [] {new QueryAlias("length", "len") },
@@ -624,7 +625,7 @@ namespace Apache.Ignite.Core.Tests.Cache
                         {
                             new QueryField("length", typeof(int)), 
                             new QueryField("name", typeof(string)), 
-                            new QueryField("location", typeof(string)),
+                            new QueryField("location", typeof(string)) {IsKeyField = true}
                         },
                         Aliases = new [] {new QueryAlias("length", "len") },
                         Indexes = new[]

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
new file mode 100644
index 0000000..c460252
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
@@ -0,0 +1,296 @@
+\ufeff/*
+ * 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.
+ */
+
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+namespace Apache.Ignite.Core.Tests.Cache.Query
+{
+    using System.Linq;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Impl.Binary;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests Data Manipulation Language queries.
+    /// </summary>
+    public class CacheDmlQueriesTest
+    {
+        /// <summary>
+        /// Sets up test fixture.
+        /// </summary>
+        [TestFixtureSetUp]
+        public void FixtureSetUp()
+        {
+            var cfg = new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                BinaryConfiguration = new BinaryConfiguration(typeof(Foo))
+                {
+                    TypeConfigurations =
+                    {
+                        new BinaryTypeConfiguration(typeof(Key))
+                        {
+                            EqualityComparer = new BinaryArrayEqualityComparer()
+                        },
+                        new BinaryTypeConfiguration(typeof(Key2))
+                        {
+                            EqualityComparer = new BinaryFieldEqualityComparer("Hi", "Lo")
+                        }
+                    }
+                }
+            };
+
+            Ignition.Start(cfg);
+        }
+
+        /// <summary>
+        /// Tears down test fixture.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void FixtureTearDown()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Tests primitive key.
+        /// </summary>
+        [Test]
+        public void TestPrimitiveKey()
+        {
+            var cfg = new CacheConfiguration("primitive_key", new QueryEntity(typeof(int), typeof(Foo)));
+            var cache = Ignition.GetIgnite().CreateCache<int, Foo>(cfg);
+
+            // Test insert.
+            var res = cache.QueryFields(new SqlFieldsQuery("insert into foo(_key, id, name) " +
+                                                           "values (?, ?, ?), (?, ?, ?)",
+                1, 2, "John", 3, 4, "Mary")).GetAll();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(1, res[0].Count);
+            Assert.AreEqual(2, res[0][0]);  // 2 affected rows
+
+            var foos = cache.OrderBy(x => x.Key).ToArray();
+
+            Assert.AreEqual(2, foos.Length);
+
+            Assert.AreEqual(1, foos[0].Key);
+            Assert.AreEqual(2, foos[0].Value.Id);
+            Assert.AreEqual("John", foos[0].Value.Name);
+
+            Assert.AreEqual(3, foos[1].Key);
+            Assert.AreEqual(4, foos[1].Value.Id);
+            Assert.AreEqual("Mary", foos[1].Value.Name);
+
+            // Test key existence.
+            Assert.IsTrue(cache.ContainsKey(1));
+            Assert.IsTrue(cache.ContainsKey(3));
+        }
+
+        /// <summary>
+        /// Tests composite key (which requires QueryField.IsKeyField).
+        /// </summary>
+        [Test]
+        public void TestCompositeKeyArrayEquality()
+        {
+            var cfg = new CacheConfiguration("composite_key_arr", new QueryEntity(typeof(Key), typeof(Foo)));
+            var cache = Ignition.GetIgnite().CreateCache<Key, Foo>(cfg);
+
+            // Test insert.
+            var res = cache.QueryFields(new SqlFieldsQuery("insert into foo(hi, lo, id, name) " +
+                                               "values (1, 2, 3, 'John'), (4, 5, 6, 'Mary')")).GetAll();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(1, res[0].Count);
+            Assert.AreEqual(2, res[0][0]);  // 2 affected rows
+
+            var foos = cache.OrderBy(x => x.Key.Lo).ToArray();
+
+            Assert.AreEqual(2, foos.Length);
+
+            Assert.AreEqual(1, foos[0].Key.Hi);
+            Assert.AreEqual(2, foos[0].Key.Lo);
+            Assert.AreEqual(3, foos[0].Value.Id);
+            Assert.AreEqual("John", foos[0].Value.Name);
+
+            Assert.AreEqual(4, foos[1].Key.Hi);
+            Assert.AreEqual(5, foos[1].Key.Lo);
+            Assert.AreEqual(6, foos[1].Value.Id);
+            Assert.AreEqual("Mary", foos[1].Value.Name);
+
+            // Existence tests check that hash codes are consistent.
+            var binary = cache.Ignite.GetBinary();
+            var binCache = cache.WithKeepBinary<IBinaryObject, IBinaryObject>();
+
+            Assert.IsTrue(cache.ContainsKey(new Key(2, 1)));
+            Assert.IsTrue(cache.ContainsKey(foos[0].Key));
+            Assert.IsTrue(binCache.ContainsKey(
+                binary.GetBuilder(typeof(Key)).SetField("hi", 1).SetField("lo", 2).Build()));
+
+            Assert.IsTrue(cache.ContainsKey(new Key(5, 4)));
+            Assert.IsTrue(cache.ContainsKey(foos[1].Key));
+            Assert.IsTrue(binCache.ContainsKey(
+                binary.GetBuilder(typeof(Key)).SetField("hi", 4).SetField("lo", 5).Build()));
+        }
+
+        /// <summary>
+        /// Tests composite key (which requires QueryField.IsKeyField).
+        /// </summary>
+        [Test]
+        public void TestCompositeKeyFieldEquality()
+        {
+            var cfg = new CacheConfiguration("composite_key_fld", new QueryEntity(typeof(Key2), typeof(Foo)));
+            var cache = Ignition.GetIgnite().CreateCache<Key2, Foo>(cfg);
+
+            // Test insert.
+            var res = cache.QueryFields(new SqlFieldsQuery("insert into foo(hi, lo, str, id, name) " +
+                                               "values (1, 2, 'Foo', 3, 'John'), (4, 5, 'Bar', 6, 'Mary')")).GetAll();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(1, res[0].Count);
+            Assert.AreEqual(2, res[0][0]);  // 2 affected rows
+
+            var foos = cache.OrderBy(x => x.Key.Lo).ToArray();
+
+            Assert.AreEqual(2, foos.Length);
+
+            Assert.AreEqual(1, foos[0].Key.Hi);
+            Assert.AreEqual(2, foos[0].Key.Lo);
+            Assert.AreEqual("Foo", foos[0].Key.Str);
+            Assert.AreEqual(3, foos[0].Value.Id);
+            Assert.AreEqual("John", foos[0].Value.Name);
+
+            Assert.AreEqual(4, foos[1].Key.Hi);
+            Assert.AreEqual(5, foos[1].Key.Lo);
+            Assert.AreEqual("Bar", foos[1].Key.Str);
+            Assert.AreEqual(6, foos[1].Value.Id);
+            Assert.AreEqual("Mary", foos[1].Value.Name);
+
+            // Existence tests check that hash codes are consistent.
+            Assert.IsTrue(cache.ContainsKey(new Key2(2, 1, "Foo")));
+            Assert.IsTrue(cache.ContainsKey(foos[0].Key));
+
+            Assert.IsTrue(cache.ContainsKey(new Key2(5, 4, "Bar")));
+            Assert.IsTrue(cache.ContainsKey(foos[1].Key));
+        }
+
+        /// <summary>
+        /// Tests the composite key without IsKeyField configuration.
+        /// </summary>
+        [Test]
+        public void TestInvalidCompositeKey()
+        {
+            var cfg = new CacheConfiguration("invalid_composite_key", new QueryEntity
+            {
+                KeyTypeName = "Key",
+                ValueTypeName = "Foo",
+                Fields = new[]
+                {
+                    new QueryField("Lo", typeof(int)),
+                    new QueryField("Hi", typeof(int)),
+                    new QueryField("Id", typeof(int)),
+                    new QueryField("Name", typeof(string))
+                }
+            });
+
+            var cache = Ignition.GetIgnite().CreateCache<Key, Foo>(cfg);
+
+            var ex = Assert.Throws<IgniteException>(
+                () => cache.QueryFields(new SqlFieldsQuery("insert into foo(lo, hi, id, name) " +
+                                                           "values (1, 2, 3, 'John'), (4, 5, 6, 'Mary')")));
+
+            Assert.AreEqual("Ownership flag not set for binary property. Have you set 'keyFields' " +
+                            "property of QueryEntity in programmatic or XML configuration?", ex.Message);
+        }
+
+        /// <summary>
+        /// Tests DML with pure binary cache mode, without classes.
+        /// </summary>
+        [Test]
+        public void TestBinaryMode()
+        {
+            var cfg = new CacheConfiguration("binary_only", new QueryEntity
+            {
+                KeyTypeName = "CarKey",
+                ValueTypeName = "Car",
+                Fields = new[]
+                {
+                    new QueryField("VIN", typeof(string)) {IsKeyField = true},
+                    new QueryField("Id", typeof(int)) {IsKeyField = true},
+                    new QueryField("Make", typeof(string)),
+                    new QueryField("Year", typeof(int))
+                }
+            });
+
+            var cache = Ignition.GetIgnite().CreateCache<object, object>(cfg)
+                .WithKeepBinary<IBinaryObject, IBinaryObject>();
+
+            var res = cache.QueryFields(new SqlFieldsQuery("insert into car(VIN, Id, Make, Year) " +
+                                                           "values ('DLRDMC', 88, 'DeLorean', 1982)")).GetAll();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(1, res[0].Count);
+            Assert.AreEqual(1, res[0][0]);
+
+            var car = cache.Single();
+            Assert.AreEqual("CarKey", car.Key.GetBinaryType().TypeName);
+            Assert.AreEqual("Car", car.Value.GetBinaryType().TypeName);
+        }
+
+        /// <summary>
+        /// Key.
+        /// </summary>
+        private struct Key
+        {
+            public Key(int lo, int hi) : this()
+            {
+                Lo = lo;
+                Hi = hi;
+            }
+
+            [QuerySqlField] public int Lo { get; private set; }
+            [QuerySqlField] public int Hi { get; private set; }
+        }
+
+        /// <summary>
+        /// Key.
+        /// </summary>
+        private struct Key2
+        {
+            public Key2(int lo, int hi, string str) : this()
+            {
+                Lo = lo;
+                Hi = hi;
+                Str = str;
+            }
+
+            [QuerySqlField] public int Lo { get; private set; }
+            [QuerySqlField] public int Hi { get; private set; }
+            [QuerySqlField] public string Str { get; private set; }
+        }
+
+        /// <summary>
+        /// Value.
+        /// </summary>
+        private class Foo
+        {
+            [QuerySqlField] public int Id { get; set; }
+            [QuerySqlField] public string Name { get; set; }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 55b8dcf..26e04a9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
@@ -45,6 +45,7 @@ namespace Apache.Ignite.Core.Tests
     using Apache.Ignite.Core.Discovery.Tcp;
     using Apache.Ignite.Core.Discovery.Tcp.Multicast;
     using Apache.Ignite.Core.Events;
+    using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Common;
     using Apache.Ignite.Core.Lifecycle;
     using Apache.Ignite.Core.Log;
@@ -72,6 +73,14 @@ namespace Apache.Ignite.Core.Tests
                                 <types>
                                     <string>Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+FooClass, Apache.Ignite.Core.Tests</string>
                                 </types>
+                                <typeConfigurations>
+                                    <binaryTypeConfiguration affinityKeyFieldName='affKeyFieldName' isEnum='true' keepDeserialized='True' typeName='typeName'>
+                                        <equalityComparer type='BinaryArrayEqualityComparer' />
+                                        <idMapper type='Apache.Ignite.Core.Tests.Binary.IdMapper, Apache.Ignite.Core.Tests' />
+                                        <nameMapper type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+NameMapper, Apache.Ignite.Core.Tests' />
+                                        <serializer type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+TestSerializer, Apache.Ignite.Core.Tests' />
+                                    </binaryTypeConfiguration>
+                                </typeConfigurations>
                             </binaryConfiguration>
                             <discoverySpi type='TcpDiscoverySpi' joinTimeout='0:1:0' localAddress='192.168.1.1' localPort='6655'>
                                 <ipFinder type='TcpDiscoveryMulticastIpFinder' addressRequestAttempts='7' />
@@ -86,7 +95,7 @@ namespace Apache.Ignite.Core.Tests
                                     <queryEntities>    
                                         <queryEntity keyType='System.Int32' valueType='System.String'>    
                                             <fields>
-                                                <queryField name='length' fieldType='System.Int32' />
+                                                <queryField name='length' fieldType='System.Int32' isKeyField='true' />
                                             </fields>
                                             <aliases>
                                                 <queryAlias fullName='somefield.field' alias='shortField' />
@@ -159,6 +168,7 @@ namespace Apache.Ignite.Core.Tests
             Assert.AreEqual(typeof(string), queryEntity.ValueType);
             Assert.AreEqual("length", queryEntity.Fields.Single().Name);
             Assert.AreEqual(typeof(int), queryEntity.Fields.Single().FieldType);
+            Assert.IsTrue(queryEntity.Fields.Single().IsKeyField);
             Assert.AreEqual("somefield.field", queryEntity.Aliases.Single().FullName);
             Assert.AreEqual("shortField", queryEntity.Aliases.Single().Alias);
             Assert.AreEqual(QueryIndexType.Geospatial, queryEntity.Indexes.Single().IndexType);
@@ -214,6 +224,16 @@ namespace Apache.Ignite.Core.Tests
             Assert.AreEqual(25, swap.MaximumWriteQueueSize);
             Assert.AreEqual(36, swap.ReadStripesNumber);
             Assert.AreEqual(47, swap.WriteBufferSize);
+
+            var binType = cfg.BinaryConfiguration.TypeConfigurations.Single();
+            Assert.AreEqual("typeName", binType.TypeName);
+            Assert.AreEqual("affKeyFieldName", binType.AffinityKeyFieldName);
+            Assert.IsTrue(binType.IsEnum);
+            Assert.AreEqual(true, binType.KeepDeserialized);
+            Assert.IsInstanceOf<BinaryArrayEqualityComparer>(binType.EqualityComparer);
+            Assert.IsInstanceOf<IdMapper>(binType.IdMapper);
+            Assert.IsInstanceOf<NameMapper>(binType.NameMapper);
+            Assert.IsInstanceOf<TestSerializer>(binType.Serializer);
         }
 
         /// <summary>
@@ -334,12 +354,12 @@ namespace Apache.Ignite.Core.Tests
             };
 
             Assert.AreEqual(FixLineEndings(@"<?xml version=""1.0"" encoding=""utf-16""?>
-<igniteConfiguration gridName=""myGrid"" clientMode=""true"" xmlns=""http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection"">
+<igniteConfiguration clientMode=""true"" gridName=""myGrid"" xmlns=""http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection"">
   <cacheConfiguration>
-    <cacheConfiguration name=""myCache"" cacheMode=""Replicated"">
+    <cacheConfiguration cacheMode=""Replicated"" name=""myCache"">
       <queryEntities>
-        <queryEntity valueTypeName=""java.lang.Integer"" valueType=""System.Int32"" />
-        <queryEntity keyTypeName=""java.lang.Integer"" keyType=""System.Int32"" valueTypeName=""java.lang.String"" valueType=""System.String"" />
+        <queryEntity valueType=""System.Int32"" valueTypeName=""java.lang.Integer"" />
+        <queryEntity keyType=""System.Int32"" keyTypeName=""java.lang.Integer"" valueType=""System.String"" valueTypeName=""java.lang.String"" />
       </queryEntities>
     </cacheConfiguration>
   </cacheConfiguration>
@@ -364,12 +384,12 @@ namespace Apache.Ignite.Core.Tests
             }
 
             Assert.AreEqual(FixLineEndings(@"<?xml version=""1.0"" encoding=""utf-16""?>
-<igCfg gridName=""myGrid"" clientMode=""true"" xmlns=""http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection"">
+<igCfg clientMode=""true"" gridName=""myGrid"" xmlns=""http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection"">
  <cacheConfiguration>
-  <cacheConfiguration name=""myCache"" cacheMode=""Replicated"">
+  <cacheConfiguration cacheMode=""Replicated"" name=""myCache"">
    <queryEntities>
-    <queryEntity valueTypeName=""java.lang.Integer"" valueType=""System.Int32"" />
-    <queryEntity keyTypeName=""java.lang.Integer"" keyType=""System.Int32"" valueTypeName=""java.lang.String"" valueType=""System.String"" />
+    <queryEntity valueType=""System.Int32"" valueTypeName=""java.lang.Integer"" />
+    <queryEntity keyType=""System.Int32"" keyTypeName=""java.lang.Integer"" valueType=""System.String"" valueTypeName=""java.lang.String"" />
    </queryEntities>
   </cacheConfiguration>
  </cacheConfiguration>
@@ -547,7 +567,8 @@ namespace Apache.Ignite.Core.Tests
                             TypeName = "typeName",
                             IdMapper = new IdMapper(),
                             NameMapper = new NameMapper(),
-                            Serializer = new TestSerializer()
+                            Serializer = new TestSerializer(),
+                            EqualityComparer = new BinaryArrayEqualityComparer()
                         },
                         new BinaryTypeConfiguration
                         {
@@ -555,7 +576,8 @@ namespace Apache.Ignite.Core.Tests
                             KeepDeserialized = false,
                             AffinityKeyFieldName = "affKeyFieldName",
                             TypeName = "typeName2",
-                            Serializer = new BinaryReflectiveSerializer()
+                            Serializer = new BinaryReflectiveSerializer(),
+                            EqualityComparer = new BinaryFieldEqualityComparer()
                         }
                     },
                     Types = new[] {typeof (string).FullName},
@@ -595,7 +617,7 @@ namespace Apache.Ignite.Core.Tests
                             {
                                 Fields = new[]
                                 {
-                                    new QueryField("field", typeof (int))
+                                    new QueryField("field", typeof (int)) { IsKeyField = true }
                                 },
                                 Indexes = new[]
                                 {

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationTest.cs
index 2e39b9b..86ece98 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationTest.cs
@@ -34,6 +34,7 @@ namespace Apache.Ignite.Core.Tests
     using Apache.Ignite.Core.Discovery.Tcp.Static;
     using Apache.Ignite.Core.Events;
     using Apache.Ignite.Core.Impl;
+    using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.SwapSpace.File;
     using Apache.Ignite.Core.Transactions;
     using NUnit.Framework;
@@ -181,6 +182,18 @@ namespace Apache.Ignite.Core.Tests
                 Assert.AreEqual(swap.MaximumWriteQueueSize, resSwap.MaximumWriteQueueSize);
                 Assert.AreEqual(swap.ReadStripesNumber, resSwap.ReadStripesNumber);
                 Assert.AreEqual(swap.WriteBufferSize, resSwap.WriteBufferSize);
+
+                var binCfg = cfg.BinaryConfiguration;
+                Assert.IsFalse(binCfg.CompactFooter);
+
+                var typ = binCfg.TypeConfigurations.Single();
+                Assert.AreEqual("myType", typ.TypeName);
+                Assert.IsTrue(typ.IsEnum);
+                Assert.AreEqual("affKey", typ.AffinityKeyFieldName);
+                Assert.AreEqual(false, typ.KeepDeserialized);
+
+                CollectionAssert.AreEqual(new[] {"fld1", "fld2"}, 
+                    ((BinaryFieldEqualityComparer)typ.EqualityComparer).FieldNames);
             }
         }
 
@@ -513,6 +526,21 @@ namespace Apache.Ignite.Core.Tests
                     WriteBufferSize = 9,
                     BaseDirectory = Path.GetTempPath(),
                     MaximumSparsity = 11.22f
+                },
+                BinaryConfiguration = new BinaryConfiguration
+                {
+                    CompactFooter = false,
+                    TypeConfigurations = new[]
+                    {
+                        new BinaryTypeConfiguration
+                        {
+                            TypeName = "myType",
+                            IsEnum = true,
+                            AffinityKeyFieldName = "affKey",
+                            KeepDeserialized = false,
+                            EqualityComparer = new BinaryFieldEqualityComparer("fld1", "fld2")
+                        }
+                    }
                 }
             };
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 a80dfc0..42ccdd4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -90,6 +90,8 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Binary\BinaryArrayEqualityComparer.cs" />
+    <Compile Include="Impl\Binary\BinaryFieldEqualityComparer.cs" />
     <Compile Include="Binary\BinaryReflectiveSerializer.cs" />
     <Compile Include="Common\JavaException.cs" />
     <Compile Include="DataStructures\Configuration\Package-Info.cs" />
@@ -97,8 +99,10 @@
     <Compile Include="Discovery\Tcp\Multicast\Package-Info.cs" />
     <Compile Include="Discovery\Tcp\Package-Info.cs" />
     <Compile Include="Discovery\Tcp\Static\Package-Info.cs" />
+    <Compile Include="Impl\Binary\BinaryEqualityComparerSerializer.cs" />
     <Compile Include="Impl\Binary\BinaryProcessor.cs" />
     <Compile Include="Impl\Binary\BinaryReflectiveSerializerInternal.cs" />
+    <Compile Include="Impl\Binary\IBinaryEqualityComparer.cs" />
     <Compile Include="Impl\Binary\IBinarySerializerInternal.cs" />
     <Compile Include="Binary\Package-Info.cs" />
     <Compile Include="Cache\Affinity\AffinityKey.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryArrayEqualityComparer.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryArrayEqualityComparer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryArrayEqualityComparer.cs
new file mode 100644
index 0000000..09f7f0f
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryArrayEqualityComparer.cs
@@ -0,0 +1,149 @@
+\ufeff/*
+ * 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.Binary
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using Apache.Ignite.Core.Impl.Binary;
+    using Apache.Ignite.Core.Impl.Binary.IO;
+
+    /// <summary>
+    /// Compares binary object equality using underlying byte array.
+    /// </summary>
+    public class BinaryArrayEqualityComparer : IEqualityComparer<IBinaryObject>, IBinaryEqualityComparer,
+        IBinaryStreamProcessor<KeyValuePair<int,int>, int>
+    {
+        /// <summary>
+        /// Determines whether the specified objects are equal.
+        /// </summary>
+        /// <param name="x">The first object to compare.</param>
+        /// <param name="y">The second object to compare.</param>
+        /// <returns>
+        /// true if the specified objects are equal; otherwise, false.
+        /// </returns>
+        public bool Equals(IBinaryObject x, IBinaryObject y)
+        {
+            if (x == null)
+                return y == null;
+
+            if (y == null)
+                return false;
+
+            if (ReferenceEquals(x, y))
+                return true;
+
+            var binx = GetBinaryObject(x);
+            var biny = GetBinaryObject(y);
+
+            var lenx = GetDataLength(binx);
+            var leny = GetDataLength(biny);
+
+            if (lenx != leny)
+                return false;
+
+            var startx = GetDataStart(binx);
+            var starty = GetDataStart(biny);
+
+            var arrx = binx.Data;
+            var arry = biny.Data;
+
+            for (var i = 0; i < lenx; i++)
+            {
+                if (arrx[i + startx] != arry[i + starty])
+                    return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Returns a hash code for this instance.
+        /// </summary>
+        /// <param name="obj">The object.</param>
+        /// <returns>
+        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
+        /// </returns>
+        public int GetHashCode(IBinaryObject obj)
+        {
+            if (obj == null)
+                return 0;
+
+            var binObj = GetBinaryObject(obj);
+
+            var arg = new KeyValuePair<int, int>(GetDataStart(binObj), GetDataLength(binObj));
+
+            return new BinaryHeapStream(binObj.Data).Apply(this, arg);
+        }
+
+        /** <inheritdoc /> */
+        int IBinaryEqualityComparer.GetHashCode(IBinaryStream stream, int startPos, int length, 
+            BinaryObjectSchemaHolder schema, int schemaId, Marshaller marshaller, IBinaryTypeDescriptor desc)
+        {
+            Debug.Assert(stream != null);
+            Debug.Assert(startPos >= 0);
+            Debug.Assert(length >= 0);
+
+            var arg = new KeyValuePair<int, int>(startPos, length);
+
+            return stream.Apply(this, arg);
+        }
+
+        /** <inheritdoc /> */
+        unsafe int IBinaryStreamProcessor<KeyValuePair<int, int>, int>.Invoke(byte* data, KeyValuePair<int, int> arg)
+        {
+            var hash = 1;
+            var ptr = data + arg.Key;
+
+            for (var i = 0; i < arg.Value; i++)
+                hash = 31 * hash + *(ptr + i);
+
+            return hash;
+        }
+
+        /// <summary>
+        /// Casts to <see cref="BinaryObject"/> or throws an error.
+        /// </summary>
+        private static BinaryObject GetBinaryObject(IBinaryObject obj)
+        {
+            var binObj = obj as BinaryObject;
+
+            if (binObj != null)
+                return binObj;
+
+            throw new NotSupportedException(string.Format("{0} of type {1} is not supported.",
+                typeof(IBinaryObject), obj.GetType()));
+        }
+
+        /// <summary>
+        /// Gets the non-raw data length.
+        /// </summary>
+        private static int GetDataLength(BinaryObject binObj)
+        {
+            return binObj.Header.FooterStartOffset - BinaryObjectHeader.Size;
+        }
+
+        /// <summary>
+        /// Gets the data starting position.
+        /// </summary>
+        private static int GetDataStart(BinaryObject binObj)
+        {
+            return binObj.Offset + BinaryObjectHeader.Size;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
index 51df907..c738f28 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
@@ -141,5 +141,29 @@ namespace Apache.Ignite.Core.Binary
         {
             get { return _compactFooter; }
         }
+
+        /// <summary>
+        /// Merges other config into this.
+        /// </summary>
+        internal void MergeTypes(BinaryConfiguration localConfig)
+        {
+            if (TypeConfigurations == null)
+            {
+                TypeConfigurations = localConfig.TypeConfigurations;
+            }
+            else if (localConfig.TypeConfigurations != null)
+            {
+                // Both configs are present.
+                // Local configuration is more complete and takes preference when it exists for a given type.
+                var localTypeNames = new HashSet<string>(localConfig.TypeConfigurations.Select(x => x.TypeName), 
+                    StringComparer.OrdinalIgnoreCase);
+
+                var configs = new List<BinaryTypeConfiguration>(localConfig.TypeConfigurations);
+
+                configs.AddRange(TypeConfigurations.Where(x=>!localTypeNames.Contains(x.TypeName)));
+
+                TypeConfigurations = configs;
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryTypeConfiguration.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryTypeConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryTypeConfiguration.cs
index c36b9fd..722197c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryTypeConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryTypeConfiguration.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Core.Binary
 {
     using System;
+    using System.Collections.Generic;
     using Apache.Ignite.Core.Impl.Common;
 
     /// <summary>
@@ -69,6 +70,7 @@ namespace Apache.Ignite.Core.Binary
             TypeName = cfg.TypeName;
             KeepDeserialized = cfg.KeepDeserialized;
             IsEnum = cfg.IsEnum;
+            EqualityComparer = cfg.EqualityComparer;
         }
 
         /// <summary>
@@ -113,6 +115,18 @@ namespace Apache.Ignite.Core.Binary
         public bool IsEnum { get; set; }
 
         /// <summary>
+        /// Gets or sets the equality comparer to compute hash codes and compare objects
+        /// in <see cref="IBinaryObject"/> form.
+        /// This comparer is important only for types that are used as cache keys.
+        /// <para />
+        /// Null means legacy behavior: hash code is computed by calling <see cref="object.GetHashCode"/>, equality is
+        /// computed by comparing bytes in serialized (binary) form.
+        /// <para />
+        /// Only predefined implementations are supported: <see cref="BinaryArrayEqualityComparer"/>.
+        /// </summary>
+        public IEqualityComparer<IBinaryObject> EqualityComparer { get; set; }
+
+        /// <summary>
         /// Returns a string that represents the current object.
         /// </summary>
         /// <returns>

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/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 adfe9e1..ff9af37 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
@@ -178,8 +178,11 @@ namespace Apache.Ignite.Core.Cache.Configuration
             ValueTypeName = reader.ReadString();
 
             var count = reader.ReadInt();
-            Fields = count == 0 ? null : Enumerable.Range(0, count).Select(x =>
-                    new QueryField(reader.ReadString(), reader.ReadString())).ToList();
+            Fields = count == 0
+                ? null
+                : Enumerable.Range(0, count).Select(x =>
+                    new QueryField(reader.ReadString(), reader.ReadString()) {IsKeyField = reader.ReadBoolean()})
+                    .ToList();
 
             count = reader.ReadInt();
             Aliases = count == 0 ? null : Enumerable.Range(0, count)
@@ -205,6 +208,7 @@ namespace Apache.Ignite.Core.Cache.Configuration
                 {
                     writer.WriteString(field.Name);
                     writer.WriteString(field.FieldTypeName);
+                    writer.WriteBoolean(field.IsKeyField);
                 }
             }
             else
@@ -264,16 +268,19 @@ namespace Apache.Ignite.Core.Cache.Configuration
         /// <summary>
         /// Rescans the attributes in <see cref="KeyType"/> and <see cref="ValueType"/>.
         /// </summary>
-        private void RescanAttributes(params Type[] types)
+        private void RescanAttributes(Type keyType, Type valType)
         {
-            if (types.Length == 0 || types.All(t => t == null))
+            if (keyType == null && valType == null)
                 return;
 
             var fields = new List<QueryField>();
             var indexes = new List<QueryIndexEx>();
 
-            foreach (var type in types.Where(t => t != null))
-                ScanAttributes(type, fields, indexes, null, new HashSet<Type>());
+            if (keyType != null)
+                ScanAttributes(keyType, fields, indexes, null, new HashSet<Type>(), true);
+
+            if (valType != null)
+                ScanAttributes(valType, fields, indexes, null, new HashSet<Type>(), false);
 
             if (fields.Any())
                 Fields = fields;
@@ -308,15 +315,17 @@ namespace Apache.Ignite.Core.Cache.Configuration
         }
 
         /// <summary>
-        /// Scans specified type for occurences of <see cref="QuerySqlFieldAttribute"/>.
+        /// Scans specified type for occurences of <see cref="QuerySqlFieldAttribute" />.
         /// </summary>
         /// <param name="type">The type.</param>
         /// <param name="fields">The fields.</param>
         /// <param name="indexes">The indexes.</param>
         /// <param name="parentPropName">Name of the parent property.</param>
         /// <param name="visitedTypes">The visited types.</param>
+        /// <param name="isKey">Whether this is a key type.</param>
+        /// <exception cref="System.InvalidOperationException">Recursive Query Field definition detected:  + type</exception>
         private static void ScanAttributes(Type type, List<QueryField> fields, List<QueryIndexEx> indexes, 
-            string parentPropName, ISet<Type> visitedTypes)
+            string parentPropName, ISet<Type> visitedTypes, bool isKey)
         {
             Debug.Assert(type != null);
             Debug.Assert(fields != null);
@@ -344,9 +353,9 @@ namespace Apache.Ignite.Core.Cache.Configuration
                     if (parentPropName != null)
                         columnName = parentPropName + "." + columnName;
 
-                    fields.Add(new QueryField(columnName, memberInfo.Value));
+                    fields.Add(new QueryField(columnName, memberInfo.Value) {IsKeyField = isKey});
 
-                    ScanAttributes(memberInfo.Value, fields, indexes, columnName, visitedTypes);
+                    ScanAttributes(memberInfo.Value, fields, indexes, columnName, visitedTypes, isKey);
                 }
 
                 foreach (var attr in customAttributes.OfType<QueryTextFieldAttribute>())
@@ -359,9 +368,9 @@ namespace Apache.Ignite.Core.Cache.Configuration
                     if (parentPropName != null)
                         columnName = parentPropName + "." + columnName;
 
-                    fields.Add(new QueryField(columnName, memberInfo.Value));
+                    fields.Add(new QueryField(columnName, memberInfo.Value) {IsKeyField = isKey});
 
-                    ScanAttributes(memberInfo.Value, fields, indexes, columnName, visitedTypes);
+                    ScanAttributes(memberInfo.Value, fields, indexes, columnName, visitedTypes, isKey);
                 }
             }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/b7908d7a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs
index 12028e2..c33aa57 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs
@@ -109,6 +109,12 @@ namespace Apache.Ignite.Core.Cache.Configuration
         }
 
         /// <summary>
+        /// Gets or sets a value indicating whether this field belongs to the cache key.
+        /// Proper value here is required for SQL DML queries which create/modify cache keys.
+        /// </summary>
+        public bool IsKeyField { get; set; }
+
+        /// <summary>
         /// Validates this instance and outputs information to the log, if necessary.
         /// </summary>
         internal void Validate(ILogger log, string logInfo)