From 522ad53026f02f91cca17ece9628b7fdc10fdd7f Mon Sep 17 00:00:00 2001 From: Matthias Gernand Date: Wed, 12 Jun 2024 17:24:39 +0200 Subject: [PATCH] Fixed MongoDB query support. (#48) --- GitVersion.yml | 2 +- .../EntityTypeBuilderExtensions.cs | 19 +++++--- .../StronglyTypedIdConverter.cs | 2 +- .../CompositeContractResolver.cs | 1 + .../StronglyTypedIdConverter.cs | 2 +- .../StronglyTypedIdConverter.cs | 2 +- .../StronglyTypedIdSerializer.cs | 2 +- .../StronglyTypedIdConverter.cs | 2 +- src/Fluxera.StronglyTypedId/Guard.cs | 3 +- .../IStronglyTypedId.cs | 20 --------- .../StronglyTypedId.cs | 44 +++++++++---------- .../StronglyTypedIdConverter.cs | 2 +- .../QueryTests.cs | 18 ++++++-- .../QueryTests.cs | 40 +++++++++++------ .../QueryTests.cs | 31 +++++++++---- 15 files changed, 108 insertions(+), 82 deletions(-) delete mode 100644 src/Fluxera.StronglyTypedId/IStronglyTypedId.cs diff --git a/GitVersion.yml b/GitVersion.yml index cc09407..ab3b403 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1 +1 @@ -next-version: 8.2.0 \ No newline at end of file +next-version: 8.3.0 \ No newline at end of file diff --git a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/EntityTypeBuilderExtensions.cs b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/EntityTypeBuilderExtensions.cs index 002ba81..1f38ec5 100644 --- a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/EntityTypeBuilderExtensions.cs +++ b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/EntityTypeBuilderExtensions.cs @@ -30,15 +30,20 @@ public static void UseStronglyTypedId(this EntityTypeBuilder entityTypeBuilder) foreach(PropertyInfo property in properties) { - Type idType = property.PropertyType; - Type valueType = idType.GetStronglyTypedIdValueType(); + Type originalMemberType = property.PropertyType; + Type memberType = Nullable.GetUnderlyingType(originalMemberType) ?? originalMemberType; - Type converterTypeTemplate = typeof(StronglyTypedIdConverter<,>); - Type converterType = converterTypeTemplate.MakeGenericType(idType, valueType); + if(memberType.IsStronglyTypedId()) + { + Type valueType = memberType.GetStronglyTypedIdValueType(); - entityTypeBuilder - .Property(property.Name) - .HasConversion(converterType); + Type converterTypeTemplate = typeof(StronglyTypedIdConverter<,>); + Type converterType = converterTypeTemplate.MakeGenericType(memberType, valueType); + + entityTypeBuilder + .Property(property.Name) + .HasConversion(converterType); + } } } } diff --git a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs index d00a712..54334ea 100644 --- a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs +++ b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs @@ -8,7 +8,7 @@ [PublicAPI] public sealed class StronglyTypedIdConverter : ValueConverter where TStronglyTypedId : StronglyTypedId - where TValue : IComparable + where TValue : IComparable, IComparable, IEquatable { /// /// Initializes a new instance of the type. diff --git a/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs b/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs index 8b077fa..1e60d4b 100644 --- a/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs +++ b/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs @@ -28,6 +28,7 @@ public JsonContract ResolveContract(Type type) /// public IEnumerator GetEnumerator() { + // ReSharper disable once NotDisposedResourceIsReturned return this.contractResolvers.GetEnumerator(); } diff --git a/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs index 86762fb..a65a9d2 100644 --- a/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs +++ b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs @@ -8,7 +8,7 @@ [PublicAPI] public sealed class StronglyTypedIdConverter : JsonConverter where TStronglyTypedId : StronglyTypedId - where TValue : IComparable + where TValue : IComparable, IComparable, IEquatable { /// public override bool CanWrite => true; diff --git a/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs index 6a0aa1b..71b2413 100644 --- a/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs +++ b/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs @@ -29,7 +29,7 @@ public static Func Serialize(Type stronglyTypedIdType) } /// - /// Deserialize a ID instance from the given bson value. + /// Deserialize an ID instance from the given bson value. /// /// /// diff --git a/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdSerializer.cs b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdSerializer.cs index e8935df..71cf018 100644 --- a/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdSerializer.cs +++ b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdSerializer.cs @@ -14,7 +14,7 @@ [PublicAPI] public sealed class StronglyTypedIdSerializer : SerializerBase where TStronglyTypedId : StronglyTypedId - where TValue : IComparable + where TValue : IComparable, IComparable, IEquatable { private readonly IBsonSerializer idValueSerializer; diff --git a/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs index c81ce78..a44eb66 100644 --- a/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs +++ b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs @@ -9,7 +9,7 @@ namespace Fluxera.StronglyTypedId.SystemTextJson [PublicAPI] public sealed class StronglyTypedIdConverter : JsonConverter where TStronglyTypedId : StronglyTypedId - where TValue : IComparable + where TValue : IComparable, IComparable, IEquatable { /// public override void Write(Utf8JsonWriter writer, TStronglyTypedId value, JsonSerializerOptions options) diff --git a/src/Fluxera.StronglyTypedId/Guard.cs b/src/Fluxera.StronglyTypedId/Guard.cs index 3cddc92..28dc482 100644 --- a/src/Fluxera.StronglyTypedId/Guard.cs +++ b/src/Fluxera.StronglyTypedId/Guard.cs @@ -1,4 +1,5 @@ -namespace Fluxera.StronglyTypedId +// ReSharper disable PossibleInvalidOperationException +namespace Fluxera.StronglyTypedId { using JetBrains.Annotations; using System; diff --git a/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs b/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs deleted file mode 100644 index c851aa9..0000000 --- a/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Fluxera.StronglyTypedId -{ - using System; - using JetBrains.Annotations; - - /// - /// A contract for strongly-typed IDs. - /// - /// - /// - [PublicAPI] - public interface IStronglyTypedId : IComparable, IEquatable - where TKey : notnull, IComparable - { - /// - /// Gets the underlying value of the strongly-typed ID. - /// - public TKey Value { get; } - } -} diff --git a/src/Fluxera.StronglyTypedId/StronglyTypedId.cs b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs index 726a793..9cfd768 100644 --- a/src/Fluxera.StronglyTypedId/StronglyTypedId.cs +++ b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs @@ -13,9 +13,9 @@ /// The type of the IDs value. [PublicAPI] [TypeConverter(typeof(StronglyTypedIdConverter))] - public abstract class StronglyTypedId : IStronglyTypedId + public abstract class StronglyTypedId : IComparable, IEquatable where TStronglyTypedId : StronglyTypedId - where TValue : notnull, IComparable + where TValue : IComparable, IComparable, IEquatable { /// /// To ensure hashcode uniqueness, a carefully selected random number multiplier @@ -139,6 +139,25 @@ public bool Equals(TStronglyTypedId other) return this.Equals(other as object); } + /// + public sealed override bool Equals(object obj) + { + if(obj is null) + { + return false; + } + + if(object.ReferenceEquals(this, obj)) + { + return true; + } + + StronglyTypedId other = obj as StronglyTypedId; + return other != null + && this.GetType() == other.GetType() + && this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + /// public int CompareTo(TStronglyTypedId other) { @@ -190,25 +209,6 @@ public static explicit operator StronglyTypedId(TValue return Create(value); } - /// - public sealed override bool Equals(object obj) - { - if(obj is null) - { - return false; - } - - if(object.ReferenceEquals(this, obj)) - { - return true; - } - - StronglyTypedId other = obj as StronglyTypedId; - return other != null - && this.GetType() == other.GetType() - && this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); - } - /// public sealed override int GetHashCode() { @@ -238,7 +238,7 @@ public sealed override string ToString() } /// - /// Gets all components of the value object that are used for equality.
+ /// Gets all components of the strongly-typed ID that are used for equality.
/// The default implementation get all properties via reflection. One /// can at any time override this behavior with a manual or custom implementation. ///
diff --git a/src/Fluxera.StronglyTypedId/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId/StronglyTypedIdConverter.cs index 764680b..dc55be7 100644 --- a/src/Fluxera.StronglyTypedId/StronglyTypedIdConverter.cs +++ b/src/Fluxera.StronglyTypedId/StronglyTypedIdConverter.cs @@ -51,7 +51,7 @@ private static TypeConverter CreateActualConverter(Type stronglyTypedIdType) internal sealed class StronglyTypedIdConverter : TypeConverter where TStronglyTypedId : StronglyTypedId - where TValue : notnull, IComparable + where TValue : IComparable, IComparable, IEquatable { // ReSharper disable once StaticMemberInGenericType private static TypeConverter IdValueConverter { get; } = GetIdValueConverter(); diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/QueryTests.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/QueryTests.cs index 311aa4e..c52994f 100644 --- a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/QueryTests.cs +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/QueryTests.cs @@ -14,20 +14,20 @@ public class QueryTests private TestDbContext context; #pragma warning restore NUnit1032 - [SetUp] + [OneTimeSetUp] public void SetUp() { this.context = DbContextFactory.Generate(); } - [TearDown] + [OneTimeTearDown] public void TearDown() { this.context?.Dispose(); } [Test] - public async Task ShouldFindByPrimitiveValueObject() + public async Task ShouldFindByStronglyTypedId() { Person linqFilterResult = await this.context .Set() @@ -36,5 +36,17 @@ public async Task ShouldFindByPrimitiveValueObject() linqFilterResult.Should().NotBeNull(); } + + [Ignore("Fix this later")] + [Test] + public async Task ShouldFindByValue() + { + Person linqFilterResult = await this.context + .Set() + .Where(x => x.Id == "12345") + .FirstOrDefaultAsync(); + + linqFilterResult.Should().NotBeNull(); + } } } diff --git a/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/QueryTests.cs b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/QueryTests.cs index e945f93..e784600 100644 --- a/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/QueryTests.cs +++ b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/QueryTests.cs @@ -15,17 +15,27 @@ public class QueryTests { private LiteDatabaseAsync database; + private ILiteCollectionAsync collection; - [SetUp] - public void SetUp() + [OneTimeSetUp] + public async Task SetUp() { BsonMapper.Global.Entity().Id(x => x.Id); BsonMapper.Global.UseStronglyTypedId(); this.database = new LiteDatabaseAsync($"{Guid.NewGuid():N}.db"); + this.collection = this.database.GetCollection(); + + Person person = new Person + { + Id = PersonId.Create("fcd5f4f9753a4284a2d0f500b9b23cf8"), + Name = "Tester" + }; + + await this.collection.InsertAsync(person); } - [TearDown] + [OneTimeTearDown] public void TearDown() { this.database?.Dispose(); @@ -34,20 +44,22 @@ public void TearDown() [Test] public async Task ShouldFindByStronglyTypedID() { - ILiteCollectionAsync collection = this.database.GetCollection(); - - Person person = new Person - { - Id = PersonId.Create(Guid.NewGuid().ToString("N")), - Name = "Tester" - }; - - await collection.InsertAsync(person); - Person linqFilterResult = await collection .AsQueryable() - .Where(x => x.Id == person.Id) + .Where(x => x.Id == PersonId.Create("fcd5f4f9753a4284a2d0f500b9b23cf8")) + .FirstOrDefaultAsync(); + linqFilterResult.Should().NotBeNull(); + } + + [Ignore("Fix this later")] + [Test] + public async Task ShouldFindByValue() + { + Person linqFilterResult = await this.collection + .AsQueryable() + .Where(x => x.Id == "6669b52802357c9886f6d24f") .FirstOrDefaultAsync(); + linqFilterResult.Should().NotBeNull(); } } diff --git a/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/QueryTests.cs b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/QueryTests.cs index e824374..9a29836 100644 --- a/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/QueryTests.cs +++ b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/QueryTests.cs @@ -1,10 +1,8 @@ namespace Fluxera.StronglyTypedId.MongoDB.UnitTests { - using System; using System.Threading.Tasks; using FluentAssertions; using Fluxera.StronglyTypedId.MongoDB.UnitTests.Model; - using global::MongoDB.Bson; using global::MongoDB.Bson.Serialization.Conventions; using global::MongoDB.Driver; using global::MongoDB.Driver.Linq; @@ -13,8 +11,10 @@ [TestFixture] public class QueryTests { - [Test] - public async Task ShouldFindByStronglyTypedID() + private IMongoCollection collection; + + [OneTimeSetUp] + public async Task SetUp() { ConventionPack pack = []; pack.UseStronglyTypedId(); @@ -22,21 +22,36 @@ public async Task ShouldFindByStronglyTypedID() IMongoClient client = new MongoClient(GlobalFixture.ConnectionString); IMongoDatabase database = client.GetDatabase(GlobalFixture.Database); - IMongoCollection collection = database.GetCollection("People"); + this.collection = database.GetCollection("People"); Person person = new Person { - Id = PersonId.Create(ObjectId.GenerateNewId().ToString()), + Id = PersonId.Create("6669b52802357c9886f6d24f"), Name = "Tester" }; await collection.InsertOneAsync(person); + } - Person linqFilterResult = await collection + [Test] + public async Task ShouldFindByStronglyTypedID() + { + Person linqFilterResult = await this.collection .AsQueryable() - .Where(x => x.Id == person.Id) + .Where(x => x.Id == PersonId.Create("6669b52802357c9886f6d24f")) .FirstOrDefaultAsync(); linqFilterResult.Should().NotBeNull(); } + + [Test] + public async Task ShouldFindByValue() + { + Person linqFilterResult = await this.collection + .AsQueryable() + .Where(x => x.Id == "6669b52802357c9886f6d24f") + .FirstOrDefaultAsync(); + + linqFilterResult.Should().NotBeNull(); + } } }