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

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


The following commit(s) were added to refs/heads/master by this push:
     new c1f92d9  IGNITE-13160 .NET: Register binary metadata during cache start
c1f92d9 is described below

commit c1f92d975b9f45c81853431bca08b24cfce75265
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Thu Oct 22 14:52:35 2020 +0300

    IGNITE-13160 .NET: Register binary metadata during cache start
    
    * Register binary metadata for QueryEntity key and value .NET types on 
cache start (same way as we do for Java types - IGNITE-5795, 
3bb03444246f863096063d084393676a84d2bc0e)
    * Move `registerMetadataForRegisteredCaches` call to `onCacheKernalStart` 
so that `PlatformProcessor` is available
    
    This builds on the fix from IGNITE-5795 by adding a platform callback to 
get metadata for .NET types. It has the same limitations as the original fix: 
does not work when classes are not present on the server node, in particular, 
for thin clients. IGNITE-13607 filed to deal with that separately.
---
 .../ignite/internal/binary/BinaryContext.java      |  27 +-
 .../binary/builder/BinaryObjectBuilderImpl.java    |   4 +-
 .../processors/platform/PlatformContext.java       |  15 +
 .../processors/platform/PlatformContextImpl.java   |  32 ++-
 .../platform/callback/PlatformCallbackGateway.java |  16 ++
 .../platform/callback/PlatformCallbackOp.java      |   3 +
 .../processors/query/GridQueryProcessor.java       |  60 +++-
 .../Apache.Ignite.Core.Tests.DotNetCore.csproj     |   3 +
 .../Apache.Ignite.Core.Tests.csproj                |   6 +
 .../Cache/Affinity/AffinityTest.cs                 |  95 ++++++-
 .../Query/QueryEntityMetadataRegistrationTest.cs   | 301 +++++++++++++++++++++
 .../ClientQueryEntityMetadataRegistrationTest.cs   | 115 ++++++++
 ...EntityMetadataRegistrationTestJavaOnlyServer.cs |  76 ++++++
 .../Config/native-client-test-cache-affinity.xml   |  11 +
 ....xml => query-entity-metadata-registration.xml} |  56 ++--
 .../Cache/Configuration/QueryEntity.cs             |   4 +-
 .../Apache.Ignite.Core/Impl/Binary/Marshaller.cs   |  10 +-
 .../Impl/Unmanaged/UnmanagedCallbackOp.cs          |   3 +-
 .../Impl/Unmanaged/UnmanagedCallbacks.cs           |  57 ++--
 19 files changed, 823 insertions(+), 71 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index 7077985..c3eb3ea 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -585,6 +585,18 @@ public class BinaryContext {
     }
 
     /**
+     * Registers binary type locally.
+     *
+     * @param binaryType Binary type to register.
+     * @param failIfUnregistered Whether to fail when not registered.
+     * @param platformId Platform ID (see {@link 
org.apache.ignite.internal.MarshallerPlatformIds}).
+     */
+    public void registerClassLocally(BinaryType binaryType, boolean 
failIfUnregistered, byte platformId) {
+        metaHnd.addMetaLocally(binaryType.typeId(), binaryType, 
failIfUnregistered);
+        registerUserClassName(binaryType.typeId(), binaryType.typeName(), 
failIfUnregistered, true, platformId);
+    }
+
+    /**
      * @param cls Class.
      * @return A descriptor for the given class. If the class hasn't been 
registered yet, then a new descriptor will be
      * created, but its {@link BinaryClassDescriptor#registered()} will be 
{@code false}.
@@ -804,7 +816,7 @@ public class BinaryContext {
 
         int typeId = desc.typeId();
 
-        boolean registered = registerUserClassName(typeId, cls.getName(), 
false, onlyLocReg);
+        boolean registered = registerUserClassName(typeId, cls.getName(), 
false, onlyLocReg, JAVA_ID);
 
         if (registered) {
             BinaryClassDescriptor regDesc = desc.makeRegistered();
@@ -1174,17 +1186,24 @@ public class BinaryContext {
      * @param failIfUnregistered If {@code true} then throw {@link 
UnregisteredBinaryTypeException} with {@link
      * org.apache.ignite.internal.processors.marshaller.MappingExchangeResult} 
future instead of synchronously awaiting
      * for its completion.
+     * @param onlyLocReg Whether to register only on the current node.
+     * @param platformId Platform ID (see {@link 
org.apache.ignite.internal.MarshallerPlatformIds}).
      * @return {@code True} if the mapping was registered successfully.
      */
-    public boolean registerUserClassName(int typeId, String clsName, boolean 
failIfUnregistered, boolean onlyLocReg) {
+    public boolean registerUserClassName(
+            int typeId,
+            String clsName,
+            boolean failIfUnregistered,
+            boolean onlyLocReg,
+            byte platformId) {
         IgniteCheckedException e = null;
 
         boolean res = false;
 
         try {
             res = onlyLocReg
-                ? marshCtx.registerClassNameLocally(JAVA_ID, typeId, clsName)
-                : marshCtx.registerClassName(JAVA_ID, typeId, clsName, 
failIfUnregistered);
+                ? marshCtx.registerClassNameLocally(platformId, typeId, 
clsName)
+                : marshCtx.registerClassName(platformId, typeId, clsName, 
failIfUnregistered);
         }
         catch (DuplicateTypeIdException dupEx) {
             // Ignore if trying to register mapped type name of the already 
registered class name and vise versa
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
index 48c3fce..6ca4047 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
@@ -46,6 +46,8 @@ import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.thread.IgniteThread;
 import org.jetbrains.annotations.Nullable;
 
+import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID;
+
 /**
  *
  */
@@ -360,7 +362,7 @@ public class BinaryObjectBuilderImpl implements 
BinaryObjectBuilder {
                 if (affFieldName0 == null)
                     affFieldName0 = ctx.affinityKeyFieldName(typeId);
 
-                ctx.registerUserClassName(typeId, typeName, 
writer.failIfUnregistered(), false);
+                ctx.registerUserClassName(typeId, typeName, 
writer.failIfUnregistered(), false, JAVA_ID);
 
                 ctx.updateMetadata(typeId, new BinaryMetadata(typeId, 
typeName, fieldsMeta, affFieldName0,
                     Collections.singleton(curSchema), false, null), 
writer.failIfUnregistered());
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
index c5b4c39..d2d363e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContext.java
@@ -22,6 +22,7 @@ import org.apache.ignite.cluster.ClusterMetrics;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.events.Event;
 import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.binary.BinaryMetadata;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
 import org.apache.ignite.internal.binary.BinaryRawWriterEx;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
@@ -317,4 +318,18 @@ public interface PlatformContext {
      * Disables thread-local optimization for platform cache update.
      */
     void disableThreadLocalForPlatformCacheUpdate();
+
+    /**
+     * Gets platform binary type metadata.
+     *
+     * @param typeName Type name.
+     * @return Metadata when type exists; null otherwise.
+     */
+    @Nullable BinaryMetadata getBinaryType(String typeName);
+
+    /**
+     * Gets marshaller platform id (see {@link 
org.apache.ignite.internal.MarshallerPlatformIds}).
+     * @return Marshaller platform id.
+     */
+    byte getMarshallerPlatformId();
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
index f37a920..98bbfad 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
@@ -40,6 +40,7 @@ import org.apache.ignite.events.EventType;
 import org.apache.ignite.events.JobEvent;
 import org.apache.ignite.events.TaskEvent;
 import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.MarshallerPlatformIds;
 import org.apache.ignite.internal.binary.BinaryContext;
 import org.apache.ignite.internal.binary.BinaryMetadata;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
@@ -82,7 +83,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * Implementation of platform context.
  */
-@SuppressWarnings("TypeMayBeWeakened")
+@SuppressWarnings({"TypeMayBeWeakened", "rawtypes"})
 public class PlatformContextImpl implements PlatformContext, 
PartitionsExchangeAware {
     /** Supported event types. */
     private static final Set<Integer> evtTyps;
@@ -106,7 +107,7 @@ public class PlatformContextImpl implements 
PlatformContext, PartitionsExchangeA
     private final CacheObjectBinaryProcessorImpl cacheObjProc;
 
     /** Node ids that has been sent to native platform. */
-    private final Set<UUID> sentNodes = Collections.newSetFromMap(new 
ConcurrentHashMap<UUID, Boolean>());
+    private final Set<UUID> sentNodes = Collections.newSetFromMap(new 
ConcurrentHashMap<>());
 
     /** Platform name. */
     private final String platform;
@@ -653,6 +654,33 @@ public class PlatformContextImpl implements 
PlatformContext, PartitionsExchangeA
     }
 
     /** {@inheritDoc} */
+    @Override public @Nullable BinaryMetadata getBinaryType(String typeName) {
+        try (PlatformMemory mem0 = mem.allocate()) {
+            PlatformOutputStream out = mem0.output();
+            BinaryRawWriterEx writer = writer(out);
+
+            writer.writeString(typeName);
+            out.synchronize();
+
+            if (gateway().binaryTypeGet(mem0.pointer()) == 0)
+                return null;
+
+            PlatformInputStream in = mem0.input();
+            in.synchronize();
+
+            return PlatformUtils.readBinaryMetadata(reader(in));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte getMarshallerPlatformId() {
+        // Only .NET has a specific marshaller ID, C++ does not have it.
+        return platform.equals(PlatformUtils.PLATFORM_DOTNET)
+                ? MarshallerPlatformIds.DOTNET_ID
+                : MarshallerPlatformIds.JAVA_ID;
+    }
+
+    /** {@inheritDoc} */
     @Override public void 
onDoneAfterTopologyUnlock(GridDhtPartitionsExchangeFuture fut) {
         AffinityTopologyVersion ver = fut.topologyVersion();
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java
index 13f2914..6493a02 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackGateway.java
@@ -1299,6 +1299,22 @@ public class PlatformCallbackGateway {
     }
 
     /**
+     * Gets binary type by name.
+     *
+     * @param memPtr Ptr to a stream with serialized type name. Result is 
returned in the same stream.
+     */
+    public long binaryTypeGet(long memPtr) {
+        enter();
+
+        try {
+            return PlatformCallbackUtils.inLongOutLong(envPtr, 
PlatformCallbackOp.BinaryTypeGet, memPtr);
+        }
+        finally {
+            leave();
+        }
+    }
+
+    /**
      * Enter gateway.
      */
     protected void enter() {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackOp.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackOp.java
index db81e1c..e131df7 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackOp.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/callback/PlatformCallbackOp.java
@@ -245,4 +245,7 @@ class PlatformCallbackOp {
 
     /** */
     public static final int ComputeActionExecute = 75;
+
+    /** */
+    public static final int BinaryTypeGet = 76;
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index 51d239e..604ac7b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -61,6 +61,7 @@ import org.apache.ignite.events.CacheQueryExecutedEvent;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.NodeStoppingException;
+import org.apache.ignite.internal.binary.BinaryMetadata;
 import org.apache.ignite.internal.managers.communication.GridMessageListener;
 import org.apache.ignite.internal.processors.GridProcessorAdapter;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
@@ -83,6 +84,8 @@ import 
org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import 
org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
+import org.apache.ignite.internal.processors.platform.PlatformContext;
+import org.apache.ignite.internal.processors.platform.PlatformProcessor;
 import 
org.apache.ignite.internal.processors.query.property.QueryBinaryProperty;
 import 
org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
 import 
org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure;
@@ -142,6 +145,7 @@ import static 
org.apache.ignite.internal.processors.query.schema.SchemaOperation
 /**
  * Indexing processor.
  */
+@SuppressWarnings("rawtypes")
 public class GridQueryProcessor extends GridProcessorAdapter {
     /** */
     private static final String INLINE_SIZES_DISCO_BAG_KEY = "inline_sizes";
@@ -282,8 +286,6 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
                     ctxs.queries().evictDetailMetrics();
             }
         }, QRY_DETAIL_METRICS_EVICTION_FREQ, QRY_DETAIL_METRICS_EVICTION_FREQ);
-
-        registerMetadataForRegisteredCaches();
     }
 
     /** {@inheritDoc} */
@@ -325,6 +327,8 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
      * @throws IgniteCheckedException If failed.
      */
     public void onCacheKernalStart() throws IgniteCheckedException {
+        registerMetadataForRegisteredCaches();
+
         synchronized (stateMux) {
             exchangeReady = true;
 
@@ -1174,14 +1178,8 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
 
                 if (binaryEnabled) {
                     for (QueryEntity qryEntity : qryEntities) {
-                        Class<?> keyCls = 
U.box(U.classForName(qryEntity.findKeyType(), null, true));
-                        Class<?> valCls = 
U.box(U.classForName(qryEntity.findValueType(), null, true));
-
-                        if (keyCls != null)
-                            registerDescriptorLocallyIfNeeded(keyCls);
-
-                        if (valCls != null)
-                            registerDescriptorLocallyIfNeeded(valCls);
+                        registerTypeLocally(qryEntity.findKeyType());
+                        registerTypeLocally(qryEntity.findValueType());
                     }
                 }
             }
@@ -1268,20 +1266,52 @@ public class GridQueryProcessor extends 
GridProcessorAdapter {
     /**
      * Register class metadata locally if it didn't do it earlier.
      *
-     * @param cls Class for which the metadata should be registered.
+     * @param clsName Class name for which the metadata should be registered.
      * @throws BinaryObjectException if register was failed.
      */
-    private void registerDescriptorLocallyIfNeeded(Class<?> cls) throws 
BinaryObjectException {
+    private void registerTypeLocally(String clsName) throws 
BinaryObjectException {
+        if (clsName == null)
+            return;
+
         IgniteCacheObjectProcessor cacheObjProc = ctx.cacheObjects();
 
         if (cacheObjProc instanceof CacheObjectBinaryProcessorImpl) {
-            ((CacheObjectBinaryProcessorImpl)cacheObjProc)
-                .binaryContext()
-                .registerClass(cls, true, false, true);
+            CacheObjectBinaryProcessorImpl binProc = 
(CacheObjectBinaryProcessorImpl) cacheObjProc;
+
+            Class<?> cls = U.box(U.classForName(clsName, null, true));
+
+            if (cls != null)
+                binProc.binaryContext().registerClass(cls, true, false, true);
+            else
+                registerPlatformTypeLocally(clsName, binProc);
         }
     }
 
     /**
+     * Registers platform type locally.
+     *
+     * @param clsName Class name.
+     * @param binProc Binary processor.
+     */
+    private void registerPlatformTypeLocally(String clsName, 
CacheObjectBinaryProcessorImpl binProc) {
+        PlatformProcessor platformProc = ctx.platform();
+
+        assert platformProc != null : "Platform processor must be initialized";
+
+        if (!platformProc.hasContext())
+            return;
+
+        PlatformContext platformCtx = platformProc.context();
+        BinaryMetadata meta = platformCtx.getBinaryType(clsName);
+
+        if (meta != null)
+            binProc.binaryContext().registerClassLocally(
+                    meta.wrap(binProc.binaryContext()),
+                    false,
+                    platformCtx.getMarshallerPlatformId());
+    }
+
+    /**
      * Handle custom discovery message.
      *
      * @param msg Message.
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
index 54d2f61..ef97e9a 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.DotNetCore.csproj
@@ -93,6 +93,9 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Remove="Compute\Forked\**" />
+    <None Update="Config\query-entity-metadata-registration.xml">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
 
   </ItemGroup>
 
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 a50d72d..d3e8f46 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
@@ -144,8 +144,11 @@
     <Compile Include="Cache\Query\Linq\CacheLinqTest.Misc.cs" />
     <Compile Include="Cache\Query\Linq\CacheLinqTest.Custom.cs" />
     <Compile Include="Cache\Query\Linq\CacheLinqTest.Contains.cs" />
+    <Compile Include="Cache\Query\QueryEntityMetadataRegistrationTest.cs" />
     <Compile Include="Cache\Store\CacheStoreSessionTestCodeConfig.cs" />
     <Compile Include="Cache\Store\CacheStoreSessionTestSharedFactory.cs" />
+    <Compile 
Include="Client\Cache\ClientQueryEntityMetadataRegistrationTest.cs" />
+    <Compile 
Include="Client\Cache\ClientQueryEntityMetadataRegistrationTestJavaOnlyServer.cs"
 />
     <Compile Include="Client\Cache\ContinuousQueryTest.cs" />
     <Compile Include="Client\Cache\CacheClientAbstractTxTest.cs" />
     <Compile Include="Client\Cache\CacheClientLocalTxTest.cs" />
@@ -521,6 +524,9 @@
     <Content Include="Config\Log\custom-log.xml">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Config\query-entity-metadata-registration.xml">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="Config\spring-test.xml">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityTest.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityTest.cs
index d62c0b1..73a1c1f 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityTest.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Affinity/AffinityTest.cs
@@ -17,9 +17,13 @@
 
 namespace Apache.Ignite.Core.Tests.Cache.Affinity
 {
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
     using System.IO;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Cache.Affinity;
+    using Apache.Ignite.Core.Cache.Configuration;
     using Apache.Ignite.Core.Cluster;
     using NUnit.Framework;
 
@@ -79,7 +83,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
         {
             IIgnite g = Ignition.GetIgnite("grid-0");
 
-            ICacheAffinity aff = g.GetAffinity("default");  
+            ICacheAffinity aff = g.GetAffinity("default");
 
             IBinaryObject affKey = g.GetBinary().ToBinary<IBinaryObject>(new 
AffinityTestKey(0, 1));
 
@@ -95,6 +99,65 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
         }
 
         /// <summary>
+        /// Tests that <see cref="AffinityKeyMappedAttribute"/> works when 
used on a property of a type that is
+        /// specified as <see cref="QueryEntity.KeyType"/> or <see 
cref="QueryEntity.ValueType"/> and
+        /// configured in a Spring XML file. 
+        /// </summary>
+        [Test]
+        public void TestAffinityKeyMappedWithQueryEntitySpringXml()
+        {
+            
TestAffinityKeyMappedWithQueryEntity0(Ignition.GetIgnite("grid-0"), "cache1");
+            
TestAffinityKeyMappedWithQueryEntity0(Ignition.GetIgnite("grid-1"), "cache1");
+        }
+
+        /// <summary>
+        /// Tests that <see cref="AffinityKeyMappedAttribute"/> works when 
used on a property of a type that is
+        /// specified as <see cref="QueryEntity.KeyType"/> or <see 
cref="QueryEntity.ValueType"/>. 
+        /// </summary>
+        [Test]
+        public void TestAffinityKeyMappedWithQueryEntity()
+        {
+            var cacheCfg = new CacheConfiguration(TestUtils.TestName)
+            {
+                QueryEntities = new List<QueryEntity>
+                {
+                    new QueryEntity(typeof(QueryEntityKey), 
typeof(QueryEntityValue))
+                }
+            };
+
+            var cache = 
Ignition.GetIgnite("grid-0").GetOrCreateCache<QueryEntityKey, 
QueryEntityValue>(cacheCfg);
+            var cache2 = 
Ignition.GetIgnite("grid-1").GetOrCreateCache<QueryEntityKey, 
QueryEntityValue>(cacheCfg);
+
+            
TestAffinityKeyMappedWithQueryEntity0(Ignition.GetIgnite("grid-0"), 
cacheCfg.Name);
+            
TestAffinityKeyMappedWithQueryEntity0(Ignition.GetIgnite("grid-1"), 
cacheCfg.Name);
+
+            // Check put/get.
+            var key = new QueryEntityKey {Data = "x", AffinityKey = 123};
+            cache[key] = new QueryEntityValue {Name = "y", AffKey = 321};
+
+            var val = cache2[key];
+            Assert.AreEqual("y", val.Name);
+            Assert.AreEqual(321, val.AffKey);
+        }
+
+        /// <summary>
+        /// Checks affinity mapping.
+        /// </summary>
+        private static void TestAffinityKeyMappedWithQueryEntity0(IIgnite 
ignite, string cacheName)
+        {
+            var aff = ignite.GetAffinity(cacheName);
+
+            var key1 = new QueryEntityKey {Data = "data1", AffinityKey = 1};
+            var key2 = new QueryEntityKey {Data = "data2", AffinityKey = 1};
+
+            var val1 = new QueryEntityValue {Name = "foo", AffKey = 100};
+            var val2 = new QueryEntityValue {Name = "bar", AffKey = 100};
+
+            Assert.AreEqual(aff.GetPartition(key1), aff.GetPartition(key2));
+            Assert.AreEqual(aff.GetPartition(val1), aff.GetPartition(val2));
+        }
+
+        /// <summary>
         /// Affinity key.
         /// </summary>
         private class AffinityTestKey
@@ -131,5 +194,35 @@ namespace Apache.Ignite.Core.Tests.Cache.Affinity
                 return _id;
             }
         }
+        
+        /// <summary>
+        /// Query entity key.
+        /// </summary>
+        [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
+        private class QueryEntityKey
+        {
+            /** */
+            [QuerySqlField]
+            public string Data { get; set; }
+            
+            /** */
+            [AffinityKeyMapped]
+            public long AffinityKey { get; set; }
+        }
+        
+        /// <summary>
+        /// Query entity key.
+        /// </summary>
+        [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
+        private class QueryEntityValue
+        {
+            /** */
+            [QuerySqlField]
+            public string Name { get; set; }
+            
+            /** */
+            [AffinityKeyMapped]
+            public long AffKey { get; set; }
+        }
     }
 }
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/QueryEntityMetadataRegistrationTest.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/QueryEntityMetadataRegistrationTest.cs
new file mode 100644
index 0000000..afa29f0
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/QueryEntityMetadataRegistrationTest.cs
@@ -0,0 +1,301 @@
+/*
+ * 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 UnusedMember.Local
+// ReSharper disable NotAccessedField.Local
+#pragma warning disable 649 // Unassigned field
+namespace Apache.Ignite.Core.Tests.Cache.Query
+{
+    using System.IO;
+    using System.Linq;
+    using Apache.Ignite.Core.Cache.Affinity;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Client;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests that <see cref="QueryEntity.KeyTypeName"/> and <see 
cref="QueryEntity.ValueTypeName"/>
+    /// settings trigger binary metadata registration on cache start for the 
specified types.
+    /// <para />
+    /// Normally, binary metadata is registered in the cluster when an object 
of the given type is first serialized
+    /// (for cache storage or other purposes - Services, Compute, etc).
+    /// However, query engine requires metadata for key/value types on cache 
start, so an eager registration
+    /// should be performed.
+    /// </summary>
+    public class QueryEntityMetadataRegistrationTest
+    {
+        /** */
+        private const string CacheName = "cache1";
+
+        /** */
+        private const string CacheName2 = "cache2";
+
+        /// <summary>
+        /// Fixture set up.
+        /// </summary>
+        [TestFixtureSetUp]
+        public void StartGrids()
+        {
+            var springConfig = Path.Combine("Config", 
"query-entity-metadata-registration.xml");
+
+            for (int i = 0; i < 2; i++)
+            {
+                var cfg = new 
IgniteConfiguration(TestUtils.GetTestConfiguration())
+                {
+                    SpringConfigUrl = springConfig,
+                    IgniteInstanceName = i.ToString(),
+
+                    // Cache configs will be merged with Spring cache configs.
+                    CacheConfiguration = new[]
+                    {
+                        new CacheConfiguration
+                        {
+                            Name = CacheName2,
+                            QueryEntities = new[]
+                            {
+                                new QueryEntity
+                                {
+                                    KeyType = typeof(Key3),
+                                    ValueType = typeof(string)
+                                }
+                            }
+                        }
+                    }
+                };
+
+                Ignition.Start(cfg);
+            }
+        }
+
+        /// <summary>
+        /// Fixture tear down.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void StopGrids()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Tests that starting a cache from code with a <see 
cref="QueryEntity"/> causes binary type registration
+        /// for key and value types.
+        /// <para />
+        /// * Start a new cache with code configuration
+        /// * Check that query entity is populated correctly
+        /// * Check that key and value types are registered in the cluster
+        /// </summary>
+        [Test]
+        public void TestCacheStartFromCodeRegistersMetaForQueryEntityTypes()
+        {
+            var cfg = new CacheConfiguration
+            {
+                Name = TestUtils.TestName,
+                QueryEntities = new[]
+                {
+                    new QueryEntity
+                    {
+                        KeyType = typeof(Key1),
+                        ValueType = typeof(Value1)
+                    }
+                }
+            };
+
+            var cache = Ignition.GetIgnite("0").CreateCache<Key1, Value1>(cfg);
+
+            foreach (var ignite in Ignition.GetAll())
+            {
+                // Do not use GetBinaryType which always returns something.
+                // Use GetBinaryTypes to make sure that types are actually 
registered.
+                var types = ignite.GetBinary().GetBinaryTypes();
+                var qryEntity = ignite.GetCache<object, 
object>(cfg.Name).GetConfiguration().QueryEntities.Single();
+
+                var keyType = types.Single(t => t.TypeName == 
qryEntity.KeyTypeName);
+                var valType = types.Single(t => t.TypeName == 
qryEntity.ValueTypeName);
+
+                Assert.AreEqual(typeof(Key1).FullName, qryEntity.KeyTypeName);
+                Assert.AreEqual(typeof(Value1).FullName, 
qryEntity.ValueTypeName);
+
+                Assert.AreEqual("Bar", keyType.AffinityKeyFieldName);
+                Assert.IsEmpty(keyType.Fields);
+
+                Assert.IsNull(valType.AffinityKeyFieldName);
+                Assert.IsEmpty(valType.Fields);
+            }
+
+            // Verify put/get on server and client.
+            cache[new Key1{Foo = "a", Bar = 2}] = new Value1 {Name = "x", 
Value = 1};
+
+            using (var client = Ignition.StartClient(new 
IgniteClientConfiguration("localhost")))
+            {
+                var clientCache = client.GetCache<Key1, Value1>(cache.Name);
+                var val = clientCache.Get(new Key1 {Foo = "a", Bar = 2});
+
+                Assert.AreEqual("x", val.Name);
+                Assert.AreEqual(1, val.Value);
+            }
+        }
+
+        /// <summary>
+        /// Tests that starting a cache from code with a <see 
cref="QueryEntity"/> skips binary type registration
+        /// for key and value types when those types can't be resolved.
+        /// <para />
+        /// * Start a new cache with code configuration and invalid key/value 
entity type names
+        /// * Check that query entity is populated correctly
+        /// </summary>
+        [Test]
+        public void TestCacheStartFromCodeSkipsInvalidQueryEntityTypes()
+        {
+            var cfg = new CacheConfiguration
+            {
+                Name = TestUtils.TestName,
+                QueryEntities = new[]
+                {
+                    new QueryEntity
+                    {
+                        KeyTypeName = "Invalid_Name",
+                        ValueTypeName = "Invalid_Name"
+                    }
+                }
+            };
+
+            Ignition.GetIgnite("0").CreateCache<object, object>(cfg);
+
+            foreach (var ignite in Ignition.GetAll())
+            {
+                var types = ignite.GetBinary().GetBinaryTypes();
+                var qryEntity = ignite.GetCache<object, 
object>(cfg.Name).GetConfiguration().QueryEntities.Single();
+
+                var keyType = types.FirstOrDefault(t => t.TypeName == 
qryEntity.KeyTypeName);
+                var valType = types.FirstOrDefault(t => t.TypeName == 
qryEntity.ValueTypeName);
+
+                Assert.IsNull(keyType);
+                Assert.IsNull(valType);
+            }
+        }
+
+        /// <summary>
+        /// Tests that starting a cache from Spring XML with a <see 
cref="QueryEntity"/> causes binary type registration
+        /// for key and value types.
+        /// <para />
+        /// * Get the cache started from Spring XML
+        /// * Check that query entity is populated correctly
+        /// * Check that key and value types are registered in the cluster
+        /// </summary>
+        [Test]
+        public void TestCacheStartFromSpringRegistersMetaForQueryEntityTypes()
+        {
+            foreach (var ignite in Ignition.GetAll())
+            {
+                // Do not use GetBinaryType which always returns something.
+                // Use GetBinaryTypes to make sure that types are actually 
registered.
+                var types = ignite.GetBinary().GetBinaryTypes();
+                var qryEntity = ignite.GetCache<object, 
object>(CacheName).GetConfiguration().QueryEntities.Single();
+
+                var keyType = types.Single(t => t.TypeName == 
qryEntity.KeyTypeName);
+                var valType = types.Single(t => t.TypeName == 
qryEntity.ValueTypeName);
+
+                Assert.AreEqual(typeof(Key2).FullName, qryEntity.KeyTypeName);
+                Assert.AreEqual(typeof(Value2).FullName, 
qryEntity.ValueTypeName);
+
+                Assert.AreEqual("AffKey", keyType.AffinityKeyFieldName);
+                Assert.IsEmpty(keyType.Fields);
+
+                Assert.IsNull(valType.AffinityKeyFieldName);
+                Assert.IsEmpty(valType.Fields);
+            }
+        }
+
+        /// <summary>
+        /// Tests that starting a cache from <see 
cref="IgniteConfiguration.CacheConfiguration"/>
+        /// with a <see cref="QueryEntity"/> causes binary type registration 
for key and value types.
+        /// <para />
+        /// * Get the cache started from <see 
cref="IgniteConfiguration.CacheConfiguration"/>
+        /// * Check that query entity is populated correctly
+        /// * Check that key and value types are registered in the cluster
+        /// </summary>
+        [Test]
+        public void 
TestCacheStartIgniteConfigurationRegistersMetaForQueryEntityTypes()
+        {
+            foreach (var ignite in Ignition.GetAll())
+            {
+                var types = ignite.GetBinary().GetBinaryTypes();
+                var qryEntity = ignite.GetCache<object, 
object>(CacheName2).GetConfiguration().QueryEntities.Single();
+
+                var keyType = types.Single(t => t.TypeName == 
qryEntity.KeyTypeName);
+
+                Assert.AreEqual(typeof(Key3).FullName, qryEntity.KeyTypeName);
+                Assert.AreEqual("Aff", keyType.AffinityKeyFieldName);
+            }
+        }
+
+        /** */
+        private class Key1
+        {
+            /** */
+            [QuerySqlField]
+            public string Foo;
+
+            /** */
+            [AffinityKeyMapped]
+            public int Bar;
+        }
+
+        /** */
+        private class Value1
+        {
+            /** */
+            [QuerySqlField]
+            public string Name { get; set; }
+
+            /** */
+            [QuerySqlField]
+            public long Value { get; set; }
+        }
+
+        /** */
+        private class Key2
+        {
+            /** */
+            public string Baz;
+
+            /** */
+            [AffinityKeyMapped]
+            public long AffKey;
+        }
+
+        /** */
+        private class Value2
+        {
+            /** */
+            public string Name { get; set; }
+
+            /** */
+            public decimal Price { get; set; }
+        }
+
+        /** */
+        private class Key3
+        {
+            /** */
+            public string Qux;
+
+            /** */
+            [AffinityKeyMapped]
+            public long Aff;
+        }
+    }
+}
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTest.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTest.cs
new file mode 100644
index 0000000..d91c419
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTest.cs
@@ -0,0 +1,115 @@
+/*
+ * 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 UnusedMember.Local
+#pragma warning disable 649
+namespace Apache.Ignite.Core.Tests.Client.Cache
+{
+    using Apache.Ignite.Core.Cache.Affinity;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Client;
+    using Apache.Ignite.Core.Client.Cache;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests that <see cref="QueryEntity.KeyTypeName"/> and <see 
cref="QueryEntity.ValueTypeName"/>
+    /// settings trigger binary metadata registration on cache start for the 
specified types.
+    /// <para />
+    /// Normally, binary metadata is registered in the cluster when an object 
of the given type is first serialized
+    /// (for cache storage or other purposes - Services, Compute, etc).
+    /// However, query engine requires metadata for key/value types on cache 
start, so an eager registration
+    /// should be performed.
+    /// </summary>
+    public class ClientQueryEntityMetadataRegistrationTest
+    {
+        /// <summary>
+        /// Fixture set up.
+        /// </summary>
+        [TestFixtureSetUp]
+        public virtual void FixtureSetUp()
+        {
+            Ignition.Start(TestUtils.GetTestConfiguration());
+        }
+
+        /// <summary>
+        /// Fixture tear down.
+        /// </summary>
+        [TestFixtureTearDown]
+        public virtual void FixtureTearDown()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Tests that starting a cache from thin client with a <see 
cref="QueryEntity"/>
+        /// causes binary type registration for key and value types.
+        /// <para />
+        /// * Connect .NET thin client to a Java-only node
+        /// * Start a new cache with code configuration from thin client
+        /// * Check that key and value types are registered in the cluster 
correctly
+        /// </summary>
+        [Test]
+        public void 
TestCacheStartFromThinClientRegistersMetaForQueryEntityTypes()
+        {
+            var cfg = new CacheClientConfiguration
+            {
+                Name = TestUtils.TestName,
+                QueryEntities = new[]
+                {
+                    new QueryEntity
+                    {
+                        KeyType = typeof(Key1),
+                        ValueType = typeof(Value1)
+                    }
+                }
+            };
+
+            using (var client = Ignition.StartClient(new 
IgniteClientConfiguration("localhost:10800..10801")))
+            {
+                client.CreateCache<Key1, Value1>(cfg);
+
+                var type = client.GetBinary().GetBinaryType(typeof(Key1));
+
+                Assert.AreEqual("Bar", type.AffinityKeyFieldName);
+            }
+        }
+
+        /** */
+        private class Key1
+        {
+            /** */
+            [QuerySqlField]
+            public string Foo;
+
+            /** */
+            [AffinityKeyMapped]
+            public int Bar;
+        }
+
+        /** */
+        private class Value1
+        {
+            /** */
+            [QuerySqlField]
+            public string Name { get; set; }
+
+            /** */
+            [QuerySqlField]
+            public long Value { get; set; }
+        }
+    }
+}
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTestJavaOnlyServer.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTestJavaOnlyServer.cs
new file mode 100644
index 0000000..d963ceb
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/ClientQueryEntityMetadataRegistrationTestJavaOnlyServer.cs
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Tests.Client.Cache
+{
+    using System.IO;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests that <see cref="QueryEntity.KeyTypeName"/> and <see 
cref="QueryEntity.ValueTypeName"/>
+    /// settings trigger binary metadata registration on cache start for the 
specified types.
+    /// <para />
+    /// Normally, binary metadata is registered in the cluster when an object 
of the given type is first serialized
+    /// (for cache storage or other purposes - Services, Compute, etc).
+    /// However, query engine requires metadata for key/value types on cache 
start, so an eager registration
+    /// should be performed.
+    /// </summary>
+    [Ignore("IGNITE-13607")]
+    public class ClientQueryEntityMetadataRegistrationTestJavaOnlyServer : 
ClientQueryEntityMetadataRegistrationTest
+    {
+        /** */
+        private const string StartTask = 
"org.apache.ignite.platform.PlatformStartIgniteTask";
+
+        /** */
+        private const string StopTask = 
"org.apache.ignite.platform.PlatformStopIgniteTask";
+
+        /** */
+        private static readonly IgniteConfiguration TempConfig 
=TestUtils.GetTestConfiguration(name: "tmp");
+
+        /** */
+        private string _javaNodeName;
+
+        /// <summary>
+        /// Fixture set up.
+        /// </summary>
+        [TestFixtureSetUp]
+        public override void FixtureSetUp()
+        {
+            var springConfig = Path.Combine("Config", 
"query-entity-metadata-registration.xml");
+
+            using (var ignite = Ignition.Start(TempConfig))
+            {
+                _javaNodeName = 
ignite.GetCompute().ExecuteJavaTask<string>(StartTask, springConfig);
+                Assert.IsTrue(ignite.WaitTopology(2, 5000));
+            }
+        }
+
+        /// <summary>
+        /// Fixture tear down.
+        /// </summary>
+        [TestFixtureTearDown]
+        public override void FixtureTearDown()
+        {
+            using (var ignite = Ignition.Start(TempConfig))
+            {
+                ignite.GetCompute().ExecuteJavaTask<object>(StopTask, 
_javaNodeName);
+                Assert.IsTrue(ignite.WaitTopology(1, 5000));
+            }
+        }
+    }
+}
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
index 610d1dd..60bc001 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
@@ -50,6 +50,17 @@
                     <property name="cacheMode" value="PARTITIONED"/>
                     <property name="name" value="default"/>
                 </bean>
+                <bean 
class="org.apache.ignite.configuration.CacheConfiguration">
+                    <property name="name" value="cache1"/>
+                    <property name="queryEntities">
+                        <list>
+                            <bean class="org.apache.ignite.cache.QueryEntity">
+                                <property name="keyType" 
value="Apache.Ignite.Core.Tests.Cache.Affinity.AffinityTest+QueryEntityKey"/>
+                                <property name="valueType" 
value="Apache.Ignite.Core.Tests.Cache.Affinity.AffinityTest+QueryEntityValue"/>
+                            </bean>
+                        </list>
+                    </property>
+                </bean>
             </list>
         </property>
 
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/query-entity-metadata-registration.xml
similarity index 53%
copy from 
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
copy to 
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/query-entity-metadata-registration.xml
index 610d1dd..6bf6d3f 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/native-client-test-cache-affinity.xml
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Config/query-entity-metadata-registration.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -19,36 +18,42 @@
 
 <beans xmlns="http://www.springframework.org/schema/beans";
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-       xsi:schemaLocation="http://www.springframework.org/schema/beans
-        http://www.springframework.org/schema/beans/spring-beans.xsd";>
+       xmlns:util="http://www.springframework.org/schema/util";
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd";>
     <bean id="grid.cfg" 
class="org.apache.ignite.configuration.IgniteConfiguration">
         <property name="localHost" value="127.0.0.1"/>
-
         <property name="connectorConfiguration"><null/></property>
-
-        <property name="platformConfiguration">
-            <bean 
class="org.apache.ignite.platform.dotnet.PlatformDotNetConfiguration">
-                <property name="binaryConfiguration">
-                    <bean 
class="org.apache.ignite.platform.dotnet.PlatformDotNetBinaryConfiguration">
-                        <property name="typesConfiguration">
-                            <list>
-                                <bean 
class="org.apache.ignite.platform.dotnet.PlatformDotNetBinaryTypeConfiguration">
-                                    <property name="typeName"
-                                              
value="Apache.Ignite.Core.Tests.Cache.Affinity.AffinityTest+AffinityTestKey"/>
-                                    <property name="affinityKeyFieldName" 
value="_affKey"/>
-                                </bean>
-                            </list>
-                        </property>
-                    </bean>
-                </property>
-            </bean>
-        </property>
+        <property name="lateAffinityAssignment" value="false"/>
 
         <property name="cacheConfiguration">
             <list>
                 <bean 
class="org.apache.ignite.configuration.CacheConfiguration">
-                    <property name="cacheMode" value="PARTITIONED"/>
-                    <property name="name" value="default"/>
+                    <property name="name" value="cache1"/>
+                    <property name="queryEntities">
+                        <list>
+                            <bean class="org.apache.ignite.cache.QueryEntity">
+                                <property name="keyType" 
value="Apache.Ignite.Core.Tests.Cache.Query.QueryEntityMetadataRegistrationTest+Key2"/>
+                                <property name="valueType" 
value="Apache.Ignite.Core.Tests.Cache.Query.QueryEntityMetadataRegistrationTest+Value2"/>
+                            </bean>
+                        </list>
+                    </property>
+                </bean>
+
+                <!-- Query entity with key/value types that don't exist -->
+                <bean 
class="org.apache.ignite.configuration.CacheConfiguration">
+                    <property name="name" value="cache_bad"/>
+                    <property name="queryEntities">
+                        <list>
+                            <bean class="org.apache.ignite.cache.QueryEntity">
+                                <property name="keyType" 
value="Apache.Ignite.Core.Tests.Cache.Query.QueryEntityMetadataRegistrationTest+ERR"/>
+                                <property name="valueType" 
value="Apache.Ignite.Core.Tests.Cache.Query.QueryEntityMetadataRegistrationTest+ERR"/>
+                            </bean>
+                        </list>
+                    </property>
                 </bean>
             </list>
         </property>
@@ -59,7 +64,6 @@
                     <bean 
class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
                         <property name="addresses">
                             <list>
-                                <!-- In distributed environment, replace with 
actual host IP address. -->
                                 <value>127.0.0.1:47500</value>
                             </list>
                         </property>
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 32173ba..17e386e 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs
@@ -156,14 +156,14 @@ namespace Apache.Ignite.Core.Cache.Configuration
         /// <summary>
         /// Gets or sets the name of the field that is used to denote the 
entire key.
         /// <para />
-        /// By default, entite key can be accessed with a special "_key" field 
name.
+        /// By default, entity key can be accessed with a special "_key" field 
name.
         /// </summary>
         public string KeyFieldName { get; set; }
 
         /// <summary>
         /// Gets or sets the name of the field that is used to denote the 
entire value.
         /// <para />
-        /// By default, entite value can be accessed with a special "_val" 
field name.
+        /// By default, entity value can be accessed with a special "_val" 
field name.
         /// </summary>
         public string ValueFieldName { get; set; }
 
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 056a713..523a3fb 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -457,8 +457,14 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Gets descriptor for type name.
         /// </summary>
         /// <param name="typeName">Type name.</param>
+        /// <param name="requiresType">If set to true, resulting descriptor 
must have Type property populated.
+        /// <para />
+        /// When working in binary mode, we don't need Type. And there is no 
Type at all in some cases.
+        /// So we should not attempt to call BinaryProcessor right away.
+        /// Only when we really deserialize the value, requiresType is set to 
true
+        /// and we attempt to resolve the type by all means.</param>
         /// <returns>Descriptor.</returns>
-        public IBinaryTypeDescriptor GetDescriptor(string typeName)
+        public IBinaryTypeDescriptor GetDescriptor(string typeName, bool 
requiresType = false)
         {
             BinaryFullTypeDescriptor desc;
 
@@ -469,7 +475,7 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             var typeId = GetTypeId(typeName, _cfg.IdMapper);
 
-            return GetDescriptor(true, typeId, typeName: typeName);
+            return GetDescriptor(true, typeId, typeName: typeName, 
requiresType: requiresType);
         }
 
         /// <summary>
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbackOp.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbackOp.cs
index 5462db1..e6027ab 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbackOp.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbackOp.cs
@@ -91,6 +91,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         OnCacheStopped = 72,
         OnAffinityTopologyVersionChanged = 73,
         ComputeOutFuncExecute = 74,
-        ComputeActionExecute = 75
+        ComputeActionExecute = 75,
+        BinaryTypeGet = 76
     }
 }
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
index ea50b29..5724b0e 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
@@ -30,6 +30,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
     using Apache.Ignite.Core.Compute;
     using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Binary.IO;
+    using Apache.Ignite.Core.Impl.Binary.Metadata;
     using Apache.Ignite.Core.Impl.Cache;
     using Apache.Ignite.Core.Impl.Cache.Affinity;
     using Apache.Ignite.Core.Impl.Cache.Query.Continuous;
@@ -220,6 +221,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             AddHandler(UnmanagedCallbackOp.OnAffinityTopologyVersionChanged, 
OnAffinityTopologyVersionChanged);
             AddHandler(UnmanagedCallbackOp.ComputeOutFuncExecute, 
ComputeOutFuncExecute);
             AddHandler(UnmanagedCallbackOp.ComputeActionExecute, 
ComputeActionExecute);
+            AddHandler(UnmanagedCallbackOp.BinaryTypeGet, BinaryTypeGet);
         }
 
         /// <summary>
@@ -233,10 +235,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         /// <summary>
         /// Adds the handler.
         /// </summary>
-        private void AddHandler(UnmanagedCallbackOp op, 
InLongLongLongObjectOutLongFunc func, 
+        private void AddHandler(UnmanagedCallbackOp op, 
InLongLongLongObjectOutLongFunc func,
             bool allowUninitialized = false)
         {
-            _inLongLongLongObjectOutLongHandlers[(int)op] 
+            _inLongLongLongObjectOutLongHandlers[(int)op]
                 = new InLongLongLongObjectOutLongHandler(func, 
allowUninitialized);
         }
 
@@ -428,7 +430,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
 
             return holder.Process(key, val, val != null, grid);
         }
-        
+
         /// <summary>
         /// Updates platform cache entry.
         /// </summary>
@@ -445,7 +447,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
 
             return 0;
         }
-        
+
         /// <summary>
         /// Updates platform cache entry.
         /// </summary>
@@ -456,10 +458,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
 
             _ignite.PlatformCacheManager.UpdateFromThreadLocal(
                 cacheId, partition, new AffinityTopologyVersion(verMajor, 
(int) verMinor));
-                
+
             return 0;
         }
-        
+
         /// <summary>
         /// Called on cache stop.
         /// </summary>
@@ -467,10 +469,10 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         private long OnCacheStopped(long cacheId)
         {
             _ignite.PlatformCacheManager.Stop((int) cacheId);
-            
+
             return 0;
         }
-        
+
         /// <summary>
         /// Called on affinity topology version change.
         /// </summary>
@@ -478,9 +480,9 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             long topologyVersion, long minorTopologyVersion, long unused, 
void* arg)
         {
             var affinityTopologyVersion = new 
AffinityTopologyVersion(topologyVersion, (int) minorTopologyVersion);
-            
+
             
_ignite.PlatformCacheManager.OnAffinityTopologyVersionChanged(affinityTopologyVersion);
-            
+
             return 0;
         }
 
@@ -619,7 +621,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         {
             return _handleRegistry.Get<ComputeJobHolder>(jobPtr);
         }
-        
+
         /// <summary>
         /// Executes <see cref="IComputeOutFunc"/>.
         /// </summary>
@@ -631,9 +633,9 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
                 var func = stream.ReadBool()
                     ? _handleRegistry.Get<object>(stream.ReadLong(), true)
                     : _ignite.Marshaller.Unmarshal<object>(stream);
-                
+
                 stream.Reset();
-                
+
                 var invoker = 
DelegateTypeDescriptor.GetComputeOutFunc(func.GetType());
                 ComputeRunner.ExecuteJobAndWriteResults(_ignite, stream, func, 
invoker);
             }
@@ -652,9 +654,9 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
                 var action = stream.ReadBool()
                     ? _handleRegistry.Get<IComputeAction>(stream.ReadLong(), 
true)
                     : _ignite.Marshaller.Unmarshal<IComputeAction>(stream);
-                
+
                 stream.Reset();
-                
+
                 ComputeRunner.ExecuteJobAndWriteResults(_ignite, stream, 
action, act =>
                 {
                     act.Invoke();
@@ -1228,6 +1230,27 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
             return 0;
         }
 
+        private long BinaryTypeGet(long memPtr)
+        {
+            return SafeCall(() =>
+            {
+                using (var stream = 
IgniteManager.Memory.Get(memPtr).GetStream())
+                {
+                    var marsh = _ignite.Marshaller;
+                    var typeName = marsh.StartUnmarshal(stream).ReadString();
+                    var desc = marsh.GetDescriptor(typeName, requiresType: 
true);
+
+                    if (desc == null || desc.Type == null)
+                        return 0;
+
+                    stream.Reset();
+                    marsh.Marshal(stream, w => 
BinaryProcessor.WriteBinaryType(w, new BinaryType(desc, marsh)));
+
+                    return 1;
+                }
+            });
+        }
+
         #endregion
 
         #region AffinityFunction
@@ -1312,7 +1335,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         #endregion
 
         #region PLUGINS
-  
+
         private long PluginCallbackInLongLongOutLong(long callbackId, long 
inPtr, long outPtr, void* arg)
         {
             return _ignite.PluginProcessor.InvokeCallback(callbackId, inPtr, 
outPtr);
@@ -1357,7 +1380,7 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
         }
 
         #endregion
-        
+
         /// <summary>
         /// Gets the log.
         /// </summary>

Reply via email to