Skip to content

Commit 5e4ec7a

Browse files
authored
Date time hash bug - id generation (#30)
* addressing DateTime serialization issue, making sure ids can be generated correctly
1 parent 722d48d commit 5e4ec7a

14 files changed

+219
-7
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
5+
namespace Redis.OM.Modeling
6+
{
7+
/// <summary>
8+
/// DateTime converter for JSON serialization.
9+
/// </summary>
10+
public class DateTimeJsonConverter : JsonConverter<DateTime>
11+
{
12+
/// <inheritdoc />
13+
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
14+
{
15+
var val = reader.GetString();
16+
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
17+
return dateTime.AddMilliseconds(long.Parse(val!));
18+
}
19+
20+
/// <inheritdoc />
21+
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
22+
{
23+
writer.WriteStringValue(new DateTimeOffset(value).ToUnixTimeMilliseconds().ToString());
24+
}
25+
}
26+
}

src/Redis.OM/Modeling/DocumentAttribute.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Redis.OM;
34

45
namespace Redis.OM.Modeling
@@ -9,6 +10,12 @@ namespace Redis.OM.Modeling
910
[AttributeUsage(AttributeTargets.Class)]
1011
public class DocumentAttribute : Attribute
1112
{
13+
private static Dictionary<string, IIdGenerationStrategy> _idGenerationStrategies = new ()
14+
{
15+
{ nameof(UlidGenerationStrategy), new UlidGenerationStrategy() },
16+
{ nameof(Uuid4IdGenerationStrategy), new Uuid4IdGenerationStrategy() },
17+
};
18+
1219
/// <summary>
1320
/// Gets or sets the storage type.
1421
/// </summary>
@@ -17,7 +24,7 @@ public class DocumentAttribute : Attribute
1724
/// <summary>
1825
/// Gets or sets the IdGenerationStrategy, will use a ULID by default.
1926
/// </summary>
20-
public IIdGenerationStrategy IdGenerationStrategy { get; set; } = new UlidGenerationStrategy();
27+
public string IdGenerationStrategyName { get; set; } = nameof(UlidGenerationStrategy);
2128

2229
/// <summary>
2330
/// Gets or sets the prefixes to use for the Documents.
@@ -43,5 +50,20 @@ public class DocumentAttribute : Attribute
4350
/// Gets or sets the filter to use for indexing documents.
4451
/// </summary>
4552
public string? Filter { get; set; }
53+
54+
/// <summary>
55+
/// Gets the IdGenerationStrategy.
56+
/// </summary>
57+
internal IIdGenerationStrategy IdGenerationStrategy => _idGenerationStrategies[IdGenerationStrategyName];
58+
59+
/// <summary>
60+
/// Registers an Id generation Strategy with the Object Mapper.
61+
/// </summary>
62+
/// <param name="strategyName">The name of the strategy, which you will reference when declaring a Document.</param>
63+
/// <param name="strategy">An instance of the Strategy class to be used by all documents to generate an id.</param>
64+
public static void RegisterIdGenerationStrategy(string strategyName, IIdGenerationStrategy strategy)
65+
{
66+
_idGenerationStrategies.Add(strategyName, strategy);
67+
}
4668
}
4769
}

src/Redis.OM/Modeling/RedisCollectionStateManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class RedisCollectionStateManager
2525
static RedisCollectionStateManager()
2626
{
2727
JsonSerializerOptions.Converters.Add(new GeoLocJsonConverter());
28+
JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
2829
}
2930

3031
/// <summary>

src/Redis.OM/Redis.OM.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<RootNamespace>Redis.OM</RootNamespace>
77
<Nullable>enable</Nullable>
88
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
9-
<PackageVersion>0.1.2</PackageVersion>
10-
<Version>0.1.2</Version>
9+
<PackageVersion>0.1.3</PackageVersion>
10+
<Version>0.1.3</Version>
1111
<Description>Object Mapping and More for Redis</Description>
1212
<Title>Redis OM</Title>
1313
<Authors>Steve Lorello</Authors>
@@ -30,7 +30,7 @@
3030
</ItemGroup>
3131

3232
<ItemGroup>
33-
<None Include="../../images/icon-square.png" Pack="true" PackagePath="\"/>
33+
<None Include="../../images/icon-square.png" Pack="true" PackagePath="\" />
3434
</ItemGroup>
3535
<ItemGroup>
3636
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">

src/Redis.OM/RedisCommands.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static class RedisCommands
2222
static RedisCommands()
2323
{
2424
Options.Converters.Add(new GeoLocJsonConverter());
25+
Options.Converters.Add(new DateTimeJsonConverter());
2526
}
2627

2728
/// <summary>

src/Redis.OM/RedisObjectHandler.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal static class RedisObjectHandler
2121
static RedisObjectHandler()
2222
{
2323
JsonSerializerOptions.Converters.Add(new GeoLocJsonConverter());
24+
JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
2425
}
2526

2627
/// <summary>
@@ -100,9 +101,19 @@ internal static string SetId(this object obj)
100101
throw new MissingMemberException("Missing Document Attribute decoration");
101102
}
102103

103-
var id = attr.IdGenerationStrategy.GenerateId();
104+
string id;
105+
id = attr.IdGenerationStrategy.GenerateId();
104106
if (idProperty != null)
105107
{
108+
if (idProperty.GetValue(obj) != default)
109+
{
110+
id = idProperty.GetValue(obj).ToString();
111+
}
112+
else
113+
{
114+
id = attr.IdGenerationStrategy.GenerateId();
115+
}
116+
106117
if (idProperty.PropertyType == typeof(string))
107118
{
108119
idProperty.SetValue(obj, id);
@@ -116,6 +127,10 @@ internal static string SetId(this object obj)
116127
throw new InvalidOperationException("Software Defined Ids on objects must either be a string or Guid");
117128
}
118129
}
130+
else
131+
{
132+
id = attr.IdGenerationStrategy.GenerateId();
133+
}
119134

120135
if (attr.Prefixes == null || string.IsNullOrEmpty(attr.Prefixes.FirstOrDefault()))
121136
{
@@ -192,6 +207,14 @@ internal static IDictionary<string, string> BuildHashSet(this object obj)
192207
hash.Add(propertyName, val.ToString());
193208
}
194209
}
210+
else if (type == typeof(DateTime) || type == typeof(DateTime?))
211+
{
212+
var val = (DateTime)property.GetValue(obj);
213+
if (val != default)
214+
{
215+
hash.Add(propertyName, new DateTimeOffset(val).ToUnixTimeMilliseconds().ToString());
216+
}
217+
}
195218
else if (type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
196219
{
197220
var e = (IEnumerable<object>)property.GetValue(obj);
@@ -253,7 +276,7 @@ private static string SendToJson(IDictionary<string, string> hash, Type t)
253276
{
254277
ret += $"\"{propertyName}\":{hash[propertyName]},";
255278
}
256-
else if (type == typeof(string) || type == typeof(GeoLoc))
279+
else if (type == typeof(string) || type == typeof(GeoLoc) || type == typeof(DateTime) || type == typeof(DateTime?))
257280
{
258281
ret += $"\"{propertyName}\":\"{hash[propertyName]}\",";
259282
}

test/Redis.OM.Unit.Tests/RediSearchTests/HashPerson.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections.Generic;
2-
using Redis.OM;
32
using Redis.OM.Modeling;
43

54
namespace Redis.OM.Unit.Tests.RediSearchTests
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using Redis.OM.Contracts;
3+
using Xunit;
4+
5+
namespace Redis.OM.Unit.Tests
6+
{
7+
[Collection("Redis")]
8+
public class DateTimeSerializationTest
9+
{
10+
private readonly IRedisConnection _connection;
11+
12+
public DateTimeSerializationTest(RedisSetup setup)
13+
{
14+
_connection = setup.Connection;
15+
}
16+
17+
[Fact]
18+
public void TestDateTimeSerialization()
19+
{
20+
var time = DateTime.Now;
21+
var obj = new ObjectWithATimestamp {Name = "Foo", Time = time};
22+
var objNonNullNullTime = new ObjectWithATimestamp {Name = "bar", Time = time, NullableTime = time};
23+
var id = _connection.Set(obj);
24+
var id2 = _connection.Set(objNonNullNullTime);
25+
var reconstituted = _connection.Get<ObjectWithATimestamp>(id);
26+
var reconstitutedObj2 = _connection.Get<ObjectWithATimestamp>(id2);
27+
Assert.Equal(time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), reconstituted.Time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
28+
Assert.Null(reconstituted.NullableTime);
29+
Assert.Equal(time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff"), reconstitutedObj2.NullableTime.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"));
30+
}
31+
32+
[Fact]
33+
public void TestJsonDateTimeSerialization()
34+
{
35+
var time = DateTime.Now;
36+
var obj = new JsonObjectWithDateTime {Name = "Foo", Time = time};
37+
var objNonNullNullTime = new JsonObjectWithDateTime {Name = "bar", Time = time, NullableTime = time};
38+
var id = _connection.Set(obj);
39+
var id2 = _connection.Set(objNonNullNullTime);
40+
var reconstituted = _connection.Get<JsonObjectWithDateTime>(id);
41+
var reconstitutedObj2 = _connection.Get<JsonObjectWithDateTime>(id2);
42+
Assert.Equal(time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), reconstituted.Time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
43+
Assert.Null(reconstituted.NullableTime);
44+
Assert.Equal(time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff"), reconstitutedObj2.NullableTime.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"));
45+
}
46+
}
47+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using Redis.OM.Modeling;
3+
4+
namespace Redis.OM.Unit.Tests
5+
{
6+
[Document(StorageType = StorageType.Json)]
7+
public class JsonObjectWithDateTime
8+
{
9+
public string Name { get; set; }
10+
public DateTime Time { get; set; }
11+
public DateTime? NullableTime { get; set; }
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using Redis.OM.Modeling;
3+
4+
namespace Redis.OM.Unit.Tests
5+
{
6+
[Document]
7+
public class ObjectWithATimestamp
8+
{
9+
public string Name { get; set; }
10+
public DateTime Time { get; set; }
11+
public DateTime? NullableTime { get; set; }
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Redis.OM.Modeling;
2+
3+
namespace Redis.OM.Unit.Tests
4+
{
5+
[Document(IdGenerationStrategyName = nameof(StaticIncrementStrategy))]
6+
public class ObjectWithCustomIdGenerationStrategy
7+
{
8+
[RedisIdField] public string Id { get; set; }
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Redis.OM.Modeling;
2+
3+
namespace Redis.OM.Unit.Tests
4+
{
5+
[Document]
6+
public class ObjectWithUserDefinedId
7+
{
8+
[RedisIdField] public string Id { get; set; }
9+
public string Name { get; set; }
10+
}
11+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Redis.OM.Contracts;
2+
using Redis.OM.Modeling;
3+
using Xunit;
4+
5+
namespace Redis.OM.Unit.Tests
6+
{
7+
[Collection("Redis")]
8+
public class SerializationTests
9+
{
10+
private readonly IRedisConnection _connection;
11+
12+
public SerializationTests(RedisSetup setup)
13+
{
14+
_connection = setup.Connection;
15+
}
16+
17+
[Fact]
18+
public void TestUserDefinedId()
19+
{
20+
var obj = new ObjectWithUserDefinedId {Id = "5", Name = "Steve"};
21+
var id = _connection.Set(obj);
22+
Assert.Equal("5", id.Split(':')[1]);
23+
}
24+
25+
[Fact]
26+
public void TestUserDefinedStrategy()
27+
{
28+
DocumentAttribute.RegisterIdGenerationStrategy(nameof(StaticIncrementStrategy), new StaticIncrementStrategy());
29+
var obj = new ObjectWithCustomIdGenerationStrategy();
30+
var id = _connection.Set(obj);
31+
Assert.Equal("1", obj.Id);
32+
Assert.Equal("1", id.Split(":")[1]);
33+
}
34+
}
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Redis.OM.Unit.Tests
2+
{
3+
public class StaticIncrementStrategy : IIdGenerationStrategy
4+
{
5+
public static int Current = 0;
6+
public string GenerateId()
7+
{
8+
return (Current++).ToString();
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)