From c46f980c09c706281009e6426bbac1fb182743ac Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Tue, 5 Sep 2023 23:57:25 +0800 Subject: [PATCH] Start work on some support for sequence value generation. Still under investigation if possible --- .../Extensions/JetModelExtensions.cs | 93 ++++++++++ .../Extensions/JetPropertyExtensions.cs | 173 ++++++++++++++++++ .../JetValueGenerationStrategyConvention.cs | 41 +++-- .../Metadata/Internal/JetAnnotationNames.cs | 18 ++ .../Metadata/JetValueGenerationStrategy.cs | 3 +- 5 files changed, 316 insertions(+), 12 deletions(-) diff --git a/src/EFCore.Jet/Extensions/JetModelExtensions.cs b/src/EFCore.Jet/Extensions/JetModelExtensions.cs index 7e9d39b..5bf60d5 100644 --- a/src/EFCore.Jet/Extensions/JetModelExtensions.cs +++ b/src/EFCore.Jet/Extensions/JetModelExtensions.cs @@ -2,6 +2,7 @@ using EntityFrameworkCore.Jet.Metadata; using EntityFrameworkCore.Jet.Metadata.Internal; +using EntityFrameworkCore.Jet.Utilities; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; @@ -10,6 +11,8 @@ namespace Microsoft.EntityFrameworkCore { public static class JetModelExtensions { + public const string DefaultSequenceNameSuffix = "Sequence"; + /// /// Returns the default identity seed. /// @@ -123,5 +126,95 @@ namespace Microsoft.EntityFrameworkCore /// The for the default . public static ConfigurationSource? GetJetValueGenerationStrategyConfigurationSource([NotNull] this IConventionModel model) => model.FindAnnotation(JetAnnotationNames.ValueGenerationStrategy)?.GetConfigurationSource(); + + /// + /// Returns the suffix to append to the name of automatically created sequences. + /// + /// The model. + /// The name to use for the default key value generation sequence. + public static string GetJetSequenceNameSuffix(this IReadOnlyModel model) + => (string?)model[JetAnnotationNames.SequenceNameSuffix] + ?? DefaultSequenceNameSuffix; + + /// + /// Sets the suffix to append to the name of automatically created sequences. + /// + /// The model. + /// The value to set. + public static void SetJetSequenceNameSuffix(this IMutableModel model, string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + model.SetOrRemoveAnnotation(JetAnnotationNames.SequenceNameSuffix, name); + } + + /// + /// Sets the suffix to append to the name of automatically created sequences. + /// + /// The model. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetJetSequenceNameSuffix( + this IConventionModel model, + string? name, + bool fromDataAnnotation = false) + => (string?)model.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceNameSuffix, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation)?.Value; + + /// + /// Returns the for the default value generation sequence name suffix. + /// + /// The model. + /// The for the default key value generation sequence name. + public static ConfigurationSource? GetJetSequenceNameSuffixConfigurationSource(this IConventionModel model) + => model.FindAnnotation(JetAnnotationNames.SequenceNameSuffix)?.GetConfigurationSource(); + + /// + /// Returns the schema to use for the default value generation sequence. + /// + /// + /// The model. + /// The schema to use for the default key value generation sequence. + public static string? GetJetSequenceSchema(this IReadOnlyModel model) + => (string?)model[JetAnnotationNames.SequenceSchema]; + + /// + /// Sets the schema to use for the default key value generation sequence. + /// + /// The model. + /// The value to set. + public static void SetJetSequenceSchema(this IMutableModel model, string? value) + { + Check.NullButNotEmpty(value, nameof(value)); + + model.SetOrRemoveAnnotation(JetAnnotationNames.SequenceSchema, value); + } + + /// + /// Sets the schema to use for the default key value generation sequence. + /// + /// The model. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetJetSequenceSchema( + this IConventionModel model, + string? value, + bool fromDataAnnotation = false) + => (string?)model.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceSchema, + Check.NullButNotEmpty(value, nameof(value)), + fromDataAnnotation)?.Value; + + /// + /// Returns the for the default key value generation sequence schema. + /// + /// The model. + /// The for the default key value generation sequence schema. + public static ConfigurationSource? GetJetSequenceSchemaConfigurationSource(this IConventionModel model) + => model.FindAnnotation(JetAnnotationNames.SequenceSchema)?.GetConfigurationSource(); } } \ No newline at end of file diff --git a/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs b/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs index 39978b1..3f17427 100644 --- a/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs +++ b/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Infrastructure; using System.Linq; +using EntityFrameworkCore.Jet.Utilities; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore @@ -302,5 +303,177 @@ namespace Microsoft.EntityFrameworkCore ?? property.FindTypeMapping()?.Converter) == null; } + + /// + /// Returns the name to use for the key value generation sequence. + /// + /// The property. + /// The name to use for the key value generation sequence. + public static string? GetJetSequenceName(this IReadOnlyProperty property) + => (string?)property[JetAnnotationNames.SequenceName]; + + /// + /// Returns the name to use for the key value generation sequence. + /// + /// The property. + /// The identifier of the store object. + /// The name to use for the key value generation sequence. + public static string? GetJetSequenceName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + { + var annotation = property.FindAnnotation(JetAnnotationNames.SequenceName); + if (annotation != null) + { + return (string?)annotation.Value; + } + + return property.FindSharedStoreObjectRootProperty(storeObject)?.GetJetSequenceName(storeObject); + } + + /// + /// Sets the name to use for the key value generation sequence. + /// + /// The property. + /// The sequence name to use. + public static void SetJetSequenceName(this IMutableProperty property, string? name) + => property.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceName, + Check.NullButNotEmpty(name, nameof(name))); + + /// + /// Sets the name to use for the key value generation sequence. + /// + /// The property. + /// The sequence name to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetJetSequenceName( + this IConventionProperty property, + string? name, + bool fromDataAnnotation = false) + => (string?)property.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceName, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation)?.Value; + + /// + /// Returns the for the key value generation sequence name. + /// + /// The property. + /// The for the key value generation sequence name. + public static ConfigurationSource? GetJetSequenceNameConfigurationSource(this IConventionProperty property) + => property.FindAnnotation(JetAnnotationNames.SequenceName)?.GetConfigurationSource(); + + /// + /// Returns the schema to use for the key value generation sequence. + /// + /// The property. + /// The schema to use for the key value generation sequence. + public static string? GetJetSequenceSchema(this IReadOnlyProperty property) + => (string?)property[JetAnnotationNames.SequenceSchema]; + + /// + /// Returns the schema to use for the key value generation sequence. + /// + /// The property. + /// The identifier of the store object. + /// The schema to use for the key value generation sequence. + public static string? GetJetSequenceSchema(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + { + var annotation = property.FindAnnotation(JetAnnotationNames.SequenceSchema); + if (annotation != null) + { + return (string?)annotation.Value; + } + + return property.FindSharedStoreObjectRootProperty(storeObject)?.GetJetSequenceSchema(storeObject); + } + + /// + /// Sets the schema to use for the key value generation sequence. + /// + /// The property. + /// The schema to use. + public static void SetJetSequenceSchema(this IMutableProperty property, string? schema) + => property.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceSchema, + Check.NullButNotEmpty(schema, nameof(schema))); + + /// + /// Sets the schema to use for the key value generation sequence. + /// + /// The property. + /// The schema to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetJetSequenceSchema( + this IConventionProperty property, + string? schema, + bool fromDataAnnotation = false) + => (string?)property.SetOrRemoveAnnotation( + JetAnnotationNames.SequenceSchema, + Check.NullButNotEmpty(schema, nameof(schema)), + fromDataAnnotation)?.Value; + + /// + /// Returns the for the key value generation sequence schema. + /// + /// The property. + /// The for the key value generation sequence schema. + public static ConfigurationSource? GetGetSequenceSchemaConfigurationSource(this IConventionProperty property) + => property.FindAnnotation(JetAnnotationNames.SequenceSchema)?.GetConfigurationSource(); + + /// + /// Finds the in the model to use for the key value generation pattern. + /// + /// The property. + /// The sequence to use, or if no sequence exists in the model. + public static IReadOnlySequence? FindJetSequence(this IReadOnlyProperty property) + { + var model = property.DeclaringEntityType.Model; + + var sequenceName = property.GetJetSequenceName() + ?? model.GetJetSequenceNameSuffix(); + + var sequenceSchema = property.GetJetSequenceSchema() + ?? model.GetJetSequenceSchema(); + + return model.FindSequence(sequenceName, sequenceSchema); + } + + /// + /// Finds the in the model to use for the key value generation pattern. + /// + /// The property. + /// The identifier of the store object. + /// The sequence to use, or if no sequence exists in the model. + public static IReadOnlySequence? FindJetSequence(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + { + var model = property.DeclaringEntityType.Model; + + var sequenceName = property.GetJetSequenceName(storeObject) + ?? model.GetJetSequenceNameSuffix(); + + var sequenceSchema = property.GetJetSequenceSchema(storeObject) + ?? model.GetJetSequenceSchema(); + + return model.FindSequence(sequenceName, sequenceSchema); + } + + /// + /// Finds the in the model to use for the key value generation pattern. + /// + /// The property. + /// The sequence to use, or if no sequence exists in the model. + public static ISequence? FindJetSequence(this IProperty property) + => (ISequence?)((IReadOnlyProperty)property).FindJetSequence(); + + /// + /// Finds the in the model to use for the key value generation pattern. + /// + /// The property. + /// The identifier of the store object. + /// The sequence to use, or if no sequence exists in the model. + public static ISequence? FindJetSequence(this IProperty property, in StoreObjectIdentifier storeObject) + => (ISequence?)((IReadOnlyProperty)property).FindJetSequence(storeObject); } } \ No newline at end of file diff --git a/src/EFCore.Jet/Metadata/Conventions/JetValueGenerationStrategyConvention.cs b/src/EFCore.Jet/Metadata/Conventions/JetValueGenerationStrategyConvention.cs index a66da93..fa72be8 100644 --- a/src/EFCore.Jet/Metadata/Conventions/JetValueGenerationStrategyConvention.cs +++ b/src/EFCore.Jet/Metadata/Conventions/JetValueGenerationStrategyConvention.cs @@ -1,6 +1,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using EntityFrameworkCore.Jet.Metadata; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -24,8 +25,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions [NotNull] ProviderConventionSetBuilderDependencies dependencies, [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies) { + Dependencies = dependencies; + RelationalDependencies = relationalDependencies; } + protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + + protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } + /// /// Called after a model is initialized. /// @@ -48,26 +55,24 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions foreach (var property in entityType.GetDeclaredProperties()) { JetValueGenerationStrategy? strategy = null; - var table = entityType.GetTableName(); - if (table != null) + var declaringTable = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); + if (declaringTable.Name != null!) { - var storeObject = StoreObjectIdentifier.Table(table, entityType.GetSchema()); - strategy = property.GetValueGenerationStrategy(storeObject); + strategy = property.GetValueGenerationStrategy(declaringTable); if (strategy == JetValueGenerationStrategy.None - && !IsStrategyNoneNeeded(property, storeObject)) + && !IsStrategyNoneNeeded(property, declaringTable)) { strategy = null; } } else { - var view = entityType.GetViewName(); - if (view != null) + var declaringView = property.GetMappedStoreObjects(StoreObjectType.View).FirstOrDefault(); + if (declaringView.Name != null!) { - var storeObject = StoreObjectIdentifier.View(view, entityType.GetViewSchema()); - strategy = property.GetValueGenerationStrategy(storeObject); + strategy = property.GetValueGenerationStrategy(declaringView); if (strategy == JetValueGenerationStrategy.None - && !IsStrategyNoneNeeded(property, storeObject)) + && !IsStrategyNoneNeeded(property, declaringView)) { strategy = null; } @@ -75,9 +80,23 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions } // Needed for the annotation to show up in the model snapshot - if (strategy != null) + if (strategy != null + && declaringTable.Name != null) { property.Builder.HasValueGenerationStrategy(strategy); + + if (strategy == JetValueGenerationStrategy.Sequence) + { + var sequence = modelBuilder.HasSequence( + property.GetJetSequenceName(declaringTable) + ?? entityType.GetRootType().ShortName() + modelBuilder.Metadata.GetJetSequenceNameSuffix(), + property.GetJetSequenceSchema(declaringTable) + ?? modelBuilder.Metadata.GetJetSequenceSchema()).Metadata; + + property.Builder.HasDefaultValueSql( + RelationalDependencies.UpdateSqlGenerator.GenerateObtainNextSequenceValueOperation( + sequence.Name, sequence.Schema)); + } } } } diff --git a/src/EFCore.Jet/Metadata/Internal/JetAnnotationNames.cs b/src/EFCore.Jet/Metadata/Internal/JetAnnotationNames.cs index a5f97db..7567be6 100644 --- a/src/EFCore.Jet/Metadata/Internal/JetAnnotationNames.cs +++ b/src/EFCore.Jet/Metadata/Internal/JetAnnotationNames.cs @@ -57,5 +57,23 @@ namespace EntityFrameworkCore.Jet.Metadata.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public const string IdentityIncrement = Prefix + "IdentityIncrement"; + + public const string SequenceNameSuffix = Prefix + "SequenceNameSuffix"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string SequenceName = Prefix + "SequenceName"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string SequenceSchema = Prefix + "SequenceSchema"; } } \ No newline at end of file diff --git a/src/EFCore.Jet/Metadata/JetValueGenerationStrategy.cs b/src/EFCore.Jet/Metadata/JetValueGenerationStrategy.cs index 0b47612..7aa6355 100644 --- a/src/EFCore.Jet/Metadata/JetValueGenerationStrategy.cs +++ b/src/EFCore.Jet/Metadata/JetValueGenerationStrategy.cs @@ -5,6 +5,7 @@ namespace EntityFrameworkCore.Jet.Metadata public enum JetValueGenerationStrategy { None, - IdentityColumn + IdentityColumn, + Sequence } }