From 6a3e4e3e5e8e9bc5769f8f160eeb4e51ce8df9ff Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 26 Sep 2021 17:10:42 +0800 Subject: [PATCH] Introduce `AbpMongoDbDateTimeSerializer`. `MongoDb` will normalize the datetime based on `AbpClockOptions`. --- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 24 +++--- .../Volo/Abp/MongoDB/AbpMongoDbContext.cs | 2 + .../MongoDB/AbpMongoDbDateTimeSerializer.cs | 43 ++++++++++ .../Volo/Abp/MongoDB/MongoModelBuilder.cs | 60 +++++++++++++- .../ModelBindingController_Tests.cs | 18 ++--- .../AbpEntityFrameworkCoreTestModule.cs | 4 - .../UnitTestModelCacheKeyFactory.cs | 17 ++++ .../AbpDateTimeValueConverter_Tests.cs | 9 --- .../EFCore_DateTimeKindTests.cs | 66 +++++++++++++++ .../EntityFrameworkCore/TestAppDbContext.cs | 7 ++ .../Serializer/MongoDB_DateTimeKind_Tests.cs | 81 +++++++++++++++++++ .../Abp/TestApp/MongoDb/PersonRepository.cs | 30 +++++++ .../AbpDateTimeValueConverter_Tests.cs | 73 ----------------- .../Abp/TestApp/Testing/DateTimeKind_Tests.cs | 46 +++++++++++ 14 files changed, 371 insertions(+), 109 deletions(-) create mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs create mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/UnitTestModelCacheKeyFactory.cs delete mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpDateTimeValueConverter_Tests.cs create mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs create mode 100644 framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs create mode 100644 framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/PersonRepository.cs delete mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/AbpDateTimeValueConverter_Tests.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DateTimeKind_Tests.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 588b052140..f873c2356e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -61,9 +61,9 @@ namespace Volo.Abp.EntityFrameworkCore public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService(); public IClock Clock => LazyServiceProvider.LazyGetRequiredService(); - + public IDistributedEventBus DistributedEventBus => LazyServiceProvider.LazyGetRequiredService(); - + public ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService(); public ILogger> Logger => LazyServiceProvider.LazyGetService>>(NullLogger>.Instance); @@ -167,9 +167,9 @@ namespace Volo.Abp.EntityFrameworkCore } var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); - + PublishEntityEvents(eventReport); - + if (entityChangeList != null) { EntityHistoryHelper.UpdateChangeList(entityChangeList); @@ -285,11 +285,11 @@ namespace Volo.Abp.EntityFrameworkCore } } } - + private void PublishEventsForTrackedEntity(EntityEntry entry) { switch (entry.State) - { + { case EntityState.Added: EntityChangeEventHelper.PublishEntityCreatingEvent(entry.Entity); EntityChangeEventHelper.PublishEntityCreatedEvent(entry.Entity); @@ -308,7 +308,7 @@ namespace Volo.Abp.EntityFrameworkCore EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); } } - + break; case EntityState.Deleted: EntityChangeEventHelper.PublishEntityDeletingEvent(entry.Entity); @@ -324,11 +324,11 @@ namespace Volo.Abp.EntityFrameworkCore ApplyAbpConcepts(entry); } } - + protected virtual EntityEventReport CreateEventReport() { var eventReport = new EntityEventReport(); - + foreach (var entry in ChangeTracker.Entries().ToList()) { var generatesDomainEventsEntity = entry.Entity as IGeneratesDomainEvents; @@ -369,7 +369,7 @@ namespace Volo.Abp.EntityFrameworkCore return eventReport; } - + protected virtual void ApplyAbpConcepts(EntityEntry entry) { switch (entry.State) @@ -638,7 +638,7 @@ namespace Volo.Abp.EntityFrameworkCore !typeof(TEntity).IsDefined(typeof(OwnedAttribute), true) && !mutableEntityType.IsOwned()) { - if (LazyServiceProvider == null || Clock == null || !Clock.SupportsMultipleTimezone) + if (LazyServiceProvider == null || Clock == null) { return; } @@ -650,7 +650,7 @@ namespace Volo.Abp.EntityFrameworkCore (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?)) && property.CanWrite && - !property.IsDefined(typeof(DisableDateTimeNormalizationAttribute), true) + ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(property) == null ).ToList(); dateTimePropertyInfos.ForEach(property => diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs index 5e7eb84111..93633e3cc8 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs @@ -6,6 +6,8 @@ namespace Volo.Abp.MongoDB { public abstract class AbpMongoDbContext : IAbpMongoDbContext, ITransientDependency { + public IAbpLazyServiceProvider LazyServiceProvider { get; set; } + public IMongoModelSource ModelSource { get; set; } public IMongoClient Client { get; private set; } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs new file mode 100644 index 0000000000..c3e9efba5a --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs @@ -0,0 +1,43 @@ +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + +namespace Volo.Abp.MongoDB +{ + public class AbpMongoDbDateTimeSerializer : DateTimeSerializer + { + protected DateTimeKind DateTimeKind { get; set; } + protected bool DisableDateTimeNormalization{ get; set; } + + public AbpMongoDbDateTimeSerializer(DateTimeKind dateTimeKind , bool disableDateTimeNormalization) + { + DateTimeKind = dateTimeKind; + DisableDateTimeNormalization = disableDateTimeNormalization; + } + + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value) + { + context.Writer.WriteDateTime(DisableDateTimeNormalization + ? ToMillisecondsSinceEpoch(value) + : ToMillisecondsSinceEpoch(DateTime.SpecifyKind(value, DateTimeKind))); + } + + public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + var dateTime = new BsonDateTime(context.Reader.ReadDateTime()).ToUniversalTime(); + return DateTime.SpecifyKind(dateTime, DisableDateTimeNormalization ? DateTimeKind.Unspecified : DateTimeKind); + } + + private static long ToMillisecondsSinceEpoch(DateTime dateTime) + { + return (dateTime - BsonConstants.UnixEpoch).Ticks / 10000L; + } + + // For unit testing. + internal void SetDateTimeKind(DateTimeKind dateTimeKind) + { + DateTimeKind = dateTimeKind; + } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs index 6d8706d7ee..1f155fc21d 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Reflection; +using Microsoft.Extensions.Options; using MongoDB.Bson; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; -using Volo.Abp.Domain.Entities; +using Volo.Abp.Reflection; +using Volo.Abp.Timing; namespace Volo.Abp.MongoDB { @@ -20,10 +24,12 @@ namespace Volo.Abp.MongoDB _entityModelBuilders = new Dictionary(); } - public MongoDbContextModel Build(AbpMongoDbContext dbContext) + public virtual MongoDbContextModel Build(AbpMongoDbContext dbContext) { lock (SyncObj) { + var clockOptions = dbContext.LazyServiceProvider?.LazyGetService>(); + var entityModels = _entityModelBuilders .Select(x => x.Value) .Cast() @@ -34,6 +40,33 @@ namespace Volo.Abp.MongoDB foreach (var entityModel in entityModels.Values) { var map = entityModel.As().GetMap(); + + if (clockOptions != null) + { + var dateTimePropertyInfos = entityModel.EntityType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .Where(property => + (property.PropertyType == typeof(DateTime) || + property.PropertyType == typeof(DateTime?)) && + property.CanWrite + ).ToList(); + + dateTimePropertyInfos.ForEach(property => + { + var disableDateTimeNormalization = + entityModel.EntityType.IsDefined(typeof(DisableDateTimeNormalizationAttribute), true) || + ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(property) != null; + + if (property.PropertyType == typeof(DateTime?)) + { + map.MapProperty(property.Name).SetSerializer(new NullableSerializer().WithSerializer(new AbpMongoDbDateTimeSerializer(clockOptions.Value.Kind, disableDateTimeNormalization))); + } + else + { + map.MapProperty(property.Name).SetSerializer(new AbpMongoDbDateTimeSerializer(clockOptions.Value.Kind, disableDateTimeNormalization)); + } + }); + } + if (!BsonClassMap.IsClassMapRegistered(map.ClassType)) { BsonClassMap.RegisterClassMap(map); @@ -52,6 +85,29 @@ namespace Volo.Abp.MongoDB { var map = new BsonClassMap(baseClass); map.ConfigureAbpConventions(); + + if (clockOptions != null) + { + var dateTimePropertyInfos = baseClass.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .Where(property => + (property.PropertyType == typeof(DateTime) || + property.PropertyType == typeof(DateTime?)) && + property.CanWrite + ).ToList(); + + dateTimePropertyInfos.ForEach(property => + { + if (property.PropertyType == typeof(DateTime?)) + { + map.MapProperty(property.Name).SetSerializer(new NullableSerializer().WithSerializer(new AbpMongoDbDateTimeSerializer(clockOptions.Value.Kind, false))); + } + else + { + map.MapProperty(property.Name).SetSerializer(new AbpMongoDbDateTimeSerializer(clockOptions.Value.Kind, false)); + } + }); + } + BsonClassMap.RegisterClassMap(map); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs index fcce316772..8ae0cfd9cb 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase { - protected DateTimeKind DateTimeKind { get; set; } + protected DateTimeKind Kind { get; set; } protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) { @@ -34,7 +34,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - resultAsString.ShouldBe(DateTimeKind.ToString().ToLower()); + resultAsString.ShouldBe(Kind.ToString().ToLower()); } [Fact] @@ -45,7 +45,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - resultAsString.ShouldBe(DateTimeKind.ToString().ToLower()); + resultAsString.ShouldBe(Kind.ToString().ToLower()); } [Fact] @@ -89,7 +89,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding var resultAsString = await response.Content.ReadAsStringAsync(); //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times - resultAsString.ShouldBe($"utc_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_utc"); + resultAsString.ShouldBe($"utc_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_utc"); } [Fact] @@ -111,7 +111,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - resultAsString.ShouldBe($"local_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_local"); + resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local"); } } @@ -119,8 +119,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) { - DateTimeKind = DateTimeKind.Utc; - services.Configure(x => x.Kind = DateTimeKind); + Kind = DateTimeKind.Utc; + services.Configure(x => x.Kind = Kind); base.ConfigureServices(context, services); } @@ -130,8 +130,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) { - DateTimeKind = DateTimeKind.Local; - services.Configure(x => x.Kind = DateTimeKind); + Kind = DateTimeKind.Local; + services.Configure(x => x.Kind = Kind); base.ConfigureServices(context, services); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index 4f8faef65e..49713b3c1d 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -13,7 +12,6 @@ using Volo.Abp.Modularity; using Volo.Abp.TestApp; using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.EntityFrameworkCore; -using Volo.Abp.Timing; namespace Volo.Abp.EntityFrameworkCore { @@ -55,8 +53,6 @@ namespace Volo.Abp.EntityFrameworkCore abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); }); }); - - Configure(options => options.Kind = DateTimeKind.Utc); } public override void OnPreApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/UnitTestModelCacheKeyFactory.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/UnitTestModelCacheKeyFactory.cs new file mode 100644 index 0000000000..0686bfb614 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/UnitTestModelCacheKeyFactory.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Volo.Abp.EntityFrameworkCore +{ + /// + /// Avoid unit test caching the configure of the entity. + /// OnModelCreating will be executed multiple times + /// + public class UnitTestModelCacheKeyFactory : IModelCacheKeyFactory + { + public object Create(DbContext context) + { + return context; + } + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpDateTimeValueConverter_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpDateTimeValueConverter_Tests.cs deleted file mode 100644 index c2793b3b67..0000000000 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpDateTimeValueConverter_Tests.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.TestApp.Testing; - -namespace Volo.Abp.EntityFrameworkCore.ValueConverters -{ - public class AbpDateTimeValueConverter_Tests : AbpDateTimeValueConverter_Tests - { - - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs new file mode 100644 index 0000000000..8a39819a2e --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.Testing; +using Volo.Abp.Timing; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.ValueConverters +{ + public abstract class EFCore_DateTimeKindTests : DateTimeKind_Tests + { + [Fact] + public async Task DateTime_Kind_Should_Be_Normalized_In_View_Query_Test() + { + var personName = "bob lee"; + await PersonRepository.InsertAsync(new Person(Guid.NewGuid(), personName, 18) + { + Birthday = DateTime.Parse("2020-01-01 00:00:00"), + LastActive = DateTime.Parse("2020-01-01 00:00:00"), + }, true); + + var person = await PersonRepository.GetViewAsync(personName); + + person.ShouldNotBeNull(); + person.CreationTime.Kind.ShouldBe(Kind); + + person.Birthday.ShouldNotBeNull(); + person.Birthday.Value.Kind.ShouldBe(Kind); + person.Birthday.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); + + //LastActive DisableDateTimeNormalization + person.LastActive.ShouldNotBeNull(); + person.LastActive.Value.Kind.ShouldBe(DateTimeKind.Unspecified); + person.LastActive.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); + } + } + + public class DateTimeKindTests : EFCore_DateTimeKindTests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Unspecified; + services.Configure(x => x.Kind = Kind); + } + } + + public class DateTimeKindTests_Local : EFCore_DateTimeKindTests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Local; + services.Configure(x => x.Kind = Kind); + } + } + + public class DateTimeKindTests_Utc : EFCore_DateTimeKindTests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Utc; + services.Configure(x => x.Kind = Kind); + } + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 014c9c42db..f7fd1b046d 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -1,5 +1,6 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; @@ -34,6 +35,12 @@ namespace Volo.Abp.TestApp.EntityFrameworkCore } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.ReplaceService(); + base.OnConfiguring(optionsBuilder); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Owned(); diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs new file mode 100644 index 0000000000..f691145d60 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs @@ -0,0 +1,81 @@ +using System; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; +using Volo.Abp.TestApp.Testing; +using Volo.Abp.Timing; +using Xunit; + +namespace Volo.Abp.MongoDB.Serializer +{ + [Collection(MongoTestCollection.Name)] + public abstract class MongoDB_DateTimeKind_Tests : DateTimeKind_Tests, IDisposable + { + protected override void AfterAddApplication(IServiceCollection services) + { + // MongoDB uses static properties to store the mapping information, + // We must reconfigure it in the new unit test. + foreach (var registeredClassMap in BsonClassMap.GetRegisteredClassMaps()) + { + var frozen = registeredClassMap.GetType().BaseType?.GetField("_frozen", BindingFlags.NonPublic | BindingFlags.Instance); + frozen?.SetValue(registeredClassMap, false); + + foreach (var declaredMemberMap in registeredClassMap.DeclaredMemberMaps) + { + var serializer = declaredMemberMap.GetSerializer(); + switch (serializer) + { + case AbpMongoDbDateTimeSerializer dateTimeSerializer: + dateTimeSerializer.SetDateTimeKind(Kind); + break; + case NullableSerializer nullableSerializer: + { + var lazySerializer = nullableSerializer.GetType() + ?.GetField("_lazySerializer", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(serializer)?.As>>(); + + if (lazySerializer?.Value is AbpMongoDbDateTimeSerializer dateTimeSerializer) + { + dateTimeSerializer.SetDateTimeKind(Kind); + } + break; + } + } + } + + frozen?.SetValue(registeredClassMap, true); + } + } + } + + public class DateTimeKindTests_Unspecified : MongoDB_DateTimeKind_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Unspecified; + services.Configure(x => x.Kind = Kind); + base.AfterAddApplication(services); + } + } + + public class DateTimeKindTests_Local : MongoDB_DateTimeKind_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Local; + services.Configure(x => x.Kind = Kind); + base.AfterAddApplication(services); + } + } + + public class DateTimeKindTests_Utc : MongoDB_DateTimeKind_Tests + { + protected override void AfterAddApplication(IServiceCollection services) + { + Kind = DateTimeKind.Utc; + services.Configure(x => x.Kind = Kind); + base.AfterAddApplication(services); + } + } +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/PersonRepository.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/PersonRepository.cs new file mode 100644 index 0000000000..3ed46288f1 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/PersonRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using MongoDB.Driver; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using Volo.Abp.TestApp.Domain; + +namespace Volo.Abp.TestApp.MongoDB +{ + public class PersonRepository : MongoDbRepository, IPersonRepository + { + public PersonRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public async Task GetViewAsync(string name) + { + var person = await (await (await GetCollectionAsync()).FindAsync(x => x.Name == name)).FirstOrDefaultAsync(); + return new PersonView() + { + Name = person.Name, + CreationTime = person.CreationTime, + Birthday = person.Birthday, + LastActive = person.LastActive + }; + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/AbpDateTimeValueConverter_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/AbpDateTimeValueConverter_Tests.cs deleted file mode 100644 index e36225e53c..0000000000 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/AbpDateTimeValueConverter_Tests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Modularity; -using Volo.Abp.TestApp.Domain; -using Volo.Abp.Timing; -using Xunit; - -namespace Volo.Abp.TestApp.Testing -{ - public abstract class AbpDateTimeValueConverter_Tests : TestAppTestBase - where TStartupModule : IAbpModule - { - private readonly IPersonRepository _personRepository; - - protected AbpDateTimeValueConverter_Tests() - { - _personRepository = GetRequiredService(); - } - - [Fact] - public async Task DateTime_Kind_Should_Be_Normalized_To_UTC_Test() - { - var personId = Guid.Parse("4125582e-d100-4c27-aa84-e4de85830dca"); - await _personRepository.InsertAsync(new Person(personId, "bob lee", 18) - { - Birthday = DateTime.Parse("2020-01-01 00:00:00"), - LastActive = DateTime.Parse("2020-01-01 00:00:00"), - }, true); - - var person = await _personRepository.GetAsync(personId); - - person.ShouldNotBeNull(); - person.CreationTime.Kind.ShouldBe(DateTimeKind.Utc); - - person.Birthday.ShouldNotBeNull(); - person.Birthday.Value.Kind.ShouldBe(DateTimeKind.Utc); - person.Birthday.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); - - //LastActive DisableDateTimeNormalization - person.LastActive.ShouldNotBeNull(); - person.LastActive.Value.Kind.ShouldBe(DateTimeKind.Unspecified); - person.LastActive.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); - } - - [Fact] - public async Task DateTime_Kind_Should_Be_Normalized_To_UTC_View_Test() - { - var personName = "bob lee"; - await _personRepository.InsertAsync(new Person(Guid.NewGuid(), personName, 18) - { - Birthday = DateTime.Parse("2020-01-01 00:00:00"), - LastActive = DateTime.Parse("2020-01-01 00:00:00"), - }, true); - - var person = await _personRepository.GetViewAsync(personName); - - person.ShouldNotBeNull(); - person.CreationTime.Kind.ShouldBe(DateTimeKind.Utc); - - person.Birthday.ShouldNotBeNull(); - person.Birthday.Value.Kind.ShouldBe(DateTimeKind.Utc); - person.Birthday.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); - - //LastActive DisableDateTimeNormalization - person.LastActive.ShouldNotBeNull(); - person.LastActive.Value.Kind.ShouldBe(DateTimeKind.Unspecified); - person.LastActive.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DateTimeKind_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DateTimeKind_Tests.cs new file mode 100644 index 0000000000..2e13db34b8 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DateTimeKind_Tests.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.TestApp.Testing +{ + public abstract class DateTimeKind_Tests : TestAppTestBase + where TStartupModule : IAbpModule + { + protected IPersonRepository PersonRepository { get; } + protected DateTimeKind Kind { get; set; } + + protected DateTimeKind_Tests() + { + PersonRepository = GetRequiredService(); + } + + [Fact] + public async Task DateTime_Kind_Should_Be_Normalized_Test() + { + var personId = Guid.NewGuid(); + await PersonRepository.InsertAsync(new Person(personId, "bob lee", 18) + { + Birthday = DateTime.Parse("2020-01-01 00:00:00"), + LastActive = DateTime.Parse("2020-01-01 00:00:00"), + }, true); + + var person = await PersonRepository.GetAsync(personId); + + person.ShouldNotBeNull(); + person.CreationTime.Kind.ShouldBe(Kind); + + person.Birthday.ShouldNotBeNull(); + person.Birthday.Value.Kind.ShouldBe(Kind); + person.Birthday.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); + + //LastActive DisableDateTimeNormalization + person.LastActive.ShouldNotBeNull(); + person.LastActive.Value.Kind.ShouldBe(DateTimeKind.Unspecified); + person.LastActive.Value.ToString("yyy-MM-dd HH:mm:ss").ShouldBe("2020-01-01 00:00:00"); + } + } +}