From ce05e26b21d74dad19ab40d2f34ab424a5c85336 Mon Sep 17 00:00:00 2001 From: Henk Kin Date: Fri, 2 Dec 2022 15:53:04 +0100 Subject: [PATCH] Added option to disable UtcDateTimePropertyEntityBehavior (#6) Co-authored-by: Henk Kin --- .../WithCascadeTimingOnSaveChangesTests.cs | 4 +- ...lient.EntityFrameworkCore.SqlServer.csproj | 4 +- .../DataAccessClientOptions.cs | 1 + .../DataAccessClientOptionsBuilder.cs | 6 + .../DefaultSoftDeletableConfiguration.cs | 3 +- .../EntityTypeBuilderExtensions.cs | 1 - .../SqlServerDbContextForEntityResolver.cs | 1 - .../ServiceCollectionExtensions.cs | 261 +----------------- .../SqlServerDbContext.cs | 1 - DataAccessClient/DataAccessClient.csproj | 2 +- .../DataLayer/ExampleDbContext.cs | 5 +- DataAccessClientExample/Startup.cs | 2 + README.md | 52 ++++ 13 files changed, 75 insertions(+), 268 deletions(-) diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer.Tests/WithCascadeTimingOnSaveChangesTests.cs b/DataAccessClient.EntityFrameworkCore.SqlServer.Tests/WithCascadeTimingOnSaveChangesTests.cs index 5e25067..7ea19cf 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer.Tests/WithCascadeTimingOnSaveChangesTests.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer.Tests/WithCascadeTimingOnSaveChangesTests.cs @@ -1,5 +1,4 @@ -using System; -using DataAccessClient.EntityFrameworkCore.SqlServer.Tests.TestModels; +using DataAccessClient.EntityFrameworkCore.SqlServer.Tests.TestModels; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.DependencyInjection; @@ -24,6 +23,7 @@ public void WhenWorkIsDoneWithCascadeTimingOnSaveChanges_ItShouldResetToCascadeT var serviceProvider = services.BuildServiceProvider().CreateScope(); var dbContext = serviceProvider.ServiceProvider.GetService(); + Assert.NotNull(dbContext); // EF Core defaults to Immediate since EF Core 3.0, before 3.0 to OnSaveChanges // https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#cascade diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj index 25e0f7e..460b18b 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj @@ -13,7 +13,7 @@ Henk Kin EntityFrameworkCore, EF, SqlServer, DependencyInjection, Multiple DbContext support, DataAccess, Repository, UnitOfWork, SoftDelete, Translation, Localization, RowVersioning, Multitenancy, Filtering, Paging, Sorting, Includes - 7.0.0 + 7.0.1 true true true @@ -23,7 +23,7 @@ - + diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptions.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptions.cs index 5a7b15c..ba52395 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptions.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptions.cs @@ -8,6 +8,7 @@ namespace DataAccessClient.EntityFrameworkCore.SqlServer public class DataAccessClientOptions { public Action DbContextOptionsBuilder { get; internal set; } + public bool DisableUtcDateTimePropertyEntityBehavior { get; internal set; } public bool UsePooling { get; internal set; } public int? PoolSize { get; internal set; } public IList CustomEntityBehaviors { get; internal set; } = new List(); diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptionsBuilder.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptionsBuilder.cs index 8b4e09a..7423820 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptionsBuilder.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClientOptionsBuilder.cs @@ -33,6 +33,12 @@ public virtual DataAccessClientOptionsBuilder UsePooling(bool usePooling, int? p return this; } + public virtual DataAccessClientOptionsBuilder DisableUtcDateTimePropertyEntityBehavior() + { + _options.DisableUtcDateTimePropertyEntityBehavior = true; + return this; + } + public virtual DataAccessClientOptionsBuilder AddCustomEntityBehavior() where TEntityHaviorType : IEntityBehaviorConfiguration { _options.CustomEntityBehaviors.Add((TEntityHaviorType)Activator.CreateInstance(typeof(TEntityHaviorType))); diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/DefaultSoftDeletableConfiguration.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/DefaultSoftDeletableConfiguration.cs index 35b78e2..3946538 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/DefaultSoftDeletableConfiguration.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/DefaultSoftDeletableConfiguration.cs @@ -21,8 +21,7 @@ public RestoreAction Disable() IsEnabled = false; return new RestoreAction(() => IsEnabled = originalIsEnabled); } - - + public RestoreAction EnableQueryFilter() { var originalIsQueryFilterEnabled = IsQueryFilterEnabled; diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/EntityTypeBuilderExtensions.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/EntityTypeBuilderExtensions.cs index ce68d42..f8ed9ee 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/EntityTypeBuilderExtensions.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/EntityTypeBuilderExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Query; diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/Resolvers/SqlServerDbContextForEntityResolver.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/Resolvers/SqlServerDbContextForEntityResolver.cs index 65526db..ad23a36 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/Resolvers/SqlServerDbContextForEntityResolver.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/Resolvers/SqlServerDbContextForEntityResolver.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Microsoft.EntityFrameworkCore; namespace DataAccessClient.EntityFrameworkCore.SqlServer.Resolvers { diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/ServiceCollectionExtensions.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/ServiceCollectionExtensions.cs index b2375ef..d2fc0e2 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/ServiceCollectionExtensions.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/ServiceCollectionExtensions.cs @@ -36,10 +36,14 @@ public static IServiceCollection AddDataAccessClient(this IServiceCo CreateEntityBehaviorTypeInstance(typeof(LocalizableEntityBehaviorConfiguration<>).MakeGenericType(localeIdentifierType)), CreateEntityBehaviorTypeInstance(typeof(TenantScopeableEntityBehaviorConfiguration<>).MakeGenericType(tenantIdentifierType)), new TranslatableEntityBehaviorConfiguration(), - new TranslatedPropertyEntityBehaviorConfiguration(), - new UtcDateTimePropertyEntityBehaviorConfiguration() + new TranslatedPropertyEntityBehaviorConfiguration() }; + if (dataAccessClientOptions.DisableUtcDateTimePropertyEntityBehavior == false) + { + entityBehaviorConfigurations.Add(new UtcDateTimePropertyEntityBehaviorConfiguration()); + } + if (dataAccessClientOptions.CustomEntityBehaviors.Any()) { entityBehaviorConfigurations.AddRange(dataAccessClientOptions.CustomEntityBehaviors); @@ -57,21 +61,6 @@ void ExtendedDbContextOptionsBuilder(DbContextOptionsBuilder dbContextOptionsBui entityBehavior.OnRegistering(services); } - //if (ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, new[] { typeof(ISoftDeletable<>) })) - //{ - // services.RequireRegistrationFor(ServiceLifetime.Scoped); - //} - - //if (ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, new[] { typeof(ITenantScopable<>) })) - //{ - // services.RequireRegistrationFor(ServiceLifetime.Scoped); - //} - - //if (ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, new[] { typeof(ILocalizable<>) })) - //{ - // services.RequireRegistrationFor(ServiceLifetime.Scoped); - //} - if (dataAccessClientOptions.UsePooling) { if (dataAccessClientOptions.PoolSize.HasValue) @@ -135,243 +124,5 @@ private static Type GetLocaleIdentifierType(this IServiceCollection services) s.Lifetime == ServiceLifetime.Scoped); return registration?.ServiceType.GenericTypeArguments[0]; } - - //private static void ValidateDataAccessClientOptions(this IServiceCollection services, DataAccessClientOptions dataAccessClientOptions, Type userIdentifierType, Type tenantIdentifierType, Type localeIdentifierType) - //{ - - // ValidateUserIdentifierType(services, dataAccessClientOptions, userIdentifierType); - - // ValidateTenantIdentifierType(services, dataAccessClientOptions, tenantIdentifierType); - - // ValidateLocaleIdentifierType(services, dataAccessClientOptions, localeIdentifierType); - //} - - //internal static void ValidateTenantIdentifierType(IServiceCollection services, DataAccessClientOptions dataAccessClientOptions, Type tenantIdentifierType) - //{ - // var tenantIdentifierRelatedEntityBehaviors = new[] { typeof(ITenantScopable<>) }; - - - // bool hasEntityBehaviorsWithTenantIdentifier = - // ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, tenantIdentifierRelatedEntityBehaviors); - // if (hasEntityBehaviorsWithTenantIdentifier) - // { - // services.RequireRegistrationForGeneric(typeof(ITenantIdentifierProvider<>), ServiceLifetime.Scoped); - - // var tenantIdentifierProviderType = typeof(ITenantIdentifierProvider<>).MakeGenericType(tenantIdentifierType); - // services.RequireRegistrationFor(tenantIdentifierProviderType, ServiceLifetime.Scoped); - - // var entityBehaviorsWithWrongTenantIdentifierType = new Dictionary>(); - // foreach (var tenantIdentifierRelatedEntityBehavior in tenantIdentifierRelatedEntityBehaviors) - // { - // var types = GetEntityTypesWithWrongIdentifierTypeInEntityBehavior( - // dataAccessClientOptions.EntityTypes, tenantIdentifierRelatedEntityBehavior, - // tenantIdentifierType); - - // if (types.Any()) - // { - // if (entityBehaviorsWithWrongTenantIdentifierType.ContainsKey(tenantIdentifierRelatedEntityBehavior)) - // { - // entityBehaviorsWithWrongTenantIdentifierType[tenantIdentifierRelatedEntityBehavior] - // .AddRange(types); - // } - // else - // { - // entityBehaviorsWithWrongTenantIdentifierType.Add(tenantIdentifierRelatedEntityBehavior, types); - // } - // } - // } - - // if (entityBehaviorsWithWrongTenantIdentifierType.Any()) - // { - // var errorMessage = new StringBuilder(); - // errorMessage.AppendLine("The following entity types have implemented the entityhavior interface with a wrong user identifier type:"); - // foreach (var entityBehaviorWithWrongTenantIdentifierType in entityBehaviorsWithWrongTenantIdentifierType) - // { - // errorMessage.AppendLine($"EntityBehavior: {entityBehaviorWithWrongTenantIdentifierType.Key.Name}"); - // foreach (var type in entityBehaviorWithWrongTenantIdentifierType.Value) - // { - // errorMessage.AppendLine($"- {type.Name} ({type.FullName})"); - // } - // } - - // throw new InvalidOperationException(errorMessage.ToString()); - // } - // } - //} - - //internal static void ValidateUserIdentifierType(IServiceCollection services, DataAccessClientOptions dataAccessClientOptions, Type userIdentifierType) - //{ - // var userIdentifierRelatedEntityBehaviors = - // new[] { typeof(ICreatable<>), typeof(IModifiable<>), typeof(ISoftDeletable<>) }; - - - // bool hasEntityBehaviorsWithUserIdentifier = - // ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, userIdentifierRelatedEntityBehaviors); - // if (hasEntityBehaviorsWithUserIdentifier) - // { - // services.RequireRegistrationForGeneric(typeof(IUserIdentifierProvider<>), ServiceLifetime.Scoped); - - // var userIdentifierProviderType = typeof(IUserIdentifierProvider<>).MakeGenericType(userIdentifierType); - // services.RequireRegistrationFor(userIdentifierProviderType, ServiceLifetime.Scoped); - - // var entityBehaviorsWithWrongUserIdentifierType = new Dictionary>(); - // foreach (var userIdentifierRelatedEntityBehavior in userIdentifierRelatedEntityBehaviors) - // { - // var types = GetEntityTypesWithWrongIdentifierTypeInEntityBehavior( - // dataAccessClientOptions.EntityTypes, userIdentifierRelatedEntityBehavior, - // userIdentifierType); - - // if (types.Any()) - // { - // if (entityBehaviorsWithWrongUserIdentifierType.ContainsKey(userIdentifierRelatedEntityBehavior)) - // { - // entityBehaviorsWithWrongUserIdentifierType[userIdentifierRelatedEntityBehavior] - // .AddRange(types); - // } - // else - // { - // entityBehaviorsWithWrongUserIdentifierType.Add(userIdentifierRelatedEntityBehavior, types); - // } - // } - // } - - // if (entityBehaviorsWithWrongUserIdentifierType.Any()) - // { - // var errorMessage = new StringBuilder(); - // errorMessage.AppendLine( - // $"The current UserIdentifier type is: {userIdentifierType.Name}, the following entity types have implemented the entityhavior interface with a wrong user identifier type:"); - // foreach (var entityBehaviorWithWrongUserIdentifierType in entityBehaviorsWithWrongUserIdentifierType) - // { - // errorMessage.AppendLine($"EntityBehavior: {entityBehaviorWithWrongUserIdentifierType.Key.Name}"); - // foreach (var type in entityBehaviorWithWrongUserIdentifierType.Value) - // { - // errorMessage.AppendLine($"- {type.Name} ({type.FullName})"); - // } - // } - - // throw new InvalidOperationException(errorMessage.ToString()); - // } - // } - //} - - //internal static void ValidateLocaleIdentifierType(IServiceCollection services, DataAccessClientOptions dataAccessClientOptions, Type localeIdentifierType) - //{ - // var localeIdentifierRelatedEntityBehaviors = - // new[] { typeof(ILocalizable<>) }; - - // bool hasEntityBehaviorsWithLocaleIdentifier = - // ContainsEntityBehaviors(dataAccessClientOptions.EntityTypes, localeIdentifierRelatedEntityBehaviors); - // if (hasEntityBehaviorsWithLocaleIdentifier) - // { - // services.RequireRegistrationForGeneric(typeof(ILocaleIdentifierProvider<>), ServiceLifetime.Scoped); - - // var localeIdentifierProviderType = typeof(ILocaleIdentifierProvider<>).MakeGenericType(localeIdentifierType); - // services.RequireRegistrationFor(localeIdentifierProviderType, ServiceLifetime.Scoped); - - // var entityBehaviorsWithWrongLocaleIdentifierType = new Dictionary>(); - // foreach (var localeIdentifierRelatedEntityBehavior in localeIdentifierRelatedEntityBehaviors) - // { - // var types = GetEntityTypesWithWrongIdentifierTypeInEntityBehavior( - // dataAccessClientOptions.EntityTypes, localeIdentifierRelatedEntityBehavior, - // localeIdentifierType); - - // if (types.Any()) - // { - // if (entityBehaviorsWithWrongLocaleIdentifierType.ContainsKey(localeIdentifierRelatedEntityBehavior)) - // { - // entityBehaviorsWithWrongLocaleIdentifierType[localeIdentifierRelatedEntityBehavior] - // .AddRange(types); - // } - // else - // { - // entityBehaviorsWithWrongLocaleIdentifierType.Add(localeIdentifierRelatedEntityBehavior, types); - // } - // } - // } - - // if (entityBehaviorsWithWrongLocaleIdentifierType.Any()) - // { - // var errorMessage = new StringBuilder(); - // errorMessage.AppendLine( - // $"The current LocaleIdentifier type is: {localeIdentifierType.Name}, the following entity types have implemented the entityhavior interface with a wrong locale identifier type:"); - // foreach (var entityBehaviorWithWrongLocaleIdentifierType in entityBehaviorsWithWrongLocaleIdentifierType) - // { - // errorMessage.AppendLine($"EntityBehavior: {entityBehaviorWithWrongLocaleIdentifierType.Key.Name}"); - // foreach (var type in entityBehaviorWithWrongLocaleIdentifierType.Value) - // { - // errorMessage.AppendLine($"- {type.Name} ({type.FullName})"); - // } - // } - - // throw new InvalidOperationException(errorMessage.ToString()); - // } - // } - //} - - ////private static void RequireRegistrationFor(this IServiceCollection services, - //// ServiceLifetime serviceLifetime) - ////{ - //// var isRegisteredWithLifetime = - //// services.Any(s => - //// s.ServiceType == typeof(TRegistrationType) && - //// s.Lifetime == serviceLifetime); - //// if (!isRegisteredWithLifetime) - //// { - //// ThrowNoRegistrationFoundException(typeof(TRegistrationType), serviceLifetime); - //// } - ////} - - - //private static void RequireRegistrationForGeneric(this IServiceCollection services, Type registrationType, ServiceLifetime serviceLifetime) - //{ - // var isRegisteredWithLifetime = - // services.Any(s => - // s.ServiceType.IsGenericType && - // s.ServiceType.GetGenericTypeDefinition() == registrationType && - // s.Lifetime == serviceLifetime); - // if (!isRegisteredWithLifetime) - // { - // ThrowNoRegistrationFoundException(registrationType, serviceLifetime); - // } - //} - - //private static void RequireRegistrationFor(this IServiceCollection services, Type registrationType, ServiceLifetime serviceLifetime) - //{ - // var isRegisteredWithLifetime = - // services.Any(s => - // s.ServiceType == registrationType && - // s.Lifetime == serviceLifetime); - // if (!isRegisteredWithLifetime) - // { - // ThrowNoRegistrationFoundException(registrationType, serviceLifetime); - // } - //} - - //private static void ThrowNoRegistrationFoundException(Type registrationType, ServiceLifetime serviceLifetime) - //{ - // throw new InvalidOperationException( - // $"No DI registration found for type {registrationType.FullName}, please register with LifeTime {serviceLifetime.ToString()} in DI"); - - //} - - //private static bool ContainsEntityBehaviors(Type[] entityTypes, Type[] entityBehaviors = null) - //{ - // var containsEntityBehaviors = entityBehaviors != null && entityTypes - // .Any(c => c.GetInterfaces().Any(i => - // i.IsGenericType && entityBehaviors.Contains(i.GetGenericTypeDefinition()))); - - // return containsEntityBehaviors; - //} - - //private static List GetEntityTypesWithWrongIdentifierTypeInEntityBehavior(Type[] entityTypes, Type entityBehavior, Type identifierType) - //{ - // var entityTypesWithWrongIdentifierTypeInEntityBehavior = entityTypes - // .Where(c => c.GetInterfaces().Any(i => - // i.IsGenericType && - // i.GetGenericTypeDefinition() == entityBehavior && - // i.GenericTypeArguments[0] != identifierType)).ToList(); - - // return entityTypesWithWrongIdentifierTypeInEntityBehavior; - //} } } \ No newline at end of file diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs index 89fbfda..c6486dc 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs @@ -11,7 +11,6 @@ using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; namespace DataAccessClient.EntityFrameworkCore.SqlServer { diff --git a/DataAccessClient/DataAccessClient.csproj b/DataAccessClient/DataAccessClient.csproj index 23398ca..224aa8c 100644 --- a/DataAccessClient/DataAccessClient.csproj +++ b/DataAccessClient/DataAccessClient.csproj @@ -13,7 +13,7 @@ DataAccess Repository, UnitOfWork, Multitenancy, SoftDelete, RowVersioning, Filtering, Paging, Translation, Localization, Sorting, Includes Provides interfaces for Data Access with IRepository<T> and IUnitOfWork. Also provides haviorial interfaces for entities like IIdentifiable, ICreatable, IModifiable, ISoftDeletable, ITranslatable, ILocalizable, ITenantScopable and IRowVersioned. Last but not least provides some types for Exceptions and searching capabilities like Filtering,Paging, Sorting and Includes. Henk Kin - 7.0.0 + 7.0.1 true true true diff --git a/DataAccessClientExample/DataLayer/ExampleDbContext.cs b/DataAccessClientExample/DataLayer/ExampleDbContext.cs index 75bea8e..c6193de 100644 --- a/DataAccessClientExample/DataLayer/ExampleDbContext.cs +++ b/DataAccessClientExample/DataLayer/ExampleDbContext.cs @@ -1,6 +1,5 @@ using DataAccessClient.EntityFrameworkCore.SqlServer; using Microsoft.EntityFrameworkCore; -using System.Linq; namespace DataAccessClientExample.DataLayer { @@ -12,14 +11,14 @@ public ExampleDbContext(DbContextOptions options) : base(optio protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() + modelBuilder.Entity() .ToTable("ExampleEntities"); modelBuilder .Entity() .HasNoKey() .ToSqlQuery("SELECT e.Id, et.LocaleId, e.Name, et.Description FROM dbo.ExampleEntities e INNER JOIN dbo.ExampleEntityTranslation et ON e.Id = et.TranslatedEntityId"); - + base.OnModelCreating(modelBuilder); } } diff --git a/DataAccessClientExample/Startup.cs b/DataAccessClientExample/Startup.cs index 41fb08e..38c1aab 100644 --- a/DataAccessClientExample/Startup.cs +++ b/DataAccessClientExample/Startup.cs @@ -37,6 +37,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDataAccessClient(conf => conf .UsePooling(true) + .DisableUtcDateTimePropertyEntityBehavior() .ConfigureDbContextOptions(builder => builder .EnableSensitiveDataLogging() .EnableDetailedErrors() @@ -46,6 +47,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDataAccessClient(conf => conf .UsePooling(true) + .DisableUtcDateTimePropertyEntityBehavior() .ConfigureDbContextOptions(builder => builder .EnableSensitiveDataLogging() .EnableDetailedErrors() diff --git a/README.md b/README.md index 77e7e9d..44b009c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,56 @@ Either commands, from Package Manager Console or .NET Core CLI, will download an No external dependencies +### (Breaking) Changes + +#### 7.0.1: Added option to disable UtcDateTimePropertyEntityBehavior +#### Future 8.0.0: Removed UtcDateTimePropertyEntityBehavior options and properties with types DateTime and Nullable no longer default to Utc. You have to do that in your own modelbuilder with a convention. + +For example +```csharp + +protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) +{ + configurationBuilder + .Properties() + .HaveConversion(); + + configurationBuilder + .Properties() + .HaveConversion(); +} + +... + +public class UtcDateTimeValueConverter : ValueConverter +{ + public UtcDateTimeValueConverter() : this(null) + { + } + + public UtcDateTimeValueConverter(ConverterMappingHints mappingHints = null) : base(ConvertToUtcExpression, ConvertToUtcExpression, mappingHints) + { + } + + private static readonly Expression> ConvertToUtcExpression = dateTime => dateTime.HasValue ? ConvertToUtc(dateTime.Value) : dateTime; + + public static DateTime ConvertToUtc(DateTime dateTime) + { + if (dateTime.Kind == DateTimeKind.Unspecified) + { + return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); + } + + if (dateTime.Kind == DateTimeKind.Local) + { + return dateTime.ToUniversalTime(); + } + + return dateTime; + } +} +``` + ### Entity behaviors The [DataAccessClient](https://github.com/HenkKin/DataAccessClient/) package provides you a set of EntityBehavior interfaces. These interfaces you can use to decorate your entites. @@ -518,6 +568,8 @@ public class Startup // register as DataAccessClient services.AddDataAccessClient(conf => conf .UsePooling(true) + // This option will be deleted in version 8.0.0 + .DisableUtcDateTimePropertyEntityBehavior() // Disable the default UtcDateTimePropertyEntityBehavior .AddCustomEntityBehavior() // optional extensible .ConfigureDbContextOptions(builder => builder .EnableSensitiveDataLogging()