Browse Source

Performance optimizations.

pull/335/head
Sebastian Stehle 8 years ago
parent
commit
265fa0aadb
  1. 29
      src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs
  2. 3
      src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs
  3. 12
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs
  4. 12
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs
  5. 18
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs
  6. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs
  7. 2
      src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
  8. 53
      src/Squidex.Infrastructure/NamedId{T}.cs
  9. 44
      src/Squidex.Infrastructure/RefToken.cs
  10. 2
      src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs
  11. 6
      src/Squidex.Infrastructure/StringExtensions.cs
  12. 1
      src/Squidex/Config/Domain/SerializationServices.cs
  13. 1
      tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs
  14. 26
      tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs
  15. 19
      tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs
  16. 44
      tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs
  17. 3
      tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs
  18. 1
      tools/Migrate_01/Migrations/ConvertEventStore.cs

29
src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs

@ -16,6 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private static readonly int GuidLength = Guid.Empty.ToString().Length;
private readonly Dictionary<Guid, Guid> oldToNewGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<Guid, Guid> newToOldGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<string, string> strings = new Dictionary<string, string>();
public Guid OldGuid(Guid newGuid)
{
@ -44,39 +45,49 @@ namespace Squidex.Domain.Apps.Entities.Backup
private bool TryGenerateNewGuidString(string value, out string result)
{
result = null;
if (value.Length == GuidLength)
{
if (strings.TryGetValue(value, out result))
{
return true;
}
if (Guid.TryParse(value, out var guid))
{
var newGuid = GenerateNewGuid(guid);
result = newGuid.ToString();
strings[value] = result = newGuid.ToString();
return true;
}
}
result = null;
return false;
}
private bool TryGenerateNewNamedId(string value, out string result)
{
result = null;
if (value.Length > GuidLength && value[GuidLength] == ',')
if (value.Length > GuidLength)
{
if (Guid.TryParse(value.Substring(0, GuidLength), out var guid))
if (strings.TryGetValue(value, out result))
{
var newGuid = GenerateNewGuid(guid);
return true;
}
result = newGuid + value.Substring(GuidLength);
if (NamedId<Guid>.TryParse(value, Guid.TryParse, out var namedId))
{
var newGuid = GenerateNewGuid(namedId.Id);
strings[value] = result = new NamedId<Guid>(newGuid, namedId.Name).ToString();
return true;
}
}
result = null;
return false;
}

3
src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs

@ -8,7 +8,6 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
@ -162,7 +161,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
public override bool CanConvert(Type objectType)
{
return objectType != typeof(EnvelopeHeaders) && typeof(IJsonValue).IsAssignableFrom(objectType);
return typeof(IJsonValue).IsAssignableFrom(objectType);
}
}
}

12
src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
{
protected override void WriteValue(JsonWriter writer, NamedId<Guid> value, JsonSerializer serializer)
{
writer.WriteValue($"{value.Id},{value.Name}");
writer.WriteValue(value.ToString());
}
protected override NamedId<Guid> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
@ -24,14 +24,12 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
try
if (!NamedId<Guid>.TryParse(reader.Value.ToString(), Guid.TryParse, out var result))
{
return NamedId<Guid>.Parse(reader.Value.ToString(), Guid.TryParse);
}
catch (ArgumentException ex)
{
throw new JsonException(ex.Message);
throw new JsonException("Named id must have more than 2 parts divided by commata.");
}
return result;
}
}
}

12
src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
{
protected override void WriteValue(JsonWriter writer, NamedId<long> value, JsonSerializer serializer)
{
writer.WriteValue($"{value.Id},{value.Name}");
writer.WriteValue(value.ToString());
}
protected override NamedId<long> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
@ -24,14 +24,12 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
try
if (!NamedId<long>.TryParse(reader.Value.ToString(), long.TryParse, out var result))
{
return NamedId<long>.Parse(reader.Value.ToString(), long.TryParse);
}
catch (ArgumentException ex)
{
throw new JsonException(ex.Message);
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}
return result;
}
}
}

18
src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Linq;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
@ -15,7 +14,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
{
protected override void WriteValue(JsonWriter writer, NamedId<string> value, JsonSerializer serializer)
{
writer.WriteValue($"{value.Id},{value.Name}");
writer.WriteValue(value.ToString());
}
protected override NamedId<string> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
@ -25,14 +24,19 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var parts = reader.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
if (!NamedId<string>.TryParse(reader.Value.ToString(), ParseString, out var result))
{
throw new JsonException("Named id must have more than 2 parts divided by colon.");
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}
return NamedId.Of(parts[0], string.Join(",", parts.Skip(1)));
return result;
}
private static bool ParseString(string value, out string result)
{
result = value;
return true;
}
}
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs

@ -24,7 +24,12 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
return RefToken.Parse(reader.Value.ToString());
if (!RefToken.TryParse(reader.Value.ToString(), out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by colon.");
}
return result;
}
}
}

2
src/Squidex.Infrastructure/Json/Objects/JsonObject.cs

@ -50,7 +50,7 @@ namespace Squidex.Infrastructure.Json.Objects
get { return JsonValueType.Array; }
}
public JsonObject()
internal JsonObject()
{
inner = new Dictionary<string, IJsonValue>();
}

53
src/Squidex.Infrastructure/NamedId{T}.cs

@ -6,7 +6,8 @@
// ==========================================================================
using System;
using System.Linq;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure
{
@ -14,6 +15,8 @@ namespace Squidex.Infrastructure
public sealed class NamedId<T> : IEquatable<NamedId<T>>
{
private static readonly int GuidLength = Guid.Empty.ToString().Length;
public T Id { get; }
public string Name { get; }
@ -48,23 +51,51 @@ namespace Squidex.Infrastructure
return (Id.GetHashCode() * 397) ^ Name.GetHashCode();
}
public static NamedId<T> Parse(string value, Parser<T> parser)
public static bool TryParse(string value, Parser<T> parser, out NamedId<T> result)
{
Guard.NotNull(value, nameof(value));
var parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
if (value != null)
{
throw new ArgumentException("Named id must have more than 2 parts divided by commata.");
if (typeof(T) == typeof(Guid))
{
if (value.Length > GuidLength + 1 && value[GuidLength] == ',')
{
if (parser(value.Substring(0, GuidLength), out var id))
{
result = new NamedId<T>(id, value.Substring(GuidLength + 1));
return true;
}
}
}
else
{
var index = value.IndexOf(',');
if (index > 0 && index < value.Length - 1)
{
if (parser(value.Substring(0, index), out var id))
{
result = new NamedId<T>(id, value.Substring(index + 1));
return true;
}
}
}
}
if (!parser(parts[0], out var id))
result = null;
return false;
}
public static NamedId<T> Parse(string value, Parser<T> parser)
{
if (!TryParse(value, parser, out var result))
{
throw new ArgumentException("Named id must be a valid guid.");
throw new ArgumentException("Named id must have at least 2 parts divided by commata.", nameof(value));
}
return new NamedId<T>(id, string.Join(",", parts.Skip(1)));
return result;
}
}
}

44
src/Squidex.Infrastructure/RefToken.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Linq;
namespace Squidex.Infrastructure
{
@ -26,20 +25,6 @@ namespace Squidex.Infrastructure
Identifier = identifier;
}
public static RefToken Parse(string input)
{
Guard.NotNullOrEmpty(input, nameof(input));
var parts = input.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
{
throw new ArgumentException("Input must have more than 2 parts divided by colon.", nameof(input));
}
return new RefToken(parts[0], string.Join(":", parts.Skip(1)));
}
public override string ToString()
{
return $"{Type}:{Identifier}";
@ -59,5 +44,34 @@ namespace Squidex.Infrastructure
{
return (Type.GetHashCode() * 397) ^ Identifier.GetHashCode();
}
public static bool TryParse(string value, out RefToken result)
{
if (value != null)
{
var idx = value.IndexOf(':');
if (idx > 0 && idx < value.Length - 1)
{
result = new RefToken(value.Substring(0, idx), value.Substring(idx + 1));
return true;
}
}
result = null;
return false;
}
public static RefToken Parse(string value)
{
if (!TryParse(value, out var result))
{
throw new ArgumentException("Ref token must have more than 2 parts divided by colon.", nameof(value));
}
return result;
}
}
}

2
src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs

@ -1,4 +1,4 @@
// ==========================================================================
 // ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)

6
src/Squidex.Infrastructure/StringExtensions.cs

@ -331,7 +331,7 @@ namespace Squidex.Infrastructure
return string.Empty;
}
var sb = new StringBuilder();
var sb = new StringBuilder(value.Length);
var last = NullChar;
var length = 0;
@ -387,7 +387,7 @@ namespace Squidex.Infrastructure
return string.Empty;
}
var sb = new StringBuilder();
var sb = new StringBuilder(value.Length);
var length = 0;
@ -429,7 +429,7 @@ namespace Squidex.Infrastructure
return string.Empty;
}
var sb = new StringBuilder();
var sb = new StringBuilder(value.Length);
var last = NullChar;
var length = 0;

1
src/Squidex/Config/Domain/SerializationServices.cs

@ -19,7 +19,6 @@ using Squidex.Domain.Apps.Core.Schemas.Json;
using Squidex.Domain.Apps.Events;
using Squidex.Extensions.Actions;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;

1
tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs

@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Schemas.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Xunit;

26
tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core;
@ -77,8 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
GuidRaw = RandomGuid(),
Values = new Dictionary<Guid, string>
{
[RandomGuid()] = $"name{i}_1",
[RandomGuid()] = $"name{i}_1",
[RandomGuid()] = "Key"
}
};
@ -149,20 +149,22 @@ namespace Squidex.Domain.Apps.Entities.Backup
targetEvents.Add(@event);
});
for (var i = 0; i < targetEvents.Count; i++)
void CompareGuid(Guid source, Guid target)
{
var tgt = targetEvents[i].Event.To<MyEvent>();
var src = sourceEvents[i].Event.To<MyEvent>();
Assert.Equal(src.Payload.GuidRaw, reader.OldGuid(tgt.Payload.GuidRaw));
Assert.Equal(src.Payload.GuidNamed.Id, reader.OldGuid(tgt.Payload.GuidNamed.Id));
Assert.Equal(source, reader.OldGuid(target));
Assert.NotEqual(source, target);
}
Assert.NotEqual(src.Payload.GuidRaw, tgt.Payload.GuidRaw);
Assert.NotEqual(src.Payload.GuidNamed.Id, tgt.Payload.GuidNamed.Id);
for (var i = 0; i < targetEvents.Count; i++)
{
var source = targetEvents[i].Event.To<MyEvent>();
Assert.Equal(src.Headers.GetGuid("Id"), reader.OldGuid(tgt.Headers.GetGuid("Id")));
var target = sourceEvents[i].Event.To<MyEvent>();
Assert.NotEqual(src.Headers.GetGuid("Id"), tgt.Headers.GetGuid("Id"));
CompareGuid(target.Payload.Values.First().Key, source.Payload.Values.First().Key);
CompareGuid(target.Payload.GuidRaw, source.Payload.GuidRaw);
CompareGuid(target.Payload.GuidNamed.Id, source.Payload.GuidNamed.Id);
CompareGuid(target.Headers.GetGuid("Id"), source.Headers.GetGuid("Id"));
}
}
}

19
tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs → tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs

@ -7,11 +7,12 @@
using System.Linq;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.EventSourcing
{
public class EnvelopeHeaderTests
public class EnvelopeHeadersTests
{
[Fact]
public void Should_create_headers()
@ -24,7 +25,8 @@ namespace Squidex.Infrastructure.EventSourcing
[Fact]
public void Should_create_headers_as_copy()
{
var source = new JsonObject().Add("Key1", 123);
var source = JsonValue.Object().Add("Key1", 123);
var headers = new EnvelopeHeaders(source);
CompareHeaders(headers, source);
@ -33,14 +35,23 @@ namespace Squidex.Infrastructure.EventSourcing
[Fact]
public void Should_clone_headers()
{
var source = new JsonObject().Add("Key1", 123);
var headers = new EnvelopeHeaders(source);
var headers = new EnvelopeHeaders(JsonValue.Object().Add("Key1", 123));
var clone = headers.Clone();
CompareHeaders(headers, clone);
}
[Fact]
public void Should_serialize_and_deserialize()
{
var value = new EnvelopeHeaders(JsonValue.Object().Add("Key1", 123));
var deserialized = value.SerializeAndDeserialize();
CompareHeaders(deserialized, value);
}
private static void CompareHeaders(JsonObject lhs, JsonObject rhs)
{
foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct())

44
tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs

@ -5,9 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NodaTime;
using Xunit;
namespace Squidex.Infrastructure.Json.Objects
@ -119,6 +121,10 @@ namespace Squidex.Infrastructure.Json.Objects
public void Should_cache_null()
{
Assert.Same(JsonValue.Null, JsonValue.Create((string)null));
Assert.Same(JsonValue.Null, JsonValue.Create((bool?)null));
Assert.Same(JsonValue.Null, JsonValue.Create((double?)null));
Assert.Same(JsonValue.Null, JsonValue.Create((object)null));
Assert.Same(JsonValue.Null, JsonValue.Create((Instant?)null));
}
[Fact]
@ -145,6 +151,28 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Same(JsonValue.Zero, JsonValue.Create(0));
}
[Fact]
public void Should_boolean_from_object()
{
Assert.Equal(JsonValue.True, JsonValue.Create((object)true));
}
[Fact]
public void Should_create_value_from_instant()
{
var instant = Instant.FromUnixTimeSeconds(4123125455);
Assert.Equal(instant.ToString(), JsonValue.Create(instant).ToString());
}
[Fact]
public void Should_create_value_from_instant_object()
{
var instant = Instant.FromUnixTimeSeconds(4123125455);
Assert.Equal(instant.ToString(), JsonValue.Create((object)instant).ToString());
}
[Fact]
public void Should_create_array()
{
@ -226,10 +254,12 @@ namespace Squidex.Infrastructure.Json.Objects
{
var numbers = new[]
{
JsonValue.Create(1.0f),
JsonValue.Create(1.0),
JsonValue.Create(1L),
JsonValue.Create(1)
JsonValue.Create(12.0f),
JsonValue.Create(12.0),
JsonValue.Create(12L),
JsonValue.Create(12),
JsonValue.Create((object)12.0d),
JsonValue.Create((double?)12.0d)
};
Assert.Single(numbers.Distinct());
@ -317,5 +347,11 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Equal(kvps, obj.ToArray());
Assert.Equal(kvps, ((IEnumerable)obj).OfType<KeyValuePair<string, IJsonValue>>().ToArray());
}
[Fact]
public void Should_throw_exception_when_creation_value_from_invalid_type()
{
Assert.Throws<ArgumentException>(() => JsonValue.Create(Guid.Empty));
}
}
}

3
tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

@ -8,7 +8,6 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
@ -27,8 +26,8 @@ namespace Squidex.Infrastructure.TestHelpers
ContractResolver = new ConverterContractResolver(
new ClaimsPrincipalConverter(),
new InstantConverter(),
new JsonValueConverter(),
new EnvelopeHeadersConverter(),
new JsonValueConverter(),
new LanguageConverter(),
new NamedGuidIdConverter(),
new NamedLongIdConverter(),

1
tools/Migrate_01/Migrations/ConvertEventStore.cs

@ -12,7 +12,6 @@ using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.MongoDb;
namespace Migrate_01.Migrations
{

Loading…
Cancel
Save