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

[07/17] ignite git commit: IGNITE-4406 .NET: Control DateTime serialization via attribute

IGNITE-4406 .NET: Control DateTime serialization via attribute


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

Branch: refs/heads/ignite-5075
Commit: 25bcd43a63c7d6496e92237cb965d32d4075722d
Parents: 0eb9507
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Thu May 18 10:39:16 2017 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Thu May 18 10:39:16 2017 +0300

----------------------------------------------------------------------
 .../Apache.Ignite.Core.Tests.csproj             |   1 +
 .../Binary/BinaryDateTimeTest.cs                | 204 +++++++++++++++++++
 .../Binary/BinarySelfTest.cs                    |   2 +-
 .../Cache/Query/CacheLinqTest.cs                |   2 +-
 .../Apache.Ignite.Core.csproj                   |   1 +
 .../Binary/BinaryReflectiveSerializer.cs        |  44 +++-
 .../Binary/TimestampAttribute.cs                |  40 ++++
 .../Impl/Binary/BinaryReflectiveActions.cs      |  89 ++++----
 .../BinaryReflectiveSerializerInternal.cs       |  10 +-
 .../Impl/Binary/BinaryUtils.cs                  |   4 +-
 10 files changed, 351 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/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 13e4889..f27e774 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
@@ -69,6 +69,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Binary\BinaryBuilderSelfTestSimpleName.cs" />
+    <Compile Include="Binary\BinaryDateTimeTest.cs" />
     <Compile Include="Binary\BinaryEqualityComparerTest.cs" />
     <Compile Include="Binary\BinaryBuilderSelfTestDynamicRegistration.cs" />
     <Compile Include="Binary\BinaryNameMapperTest.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
new file mode 100644
index 0000000..e971eea
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
@@ -0,0 +1,204 @@
+/*
+ * 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;
+    using System.Linq;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// DateTime binary serialization tests.
+    /// </summary>
+    public class BinaryDateTimeTest
+    {
+        /// <summary>
+        /// Sets up the test fixture.
+        /// </summary>
+        [TestFixtureSetUp]
+        public void FixtureSetUp()
+        {
+            Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                BinaryConfiguration = new BinaryConfiguration
+                {
+                    TypeConfigurations = new[]
+                    {
+                        new BinaryTypeConfiguration(typeof(DateTimeObj2))
+                        {
+                            Serializer = new BinaryReflectiveSerializer {ForceTimestamp = true}
+                        }
+                    }
+                }
+            });
+        }
+
+        /// <summary>
+        /// Tears down the test fixture.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void FixtureTearDown()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Tests the default behavior: DateTime is written as ISerializable object.
+        /// </summary>
+        [Test]
+        public void TestDefaultBehavior()
+        {
+            AssertDateTimeField<DateTimeObj>((o, d) => o.Value = d, o => o.Value, "Value");
+        }
+
+        /// <summary>
+        /// Tests the ForceTimestamp option in serializer.
+        /// </summary>
+        [Test]
+        public void TestSerializerForceTimestamp()
+        {
+            // Check config.
+            var ser = Ignition.GetIgnite()
+                .GetConfiguration()
+                .BinaryConfiguration.TypeConfigurations
+                .Select(x => x.Serializer)
+                .OfType<BinaryReflectiveSerializer>()
+                .Single();
+            
+            Assert.IsTrue(ser.ForceTimestamp);
+
+            AssertTimestampField<DateTimeObj2>((o, d) => o.Value = d, o => o.Value, "Value");
+        }
+
+        /// <summary>
+        /// Tests TimestampAttribute applied to class members.
+        /// </summary>
+        [Test]
+        public void TestMemberAttributes()
+        {
+            AssertTimestampField<DateTimePropertyAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
+
+            AssertTimestampField<DateTimeFieldAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
+
+            AssertTimestampField<DateTimeQueryFieldAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
+        }
+
+        /// <summary>
+        /// Tests TimestampAttribute applied to entire class.
+        /// </summary>
+        [Test]
+        public void TestClassAttributes()
+        {
+            AssertTimestampField<DateTimeClassAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
+
+            AssertTimestampField<DateTimeClassAttribute2>((o, d) => o.Value = d, o => o.Value, "Value");
+        }
+
+        /// <summary>
+        /// Asserts that specified field is serialized as DateTime object.
+        /// </summary>
+        private static void AssertDateTimeField<T>(Action<T, DateTime> setValue,
+            Func<T, DateTime> getValue, string fieldName) where T : new()
+        {
+            var binary = Ignition.GetIgnite().GetBinary();
+
+            foreach (var dateTime in new[] { DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue })
+            {
+                var obj = new T();
+                setValue(obj, dateTime);
+
+                var bin = binary.ToBinary<IBinaryObject>(obj);
+                var res = bin.Deserialize<T>();
+
+                Assert.AreEqual(getValue(obj), getValue(res));
+                Assert.AreEqual(getValue(obj), bin.GetField<IBinaryObject>(fieldName).Deserialize<DateTime>());
+                Assert.AreEqual("Object", bin.GetBinaryType().GetFieldTypeName(fieldName));
+            }
+        }
+
+        /// <summary>
+        /// Asserts that specified field is serialized as Timestamp.
+        /// </summary>
+        private static void AssertTimestampField<T>(Action<T, DateTime> setValue,
+            Func<T, DateTime> getValue, string fieldName) where T : new()
+        {
+            // Non-UTC DateTime throws.
+            var binary = Ignition.GetIgnite().GetBinary();
+
+            var obj = new T();
+
+            setValue(obj, DateTime.Now);
+
+            var ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<IBinaryObject>(obj), 
+                "Timestamp fields should throw an error on non-UTC values");
+
+            Assert.AreEqual("DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.",
+                ex.Message);
+
+            // UTC DateTime works.
+            setValue(obj, DateTime.UtcNow);
+            var bin = binary.ToBinary<IBinaryObject>(obj);
+            var res = bin.Deserialize<T>();
+
+            Assert.AreEqual(getValue(obj), getValue(res));
+            Assert.AreEqual(getValue(obj), bin.GetField<DateTime>(fieldName));
+            Assert.AreEqual("Timestamp", bin.GetBinaryType().GetFieldTypeName(fieldName));
+        }
+
+        private class DateTimeObj
+        {
+            public DateTime Value { get; set; }
+        }
+
+        private class DateTimeObj2
+        {
+            public DateTime Value { get; set; }
+        }
+
+        private class DateTimePropertyAttribute
+        {
+            [Timestamp]
+            public DateTime Value { get; set; }
+        }
+
+        private class DateTimeFieldAttribute
+        {
+            [Timestamp]
+            public DateTime Value;
+        }
+
+        private class DateTimeQueryFieldAttribute
+        {
+            [QuerySqlField]
+            public DateTime Value { get; set; }
+        }
+
+        [Timestamp]
+        private class DateTimeClassAttribute
+        {
+            public DateTime Value { get; set; }
+        }
+
+        [Timestamp]
+        private class DateTimeClassAttribute2
+        {
+            public DateTime Value;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
index 4a0827b..bae126f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
@@ -673,7 +673,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             // Check exception with non-UTC date
             var stream = new BinaryHeapStream(128);
             var writer = _marsh.StartMarshal(stream);
-            Assert.Throws<InvalidOperationException>(() => writer.WriteTimestamp(DateTime.Now));
+            Assert.Throws<BinaryObjectException>(() => writer.WriteTimestamp(DateTime.Now));
         }
 
         /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs
index ab661cf..265a149 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs
@@ -895,7 +895,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
 
             // Invalid dateTime
             // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
-            var ex = Assert.Throws<InvalidOperationException>(() =>
+            var ex = Assert.Throws<BinaryObjectException>(() =>
                 roles.Where(x => x.Value.Date > DateTime.Now).ToArray());
             Assert.AreEqual("DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.", 
                 ex.Message);

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/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 fd6e5ec..25b9603 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -92,6 +92,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Binary\BinaryBasicNameMapper.cs" />
+    <Compile Include="Binary\TimestampAttribute.cs" />
     <Compile Include="Cache\Configuration\DataPageEvictionMode.cs" />
     <Compile Include="Cache\Configuration\MemoryPolicyConfiguration.cs" />
     <Compile Include="Cache\Configuration\PartitionLossPolicy.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
index c2f00e6..f9874ba 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs
@@ -45,6 +45,9 @@ namespace Apache.Ignite.Core.Binary
         /** In use flag. */
         private bool _isInUse;
 
+        /** Force timestamp flag. */
+        private bool _forceTimestamp;
+
         /// <summary>
         /// Write binary object.
         /// </summary>
@@ -76,15 +79,35 @@ namespace Apache.Ignite.Core.Binary
             get { return _rawMode; }
             set
             {
-                if (_isInUse)
-                    throw new InvalidOperationException(typeof(BinaryReflectiveSerializer).Name +
-                        ".RawMode cannot be changed after first serialization.");
+                ThrowIfInUse();
 
                 _rawMode = value;
             }
         }
 
         /// <summary>
+        /// Gets or sets a value indicating whether all DateTime values should be written as 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 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"/>.
+        /// </summary>
+        public bool ForceTimestamp
+        {
+            get { return _forceTimestamp; }
+            set
+            {
+                ThrowIfInUse();
+
+                _forceTimestamp = value;
+            }
+        }
+
+        /// <summary>
         /// Registers the specified type.
         /// </summary>
         internal IBinarySerializerInternal Register(Type type, int typeId, IBinaryNameMapper converter,
@@ -92,7 +115,20 @@ namespace Apache.Ignite.Core.Binary
         {
             _isInUse = true;
 
-            return new BinaryReflectiveSerializerInternal(_rawMode).Register(type, typeId, converter, idMapper);
+            return new BinaryReflectiveSerializerInternal(_rawMode)
+                .Register(type, typeId, converter, idMapper, _forceTimestamp);
+        }
+
+        /// <summary>
+        /// Throws an exception if this instance is already in use.
+        /// </summary>
+        private void ThrowIfInUse()
+        {
+            if (_isInUse)
+            {
+                throw new InvalidOperationException(typeof(BinaryReflectiveSerializer).Name +
+                                                    ".RawMode cannot be changed after first serialization.");
+            }
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/TimestampAttribute.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/TimestampAttribute.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/TimestampAttribute.cs
new file mode 100644
index 0000000..9e7d654
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/TimestampAttribute.cs
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+    /// <summary>
+    /// Instructs the serializer to write DateTime fields and properties in Timestamp format,
+    /// which is interoperable with other platforms and works in SQL,
+    /// but does not allow non-UTC values.
+    /// <para />
+    /// When applied to a struct or a class, changes behavior for all fields and properties.
+    /// <para />
+    /// 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="BinaryReflectiveSerializer.ForceTimestamp"/>.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | 
+        AttributeTargets.Class | AttributeTargets.Struct)]
+    public class TimestampAttribute : Attribute
+    {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
index bdcdd09..5b6e5f1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
@@ -106,17 +106,24 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <param name="writeAction">Write action.</param>
         /// <param name="readAction">Read action.</param>
         /// <param name="raw">Raw mode.</param>
+        /// <param name="forceTimestamp">Force timestamp serialization for DateTime fields..</param>
         public static void GetTypeActions(FieldInfo field, out BinaryReflectiveWriteAction writeAction,
-            out BinaryReflectiveReadAction readAction, bool raw)
+            out BinaryReflectiveReadAction readAction, bool raw, bool forceTimestamp)
         {
+            Debug.Assert(field != null);
+            Debug.Assert(field.DeclaringType != null);
+
             var type = field.FieldType;
 
+            forceTimestamp = forceTimestamp ||
+                             field.DeclaringType.GetCustomAttributes(typeof(TimestampAttribute), true).Any();
+
             if (type.IsPrimitive)
                 HandlePrimitive(field, out writeAction, out readAction, raw);
             else if (type.IsArray)
                 HandleArray(field, out writeAction, out readAction, raw);
             else
-                HandleOther(field, out writeAction, out readAction, raw);
+                HandleOther(field, out writeAction, out readAction, raw, forceTimestamp);
         }
 
         /// <summary>
@@ -400,39 +407,15 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
-        /// Determines whether specified field is a query field (has QueryFieldAttribute).
-        /// </summary>
-        private static bool IsQueryField(FieldInfo fieldInfo)
-        {
-            Debug.Assert(fieldInfo != null && fieldInfo.DeclaringType != null);
-
-            var fieldName = BinaryUtils.CleanFieldName(fieldInfo.Name);
-
-            object[] attrs = null;
-
-            if (fieldName != fieldInfo.Name)
-            {
-                // Backing field, check corresponding property
-                var prop = fieldInfo.DeclaringType.GetProperty(fieldName, fieldInfo.FieldType);
-
-                if (prop != null)
-                    attrs = prop.GetCustomAttributes(true);
-            }
-
-            attrs = attrs ?? fieldInfo.GetCustomAttributes(true);
-
-            return attrs.OfType<QuerySqlFieldAttribute>().Any();
-        }
-
-        /// <summary>
         /// Handle other type.
         /// </summary>
         /// <param name="field">The field.</param>
         /// <param name="writeAction">Write action.</param>
         /// <param name="readAction">Read action.</param>
         /// <param name="raw">Raw mode.</param>
+        /// <param name="forceTimestamp">Force timestamp serialization for DateTime fields..</param>
         private static void HandleOther(FieldInfo field, out BinaryReflectiveWriteAction writeAction,
-            out BinaryReflectiveReadAction readAction, bool raw)
+            out BinaryReflectiveReadAction readAction, bool raw, bool forceTimestamp)
         {
             var type = field.FieldType;
 
@@ -501,18 +484,12 @@ namespace Apache.Ignite.Core.Impl.Binary
                     ? GetRawReader(field, r => r.ReadCollection())
                     : GetReader(field, (f, r) => r.ReadCollection(f));
             }
-            else if (type == typeof(DateTime) && IsQueryField(field) && !raw)
+            else if (type == typeof(DateTime) && IsTimestamp(field, forceTimestamp, raw))
             {
-                // Special case for DateTime and query fields.
-                // If a field is marked with [QuerySqlField], write it as TimeStamp so that queries work.
-                // This is not needed in raw mode (queries do not work anyway).
-                // It may cause issues when field has attribute, but is used in a cache without queries, and user
-                // may expect non-UTC dates to work. However, such cases are rare, and there are workarounds.
-
                 writeAction = GetWriter<DateTime>(field, (f, w, o) => w.WriteTimestamp(f, o));
                 readAction = GetReader(field, (f, r) => r.ReadObject<DateTime>(f));
             }
-            else if (nullableType == typeof(DateTime) && IsQueryField(field) && !raw)
+            else if (nullableType == typeof(DateTime) && IsTimestamp(field, forceTimestamp, raw))
             {
                 writeAction = GetWriter<DateTime?>(field, (f, w, o) => w.WriteTimestamp(f, o));
                 readAction = GetReader(field, (f, r) => r.ReadTimestamp(f));
@@ -525,6 +502,46 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Determines whether specified field should be written as timestamp.
+        /// </summary>
+        private static bool IsTimestamp(FieldInfo field, bool forceTimestamp, bool raw)
+        {
+            if (forceTimestamp)
+            {
+                return true;
+            }
+
+            Debug.Assert(field != null && field.DeclaringType != null);
+
+            var fieldName = BinaryUtils.CleanFieldName(field.Name);
+
+            object[] attrs = null;
+
+            if (fieldName != field.Name)
+            {
+                // Backing field, check corresponding property
+                var prop = field.DeclaringType.GetProperty(fieldName, field.FieldType);
+
+                if (prop != null)
+                    attrs = prop.GetCustomAttributes(true);
+            }
+
+            attrs = attrs ?? field.GetCustomAttributes(true);
+
+            if (attrs.Any(x => x is TimestampAttribute))
+            {
+                return true;
+            }
+
+            // Special case for DateTime and query fields.
+            // If a field is marked with [QuerySqlField], write it as TimeStamp so that queries work.
+            // This is not needed in raw mode (queries do not work anyway).
+            // It may cause issues when field has attribute, but is used in a cache without queries, and user
+            // may expect non-UTC dates to work. However, such cases are rare, and there are workarounds.
+            return !raw && attrs.Any(x => x is QuerySqlFieldAttribute);
+        }
+
+        /// <summary>
         /// Gets the reader with a specified write action.
         /// </summary>
         private static BinaryReflectiveWriteAction GetWriter<T>(FieldInfo field,

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveSerializerInternal.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveSerializerInternal.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveSerializerInternal.cs
index b179f92..69f7132 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveSerializerInternal.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveSerializerInternal.cs
@@ -125,13 +125,17 @@ namespace Apache.Ignite.Core.Impl.Binary
             get { return true; }
         }
 
-        /// <summary>Register type.</summary>
+        /// <summary>
+        /// Register type.
+        /// </summary>
         /// <param name="type">Type.</param>
         /// <param name="typeId">Type ID.</param>
         /// <param name="converter">Name converter.</param>
         /// <param name="idMapper">ID mapper.</param>
+        /// <param name="forceTimestamp">Force timestamp serialization for DateTime fields..</param>
+        /// <returns>Resulting serializer.</returns>
         internal BinaryReflectiveSerializerInternal Register(Type type, int typeId, IBinaryNameMapper converter,
-            IBinaryIdMapper idMapper)
+            IBinaryIdMapper idMapper, bool forceTimestamp)
         {
             Debug.Assert(_wActions == null && _rActions == null);
 
@@ -165,7 +169,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 BinaryReflectiveWriteAction writeAction;
                 BinaryReflectiveReadAction readAction;
 
-                BinaryReflectiveActions.GetTypeActions(fields[i], out writeAction, out readAction, _rawMode);
+                BinaryReflectiveActions.GetTypeActions(fields[i], out writeAction, out readAction, _rawMode, forceTimestamp);
 
                 wActions[i] = writeAction;
                 rActions[i] = readAction;

http://git-wip-us.apache.org/repos/asf/ignite/blob/25bcd43a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
index 977251c..a2783ba 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
@@ -1670,8 +1670,10 @@ namespace Apache.Ignite.Core.Impl.Binary
         private static void ToJavaDate(DateTime date, out long high, out int low)
         {
             if (date.Kind != DateTimeKind.Utc)
-                throw new InvalidOperationException(
+            {
+                throw new BinaryObjectException(
                     "DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.");
+            }
 
             long diff = date.Ticks - JavaDateTicks;