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

[ignite] branch master updated: IGNITE-13865 Support DateTime as a key or value in .NET and Java (#8580)

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

nizhikov 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 685c1b7  IGNITE-13865 Support  DateTime as a key or value in .NET and Java (#8580)
685c1b7 is described below

commit 685c1b70ca6d48e18991514531f22d789464f62b
Author: Nikolay <ni...@apache.org>
AuthorDate: Sat Dec 19 00:22:34 2020 +0300

    IGNITE-13865 Support  DateTime as a key or value in .NET and Java (#8580)
---
 .../ignite/platform/PlatformDeployServiceTask.java | 36 ++++++++++++++++++++++
 .../Services/IJavaService.cs                       | 11 ++++++-
 .../Services/JavaServiceDynamicProxy.cs            | 18 +++++++++++
 .../Apache.Ignite.Core.Tests/Services/Model.cs     |  6 ++--
 .../Services/ServicesTest.cs                       | 35 ++++++++++++++++++++-
 .../Binary/BinaryConfiguration.cs                  | 28 +++++++++++++++--
 .../Binary/BinaryReflectiveSerializer.cs           | 22 ++++++-------
 .../IgniteClientConfigurationSection.xsd           |  5 +++
 .../IgniteConfigurationSection.xsd                 |  5 +++
 .../Impl/Binary/BinarySystemHandlers.cs            | 35 ++++++++++++++++++++-
 .../Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs |  4 +--
 .../Apache.Ignite.Core/Impl/Binary/Marshaller.cs   | 29 +++++++++++------
 .../Impl/Services/ServiceProxySerializer.cs        |  5 +--
 13 files changed, 205 insertions(+), 34 deletions(-)

diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
index 4e04131..cb3f8d7 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
@@ -19,14 +19,17 @@ package org.apache.ignite.platform;
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.binary.BinaryObject;
 import org.apache.ignite.cluster.ClusterNode;
@@ -44,6 +47,7 @@ import org.jetbrains.annotations.Nullable;
 
 import static java.util.Calendar.JANUARY;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -94,6 +98,9 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
      * Test service.
      */
     public static class PlatformTestService implements Service {
+        @IgniteInstanceResource
+        private Ignite ignite;
+
         /** */
         private boolean isCancelled;
 
@@ -495,6 +502,35 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
         }
 
         /** */
+        public void testDateArray(Timestamp[] dates) {
+            assertNotNull(dates);
+            assertEquals(2, dates.length);
+            assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), dates[0]);
+            assertEquals(new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()), dates[1]);
+        }
+
+        /** */
+        public Timestamp testDate(Timestamp date) {
+            if (date == null)
+                return null;
+
+            assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), date);
+
+            return new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime());
+        }
+
+        /** */
+        public void testUTCDateFromCache() {
+            IgniteCache<Integer, Timestamp> cache = ignite.cache("net-dates");
+
+            cache.put(3, new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()));
+            cache.put(4, new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()));
+
+            assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), cache.get(1));
+            assertEquals(new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()), cache.get(2));
+        }
+
+        /** */
         public void sleep(long delayMs) {
             try {
                 U.sleep(delayMs);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
index 8c9e6b6..dfcaaff 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
@@ -176,7 +176,16 @@ namespace Apache.Ignite.Core.Tests.Services
 
         /** */
         IDictionary testMap(IDictionary<Key, Value> dict);
-        
+
+        /** */
+        void testDateArray(DateTime?[] dates);
+
+        /** */
+        DateTime testDate(DateTime date);
+
+        /** */
+        void testUTCDateFromCache();
+
         /** */
         void sleep(long delayMs);
     }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
index 3276b83..26c0637 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
@@ -332,6 +332,24 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /** <inheritDoc /> */
+        public void testDateArray(DateTime?[] dates)
+        {
+            _svc.testDateArray(dates);
+        }
+
+        /** <inheritDoc /> */
+        public DateTime testDate(DateTime date)
+        {
+            return _svc.testDate(date);
+        }
+
+        /** <inheritDoc /> */
+        public void testUTCDateFromCache()
+        {
+            _svc.testDateFromCache();
+        }
+
+        /** <inheritDoc /> */
         public void sleep(long delayMs)
         {
             _svc.sleep(delayMs);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs
index 584511c..0793e87 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+// ReSharper disable once CheckNamespace
 namespace org.apache.ignite.platform
 {
     /// <summary>
@@ -28,7 +29,7 @@ namespace org.apache.ignite.platform
         /** */
         public string Addr { get; set; }
     }
-    
+
     /// <summary>
     /// A class is a clone of Java class Department with the same namespace.
     /// </summary>
@@ -37,7 +38,7 @@ namespace org.apache.ignite.platform
         /** */
         public string Name { get; set; }
     }
-    
+
     /// <summary>
     /// A class is a clone of Java class Employee with the same namespace.
     /// </summary>
@@ -72,6 +73,7 @@ namespace org.apache.ignite.platform
 
         public override int GetHashCode()
         {
+            // ReSharper disable once NonReadonlyMemberInGetHashCode
             return Id.GetHashCode();
         }
     }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
index ee0bdea..b2748ae 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -956,6 +956,23 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.AreEqual(new[] {11, 12, 13}, binSvc.testBinaryObjectArray(binArr)
                 .Select(x => x.GetField<int>("Field")));
 
+            DateTime dt1 = new DateTime(1982, 4, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+            DateTime dt2 = new DateTime(1991, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+
+            Assert.AreEqual(dt2, svc.testDate(dt1));
+
+            svc.testDateArray(new DateTime?[] {dt1, dt2});
+
+            var cache = Grid1.GetOrCreateCache<int, DateTime>("net-dates");
+
+            cache.Put(1, dt1);
+            cache.Put(2, dt2);
+
+            svc.testUTCDateFromCache();
+
+            Assert.AreEqual(dt1, cache.Get(3));
+            Assert.AreEqual(dt2, cache.Get(4));
+
             Services.Cancel(javaSvcName);
         }
 
@@ -1038,6 +1055,21 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.AreEqual(guid, svc.testNullUUID(guid));
             Assert.IsNull(svc.testNullUUID(null));
             Assert.AreEqual(guid, svc.testArray(new Guid?[] { guid })[0]);
+
+            DateTime dt1 = new DateTime(1982, 4, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+            DateTime dt2 = new DateTime(1991, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+
+            Assert.AreEqual(dt2, svc.testDate(dt1));
+
+            var cache = Grid1.GetOrCreateCache<int, DateTime>("net-dates");
+
+            cache.Put(1, dt1);
+            cache.Put(2, dt2);
+
+            svc.testUTCDateFromCache();
+
+            Assert.AreEqual(dt1, cache.Get(3));
+            Assert.AreEqual(dt2, cache.Get(4));
         }
 
         /// <summary>
@@ -1123,7 +1155,8 @@ namespace Apache.Ignite.Core.Tests.Services
                     typeof (PlatformComputeBinarizable),
                     typeof (BinarizableObject))
                 {
-                    NameMapper = BinaryBasicNameMapper.SimpleNameInstance
+                    NameMapper = BinaryBasicNameMapper.SimpleNameInstance,
+                    ForceTimestamp = true
                 }
             };
         }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
index 32d2c9d..2a2bb95 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
@@ -40,6 +40,11 @@ namespace Apache.Ignite.Core.Binary
         /// </summary>
         public const bool DefaultKeepDeserialized = true;
 
+        /// <summary>
+        /// Default <see cref="ForceTimestamp"/> setting.
+        /// </summary>
+        public const bool DefaultForceTimestamp = false;
+
         /** Footer setting. */
         private bool? _compactFooter;
 
@@ -49,6 +54,7 @@ namespace Apache.Ignite.Core.Binary
         public BinaryConfiguration()
         {
             KeepDeserialized = DefaultKeepDeserialized;
+            ForceTimestamp = DefaultForceTimestamp;
         }
 
         /// <summary>
@@ -72,6 +78,7 @@ namespace Apache.Ignite.Core.Binary
             IdMapper = cfg.IdMapper;
             NameMapper = cfg.NameMapper;
             KeepDeserialized = cfg.KeepDeserialized;
+            ForceTimestamp = cfg.ForceTimestamp;
 
             if (cfg.Serializer != null)
             {
@@ -106,7 +113,7 @@ namespace Apache.Ignite.Core.Binary
         public ICollection<BinaryTypeConfiguration> TypeConfigurations { get; set; }
 
         /// <summary>
-        /// Gets or sets a collection of assembly-qualified type names 
+        /// Gets or sets a collection of assembly-qualified type names
         /// (the result of <see cref="Type.AssemblyQualifiedName"/>) for binarizable types.
         /// <para />
         /// Shorthand for creating <see cref="BinaryTypeConfiguration"/>.
@@ -137,12 +144,12 @@ namespace Apache.Ignite.Core.Binary
 
         /// <summary>
         /// Gets or sets a value indicating whether to write footers in compact form.
-        /// When enabled, Ignite will not write fields metadata when serializing objects, 
+        /// When enabled, Ignite will not write fields metadata when serializing objects,
         /// because internally metadata is distributed inside cluster.
         /// This increases serialization performance.
         /// <para/>
         /// <b>WARNING!</b> This mode should be disabled when already serialized data can be taken from some external
-        /// sources (e.g.cache store which stores data in binary form, data center replication, etc.). 
+        /// sources (e.g.cache store which stores data in binary form, data center replication, etc.).
         /// Otherwise binary objects without any associated metadata could could not be deserialized.
         /// </summary>
         [DefaultValue(DefaultCompactFooter)]
@@ -153,6 +160,21 @@ namespace Apache.Ignite.Core.Binary
         }
 
         /// <summary>
+        /// Gets or sets a value indicating whether all DateTime keys, values and object fields
+        /// should be written as a Timestamp.
+        /// <para />
+        /// Timestamp format is required for values used in SQL and for interoperation with other platforms.
+        /// Only UTC values are supported in Timestamp format. Other values will cause an exception on write.
+        /// <para />
+        /// Normally Ignite serializer uses <see cref="IBinaryWriter.WriteObject{T}"/> for DateTime fields,
+        /// keys and values.
+        /// This attribute changes the behavior to <see cref="IBinaryWriter.WriteTimestamp"/>.
+        /// <para />
+        /// See also <see cref="TimestampAttribute"/>, <see cref="BinaryReflectiveSerializer.ForceTimestamp"/>.
+        /// </summary>
+        public bool ForceTimestamp { get; set; }
+
+        /// <summary>
         /// Gets the compact footer internal nullable value.
         /// </summary>
         internal bool? CompactFooterInternal
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
index f9874ba..c97b0e61 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
@@ -21,21 +21,21 @@ namespace Apache.Ignite.Core.Binary
     using Apache.Ignite.Core.Impl.Binary;
 
     /// <summary>
-    /// Binary serializer which reflectively writes all fields except of ones with 
+    /// Binary serializer which reflectively writes all fields except of ones with
     /// <see cref="System.NonSerializedAttribute"/>.
     /// <para />
-    /// Note that Java platform stores dates as a difference between current time 
-    /// and predefined absolute UTC date. Therefore, this difference is always the 
-    /// same for all time zones. .NET, in contrast, stores dates as a difference 
-    /// between current time and some predefined date relative to the current time 
-    /// zone. It means that this difference will be different as you change time zones. 
-    /// To overcome this discrepancy Ignite always converts .Net date to UTC form 
-    /// before serializing and allows user to decide whether to deserialize them 
-    /// in UTC or local form using <c>ReadTimestamp(..., true/false)</c> methods in 
+    /// Note that Java platform stores dates as a difference between current time
+    /// and predefined absolute UTC date. Therefore, this difference is always the
+    /// same for all time zones. .NET, in contrast, stores dates as a difference
+    /// between current time and some predefined date relative to the current time
+    /// zone. It means that this difference will be different as you change time zones.
+    /// To overcome this discrepancy Ignite always converts .Net date to UTC form
+    /// before serializing and allows user to decide whether to deserialize them
+    /// in UTC or local form using <c>ReadTimestamp(..., true/false)</c> methods in
     /// <see cref="IBinaryReader"/> and <see cref="IBinaryRawReader"/>.
     /// This serializer always read dates in UTC form. It means that if you have
     /// local date in any field/property, it will be implicitly converted to UTC
-    /// form after the first serialization-deserialization cycle. 
+    /// form after the first serialization-deserialization cycle.
     /// </summary>
     public sealed class BinaryReflectiveSerializer : IBinarySerializer
     {
@@ -94,7 +94,7 @@ namespace Apache.Ignite.Core.Binary
         /// Normally serializer uses <see cref="IBinaryWriter.WriteObject{T}"/> for DateTime fields.
         /// This attribute changes the behavior to <see cref="IBinaryWriter.WriteTimestamp"/>.
         /// <para />
-        /// See also <see cref="TimestampAttribute"/>.
+        /// See also <see cref="TimestampAttribute"/>, <see cref="BinaryConfiguration.ForceTimestamp"/>.
         /// </summary>
         public bool ForceTimestamp
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
index f78508e..42aa56b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
@@ -185,6 +185,11 @@
                                 <xs:documentation>Compact footer flag.</xs:documentation>
                             </xs:annotation>
                         </xs:attribute>
+                        <xs:attribute name="forceTimestamp" type="xs:boolean">
+                            <xs:annotation>
+                                <xs:documentation>Force timestamp flag.</xs:documentation>
+                            </xs:annotation>
+                        </xs:attribute>
                     </xs:complexType>
                 </xs:element>
                 <xs:element name="endpoints" minOccurs="0" maxOccurs="1">
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
index 8bb6f87..4bb0fef 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
@@ -274,6 +274,11 @@
                                 <xs:documentation>Compact footer flag.</xs:documentation>
                             </xs:annotation>
                         </xs:attribute>
+                        <xs:attribute name="forceTimestamp" type="xs:boolean">
+                            <xs:annotation>
+                                <xs:documentation>Force timestamp flag.</xs:documentation>
+                            </xs:annotation>
+                        </xs:attribute>
                     </xs:complexType>
                 </xs:element>
                 <xs:element name="cacheConfiguration" minOccurs="0">
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
index cae0f5f..3ffb1f9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
@@ -122,9 +122,18 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Try getting write handler for type.
         /// </summary>
         /// <param name="type"></param>
+        /// <param name="forceTimestamp"></param>
         /// <returns></returns>
-        public static IBinarySystemWriteHandler GetWriteHandler(Type type)
+        public static IBinarySystemWriteHandler GetWriteHandler(Type type, bool forceTimestamp)
         {
+            if (forceTimestamp)
+            {
+                if (type == typeof(DateTime))
+                    return new BinarySystemWriteHandler<DateTime>(WriteTimestamp, false);
+                if (type == typeof(DateTime?[]))
+                    return new BinarySystemWriteHandler<DateTime?[]>(WriteTimestampArray, true);
+            }
+
             return WriteHandlers.GetOrAdd(type, t =>
             {
                 return FindWriteHandler(t);
@@ -294,6 +303,18 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Write Date.
+        /// </summary>
+        /// <param name="ctx">Context.</param>
+        /// <param name="obj">Value.</param>
+        private static void WriteTimestamp(BinaryWriter ctx, DateTime obj)
+        {
+            ctx.Stream.WriteByte(BinaryTypeId.Timestamp);
+
+            BinaryUtils.WriteTimestamp(obj, ctx.Stream);
+        }
+
+        /// <summary>
         /// Write boolaen array.
         /// </summary>
         /// <param name="ctx">Context.</param>
@@ -426,6 +447,18 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Write nullable Date array.
+        /// </summary>
+        /// <param name="ctx">Context.</param>
+        /// <param name="obj">Value.</param>
+        private static void WriteTimestampArray(BinaryWriter ctx, DateTime?[] obj)
+        {
+            ctx.Stream.WriteByte(BinaryTypeId.ArrayTimestamp);
+
+            BinaryUtils.WriteTimestampArray(obj, ctx.Stream);
+        }
+
+        /// <summary>
         /// Writes the enum array.
         /// </summary>
         private static void WriteEnumArray(BinaryWriter ctx, object obj)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
index d09bfad..9025b4d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
@@ -875,7 +875,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                     throw new BinaryObjectException("Type is not an enum: " + type);
                 }
 
-                var handler = BinarySystemHandlers.GetWriteHandler(type);
+                var handler = BinarySystemHandlers.GetWriteHandler(type, _marsh.ForceTimestamp);
 
                 if (handler != null)
                 {
@@ -1180,7 +1180,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 return;
 
             // Are we dealing with a well-known type?
-            var handler = BinarySystemHandlers.GetWriteHandler(type);
+            var handler = BinarySystemHandlers.GetWriteHandler(type, _marsh.ForceTimestamp);
 
             if (handler != null)
             {
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 4a61cf5..cc9aeff 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -137,6 +137,14 @@ namespace Apache.Ignite.Core.Impl.Binary
         public bool RegistrationDisabled { get; set; }
 
         /// <summary>
+        /// Gets force timestamp flag value.
+        /// </summary>
+        public bool ForceTimestamp
+        {
+            get { return _cfg.ForceTimestamp; }
+        }
+
+        /// <summary>
         /// Marshal object.
         /// </summary>
         /// <param name="val">Value.</param>
@@ -376,7 +384,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             {
                 return holder;
             }
-            
+
             lock (this)
             {
                 if (!_metas.TryGetValue(desc.TypeId, out holder))
@@ -394,9 +402,9 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             return holder;
         }
-        
+
         /// <summary>
-        /// Updates or creates cached binary type holder. 
+        /// Updates or creates cached binary type holder.
         /// </summary>
         private void UpdateOrCreateBinaryTypeHolder(BinaryType meta)
         {
@@ -406,7 +414,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 holder.Merge(meta);
                 return;
             }
-            
+
             lock (this)
             {
                 if (_metas.TryGetValue(meta.TypeId, out holder))
@@ -414,7 +422,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                     holder.Merge(meta);
                     return;
                 }
-                
+
                 var metas0 = new Dictionary<int, BinaryTypeHolder>(_metas);
 
                 holder = new BinaryTypeHolder(meta.TypeId, meta.TypeName, meta.AffinityKeyFieldName, meta.IsEnum, this);
@@ -702,7 +710,10 @@ namespace Apache.Ignite.Core.Impl.Binary
                     return new SerializableSerializer(type);
                 }
 
-                serializer = new BinaryReflectiveSerializer();
+                serializer = new BinaryReflectiveSerializer
+                {
+                    ForceTimestamp = cfg != null && cfg.ForceTimestamp
+                };
             }
 
             var refSerializer = serializer as BinaryReflectiveSerializer;
@@ -877,16 +888,16 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             if (!clusterRestarted)
                 return;
-            
+
             // Reset all binary structures. Metadata must be sent again.
             // _idToDesc enumerator is thread-safe (returns a snapshot).
             // If there are new descriptors added concurrently, they are fine (we are already connected).
-            
+
             // Race is possible when serialization is started before reconnect (or even before disconnect)
             // and finished after reconnect, meta won't be sent to cluster because it is assumed to be known,
             // but operation will succeed.
             // We don't support this use case. Users should handle reconnect events properly when cluster is restarted.
-            // Supporting this very rare use case will complicate the code a lot with little benefit. 
+            // Supporting this very rare use case will complicate the code a lot with little benefit.
             foreach (var desc in _idToDesc)
             {
                 desc.Value.ResetWriteStructure();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
index 66998cb..16e3c47 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
@@ -267,7 +267,7 @@ namespace Apache.Ignite.Core.Impl.Services
                     return (writer, o) => writer.WriteTimestampArray((DateTime?[]) o);
             }
 
-            var handler = BinarySystemHandlers.GetWriteHandler(type);
+            var handler = BinarySystemHandlers.GetWriteHandler(type, true);
 
             if (handler != null)
                 return null;
@@ -281,9 +281,6 @@ namespace Apache.Ignite.Core.Impl.Services
             if (arg is ICollection)
                 return (writer, o) => writer.WriteCollection((ICollection) o);
 
-            if (arg is DateTime)
-                return (writer, o) => writer.WriteTimestamp((DateTime) o);
-
             return null;
         }
     }