From 853e21fa8ecbcd7267bc3fa4c012f81bec684e93 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 29 Jun 2022 11:29:27 -0700 Subject: [PATCH] Add a way to configure identity columns and triggers on mapping fragments Add support for annotations to mapping fragments, property overrides and sequences Fixes #28195 Fixes #28237 --- .../Design/CSharpSnapshotGenerator.cs | 160 ++++++---- .../Internal/CSharpDbContextGenerator.cs | 57 ++-- .../Design/AnnotationCodeGenerator.cs | 172 +++++++++++ .../Design/IAnnotationCodeGenerator.cs | 26 ++ ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 14 +- .../RelationalPropertyExtensions.cs | 39 ++- .../Metadata/Builders/ColumnBuilder.cs | 17 ++ .../Metadata/Builders/ColumnBuilder`.cs | 11 + .../OwnedNavigationSplitTableBuilder.cs | 16 + .../OwnedNavigationSplitTableBuilder``.cs | 11 + .../Metadata/Builders/SequenceBuilder.cs | 16 + .../Metadata/Builders/SplitTableBuilder.cs | 16 + .../Metadata/Builders/SplitTableBuilder`.cs | 10 + .../SqlServerAnnotationCodeGenerator.cs | 7 + ...verCSharpRuntimeAnnotationCodeGenerator.cs | 13 + .../SqlServerPropertyBuilderExtensions.cs | 224 ++++++++++++++ .../Extensions/SqlServerPropertyExtensions.cs | 289 +++++++++++++++++- .../SqlServerRuntimeModelConvention.cs | 56 ++-- .../Infrastructure/AnnotatableBuilder.cs | 30 +- .../Infrastructure/ConventionAnnotatable.cs | 44 ++- .../ForeignKeyPropertyDiscoveryConvention.cs | 17 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 39 ++- .../CSharpRuntimeModelCodeGeneratorTest.cs | 37 ++- 23 files changed, 1153 insertions(+), 168 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 1bb73c75382..835ca869c2a 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -316,80 +316,103 @@ protected virtual void GenerateSequence( ISequence sequence, IndentedStringBuilder stringBuilder) { - stringBuilder - .AppendLine() + var sequenceBuilderNameBuilder = new StringBuilder(); + sequenceBuilderNameBuilder .Append(modelBuilderName) .Append(".HasSequence"); if (sequence.Type != Sequence.DefaultClrType) { - stringBuilder + sequenceBuilderNameBuilder .Append("<") .Append(Code.Reference(sequence.Type)) .Append(">"); } - stringBuilder + sequenceBuilderNameBuilder .Append("(") .Append(Code.Literal(sequence.Name)); if (!string.IsNullOrEmpty(sequence.Schema) && sequence.Model.GetDefaultSchema() != sequence.Schema) { - stringBuilder + sequenceBuilderNameBuilder .Append(", ") .Append(Code.Literal(sequence.Schema)); } - stringBuilder.Append(")"); + sequenceBuilderNameBuilder.Append(")"); + var sequenceBuilderName = sequenceBuilderNameBuilder.ToString(); - using (stringBuilder.Indent()) + stringBuilder + .AppendLine() + .Append(sequenceBuilderName); + + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); + + if (sequence.StartValue != Sequence.DefaultStartValue) { - if (sequence.StartValue != Sequence.DefaultStartValue) - { - stringBuilder - .AppendLine() - .Append(".StartsAt(") - .Append(Code.Literal(sequence.StartValue)) - .Append(")"); - } + stringBuilder + .AppendLine() + .Append(".StartsAt(") + .Append(Code.Literal(sequence.StartValue)) + .Append(")"); + } - if (sequence.IncrementBy != Sequence.DefaultIncrementBy) - { - stringBuilder - .AppendLine() - .Append(".IncrementsBy(") - .Append(Code.Literal(sequence.IncrementBy)) - .Append(")"); - } + if (sequence.IncrementBy != Sequence.DefaultIncrementBy) + { + stringBuilder + .AppendLine() + .Append(".IncrementsBy(") + .Append(Code.Literal(sequence.IncrementBy)) + .Append(")"); + } - if (sequence.MinValue != Sequence.DefaultMinValue) - { - stringBuilder - .AppendLine() - .Append(".HasMin(") - .Append(Code.Literal(sequence.MinValue)) - .Append(")"); - } + if (sequence.MinValue != Sequence.DefaultMinValue) + { + stringBuilder + .AppendLine() + .Append(".HasMin(") + .Append(Code.Literal(sequence.MinValue)) + .Append(")"); + } - if (sequence.MaxValue != Sequence.DefaultMaxValue) - { - stringBuilder - .AppendLine() - .Append(".HasMax(") - .Append(Code.Literal(sequence.MaxValue)) - .Append(")"); - } + if (sequence.MaxValue != Sequence.DefaultMaxValue) + { + stringBuilder + .AppendLine() + .Append(".HasMax(") + .Append(Code.Literal(sequence.MaxValue)) + .Append(")"); + } - if (sequence.IsCyclic != Sequence.DefaultIsCyclic) - { - stringBuilder - .AppendLine() - .Append(".IsCyclic()"); - } + if (sequence.IsCyclic != Sequence.DefaultIsCyclic) + { + stringBuilder + .AppendLine() + .Append(".IsCyclic()"); } - stringBuilder.AppendLine(";"); + GenerateSequenceAnnotations(sequenceBuilderName, sequence, stringBuilder); + } + + /// + /// Generates code for sequence annotations. + /// + /// The name of the sequence builder variable. + /// The sequence. + /// The builder code is added to. + protected virtual void GenerateSequenceAnnotations( + string sequenceBuilderName, + ISequence sequence, + IndentedStringBuilder stringBuilder) + { + var annotations = Dependencies.AnnotationCodeGenerator + .FilterIgnoredAnnotations(sequence.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + + GenerateAnnotations(sequenceBuilderName, sequence, stringBuilder, annotations, inChainedCall: true); } /// @@ -849,7 +872,7 @@ private void GenerateTableMapping( annotations.Remove(isExcludedAnnotation.Name); } - var hasTriggers = entityType.GetTriggers().Any(); + var hasTriggers = entityType.GetTriggers().Any(t => t.TableName == tableName! && t.TableSchema == schema); var hasOverrides = table != null && entityType.GetProperties().Select(p => p.FindOverrides(table.Value)).Any(o => o != null); var requiresTableBuilder = isExcludedFromMigrations @@ -890,7 +913,7 @@ private void GenerateTableMapping( if (hasTriggers) { stringBuilder.AppendLine(); - GenerateTriggers("t", entityType, stringBuilder); + GenerateTriggers("t", entityType, tableName!, schema, stringBuilder); } if (hasOverrides) @@ -914,13 +937,14 @@ private void GenerateSplitTableMapping( { foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table)) { + var table = fragment.StoreObject; stringBuilder .AppendLine() .Append(entityTypeBuilderName) .Append(".SplitToTable(") - .Append(Code.UnknownLiteral(fragment.StoreObject.Name)) + .Append(Code.UnknownLiteral(table.Name)) .Append(", ") - .Append(Code.UnknownLiteral(fragment.StoreObject.Schema)) + .Append(Code.UnknownLiteral(table.Schema)) .AppendLine(", t =>"); using (stringBuilder.Indent()) @@ -929,7 +953,9 @@ private void GenerateSplitTableMapping( using (stringBuilder.Indent()) { - GenerateOverrides("t", entityType, fragment.StoreObject, stringBuilder); + GenerateTriggers("t", entityType, table.Name, table.Schema, stringBuilder); + GenerateOverrides("t", entityType, table, stringBuilder); + GenerateEntityTypeMappingFragmentAnnotations("t", fragment, stringBuilder); } stringBuilder @@ -1025,6 +1051,7 @@ private void GenerateSplitViewMapping( using (stringBuilder.Indent()) { GenerateOverrides("v", entityType, fragment.StoreObject, stringBuilder); + GenerateEntityTypeMappingFragmentAnnotations("v", fragment, stringBuilder); } stringBuilder @@ -1034,6 +1061,28 @@ private void GenerateSplitViewMapping( } } + /// + /// Generates code for mapping fragment annotations. + /// + /// The name of the table builder variable. + /// The mapping fragment. + /// The builder code is added to. + protected virtual void GenerateEntityTypeMappingFragmentAnnotations( + string tableBuilderName, + IEntityTypeMappingFragment fragment, + IndentedStringBuilder stringBuilder) + { + var annotations = Dependencies.AnnotationCodeGenerator + .FilterIgnoredAnnotations(fragment.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + if (annotations.Count > 0) + { + stringBuilder.AppendLine(); + + GenerateAnnotations(tableBuilderName, fragment, stringBuilder, annotations, inChainedCall: false); + } + } + /// /// Generates code for objects. /// @@ -1137,14 +1186,23 @@ protected virtual void GenerateCheckConstraintAnnotations( /// /// The name of the table builder variable. /// The entity type. + /// The table name. + /// The table schema. /// The builder code is added to. protected virtual void GenerateTriggers( string tableBuilderName, IEntityType entityType, + string table, + string? schema, IndentedStringBuilder stringBuilder) { foreach (var trigger in entityType.GetTriggers()) { + if (trigger.TableName != table || trigger.TableSchema != schema) + { + continue; + } + GenerateTrigger(tableBuilderName, trigger, stringBuilder); } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 41cbee2abbb..a8acffb1767 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -435,55 +435,62 @@ private void GenerateEntityType(IEntityType entityType) _builder.Append($"{EntityLambdaIdentifier}.{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}(tb => "); - // Note: no trigger annotation support as of yet + _builder.AppendLine("{"); - if (triggers.Length == 1) + using (_builder.Indent()) { - var trigger = triggers[0]; - if (trigger.Name is not null) + foreach (var trigger in entityType.GetTriggers().Where(t => t.Name is not null)) { - _builder.AppendLine($"tb.HasTrigger({_code.Literal(trigger.Name)}));"); + GenerateTrigger("tb", trigger); } } - else - { - _builder.AppendLine("{"); - using (_builder.Indent()) - { - foreach (var trigger in entityType.GetTriggers().Where(t => t.Name is not null)) - { - _builder.AppendLine($"tb.HasTrigger({_code.Literal(trigger.Name!)});"); - } - } - - _builder.AppendLine("});"); - } + _builder.AppendLine("});"); } } } - private void AppendMultiLineFluentApi(IEntityType entityType, IList lines) + private void GenerateTrigger(string tableBuilderName, ITrigger trigger) + { + var lines = new List { $".HasTrigger({_code.Literal(trigger.Name!)}))" }; + + var annotations = _annotationCodeGenerator + .FilterIgnoredAnnotations(trigger.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + + _annotationCodeGenerator.RemoveAnnotationsHandledByConventions(trigger, annotations); + + GenerateAnnotations(trigger, annotations, lines); + + AppendMultiLineFluentApi(null, lines, tableBuilderName); + } + + private void AppendMultiLineFluentApi(IEntityType? entityType, IList lines, string? builderName = null) { if (lines.Count <= 0) { return; } - InitializeEntityTypeBuilder(entityType); + if (entityType != null) + { + InitializeEntityTypeBuilder(entityType); + } using (_builder.Indent()) { - _builder.AppendLine(); - - _builder.Append(EntityLambdaIdentifier + lines[0]); + _builder + .AppendLine() + .Append(builderName ?? EntityLambdaIdentifier) + .Append(lines[0]); using (_builder.Indent()) { foreach (var line in lines.Skip(1)) { - _builder.AppendLine(); - _builder.Append(line); + _builder + .AppendLine() + .Append(line); } } diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 3e80ae0dc5d..a10c536e4f2 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -157,6 +157,12 @@ public virtual void RemoveAnnotationsHandledByConventions( RemoveConventionalAnnotationsHelper(entityType, annotations, IsHandledByConvention); } + /// + public void RemoveAnnotationsHandledByConventions( + IEntityTypeMappingFragment fragment, + IDictionary annotations) + => RemoveConventionalAnnotationsHelper(fragment, annotations, IsHandledByConvention); + /// public virtual void RemoveAnnotationsHandledByConventions( IProperty property, @@ -185,6 +191,20 @@ public virtual void RemoveAnnotationsHandledByConventions( public virtual void RemoveAnnotationsHandledByConventions(IIndex index, IDictionary annotations) => RemoveConventionalAnnotationsHelper(index, annotations, IsHandledByConvention); + /// + public virtual void RemoveAnnotationsHandledByConventions( + ICheckConstraint checkConstraint, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(checkConstraint, annotations, IsHandledByConvention); + + /// + public virtual void RemoveAnnotationsHandledByConventions(ITrigger trigger, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(trigger, annotations, IsHandledByConvention); + + /// + public virtual void RemoveAnnotationsHandledByConventions( + IRelationalPropertyOverrides overrides, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(overrides, annotations, IsHandledByConvention); + /// public virtual IReadOnlyList GenerateFluentApiCalls( IModel model, @@ -244,6 +264,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + /// + public IReadOnlyList GenerateFluentApiCalls( + IEntityTypeMappingFragment fragment, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(fragment, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + /// public virtual IReadOnlyList GenerateFluentApiCalls( IProperty property, @@ -371,6 +403,42 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + ICheckConstraint checkConstraint, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(checkConstraint, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + ITrigger trigger, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(trigger, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IRelationalPropertyOverrides overrides, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(overrides, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + /// public virtual IReadOnlyList GenerateDataAnnotationAttributes( IEntityType entityType, @@ -434,6 +502,19 @@ protected virtual bool IsHandledByConvention(IModel model, IAnnotation annotatio protected virtual bool IsHandledByConvention(IEntityType entityType, IAnnotation annotation) => false; + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(IEntityTypeMappingFragment fragment, IAnnotation annotation) + => false; + /// /// Checks if the given is handled by convention when /// applied to the given . @@ -486,6 +567,45 @@ protected virtual bool IsHandledByConvention(IForeignKey foreignKey, IAnnotation protected virtual bool IsHandledByConvention(IIndex index, IAnnotation annotation) => false; + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(ICheckConstraint checkConstraint, IAnnotation annotation) + => false; + + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(ITrigger trigger, IAnnotation annotation) + => false; + + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(IRelationalPropertyOverrides overrides, IAnnotation annotation) + => false; + /// /// Returns a fluent API call for the given , or /// if no fluent API call exists for it. @@ -512,6 +632,19 @@ protected virtual bool IsHandledByConvention(IIndex index, IAnnotation annotatio protected virtual MethodCallCodeFragment? GenerateFluentApi(IEntityType entityType, IAnnotation annotation) => null; + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(IEntityTypeMappingFragment fragment, IAnnotation annotation) + => null; + /// /// Returns a fluent API call for the given , or /// if no fluent API call exists for it. @@ -590,6 +723,45 @@ protected virtual bool IsHandledByConvention(IIndex index, IAnnotation annotatio protected virtual MethodCallCodeFragment? GenerateFluentApi(IIndex index, IAnnotation annotation) => null; + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(ICheckConstraint checkConstraint, IAnnotation annotation) + => null; + + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(ITrigger trigger, IAnnotation annotation) + => null; + + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(IRelationalPropertyOverrides overrides, IAnnotation annotation) + => null; + /// /// Returns a data annotation attribute code fragment for the given , /// or if no data annotation exists for it. diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index 68b9fd17393..a4f6572de06 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -48,6 +48,16 @@ void RemoveAnnotationsHandledByConventions(IEntityType entity, IDictionary + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. + /// + /// The entity mapping fragment to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveAnnotationsHandledByConventions(IEntityTypeMappingFragment fragment, IDictionary annotations) + { + } + /// /// Removes annotation whose configuration is already applied by convention, and do not need to be /// specified explicitly. @@ -136,6 +146,10 @@ void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary RemoveAnnotationsHandledByConventions(entityType, annotations); return; + case IEntityTypeMappingFragment fragment: + RemoveAnnotationsHandledByConventions(fragment, annotations); + return; + case IProperty property: RemoveAnnotationsHandledByConventions(property, annotations); return; @@ -199,6 +213,17 @@ IReadOnlyList GenerateFluentApiCalls( IDictionary annotations) => Array.Empty(); + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The entity mapping fragment to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + IEntityTypeMappingFragment fragment, + IDictionary annotations) + => Array.Empty(); + /// /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls /// and removes the annotations. @@ -309,6 +334,7 @@ IReadOnlyList GenerateFluentApiCalls(IAnnotatable annota { IModel model => GenerateFluentApiCalls(model, annotations), IEntityType entityType => GenerateFluentApiCalls(entityType, annotations), + IEntityTypeMappingFragment fragment => GenerateFluentApiCalls(fragment, annotations), IProperty property => GenerateFluentApiCalls(property, annotations), IKey key => GenerateFluentApiCalls(key, annotations), IForeignKey foreignKey => GenerateFluentApiCalls(foreignKey, annotations), diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 99233db8e21..867ba686d71 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -385,7 +385,7 @@ private void Create( CreateAnnotations( fragment, - GenerateOverrides, + Generate, parameters with { TargetName = overrideVariable }); mainBuilder.Append(fragmentsVariable).Append(".Add("); @@ -396,6 +396,14 @@ private void Create( .Append(overrideVariable).AppendLine(");"); } + /// + /// Generates code to create the given annotations. + /// + /// The fragment to which the annotations are applied. + /// Additional parameters used during code generation. + public virtual void Generate(IEntityTypeMappingFragment fragment, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + => GenerateSimpleAnnotations(parameters); + private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var code = Dependencies.CSharpHelper; @@ -500,7 +508,7 @@ private void Create( CreateAnnotations( overrides, - GenerateOverrides, + Generate, parameters with { TargetName = overrideVariable }); mainBuilder.Append(overridesVariable).Append(".Add("); @@ -516,7 +524,7 @@ private void Create( /// /// The property overrides to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void GenerateOverrides(IAnnotatable overrides, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IRelationalPropertyOverrides overrides, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) => GenerateSimpleAnnotations(parameters); /// diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 7dff017dcce..6956098c8c5 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -51,7 +51,7 @@ public static string GetColumnName(this IReadOnlyProperty property) /// The name of the column to which the property is mapped. public static string? GetColumnName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - var overrides = RelationalPropertyOverrides.Find(property, storeObject); + var overrides = property.FindOverrides(storeObject); if (overrides?.ColumnNameOverridden == true) { return overrides.ColumnName; @@ -294,8 +294,7 @@ public static void SetColumnName( public static ConfigurationSource? GetColumnNameConfigurationSource( this IConventionProperty property, in StoreObjectIdentifier storeObject) - => ((IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject)) - ?.GetColumnNameConfigurationSource(); + => property.FindOverrides(storeObject)?.GetColumnNameConfigurationSource(); /// /// Returns the order of the column this property is mapped to. @@ -1472,6 +1471,40 @@ public static IEnumerable GetOverrides(this IPrope in StoreObjectIdentifier storeObject) => RelationalPropertyOverrides.Find(property, storeObject); + /// + /// + /// Returns the property facet overrides for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// An object that stores property facet overrides. + public static IMutableRelationalPropertyOverrides? FindOverrides( + this IMutableProperty property, + in StoreObjectIdentifier storeObject) + => (IMutableRelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject); + + /// + /// + /// Returns the property facet overrides for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// An object that stores property facet overrides. + public static IConventionRelationalPropertyOverrides? FindOverrides( + this IConventionProperty property, + in StoreObjectIdentifier storeObject) + => (IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject); + /// /// /// Returns the property facet overrides for a particular table-like store object. diff --git a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs index 484e6fd5e7f..360b3c8ef73 100644 --- a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs @@ -62,6 +62,23 @@ public virtual ColumnBuilder HasColumnName(string? name) return this; } + /// + /// Adds or updates an annotation on the property for a specific table. + /// If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ColumnBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + PropertyBuilder IInfrastructure.Instance => PropertyBuilder; #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs index 51e666742a8..d51269971e3 100644 --- a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs @@ -34,6 +34,17 @@ private PropertyBuilder PropertyBuilder /// The same builder instance so that multiple calls can be chained. public new virtual ColumnBuilder HasColumnName(string? name) => (ColumnBuilder)base.HasColumnName(name); + + /// + /// Adds or updates an annotation on the property for a specific table. + /// If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ColumnBuilder HasAnnotation(string annotation, object? value) + => (ColumnBuilder)base.HasAnnotation(annotation, value); PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs index 7e7f7596b1d..45490cff19f 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs @@ -96,6 +96,22 @@ public virtual ColumnBuilder Property(string propertyName) public virtual ColumnBuilder Property(string propertyName) => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyName)); + /// + /// Adds or updates an annotation on the table. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationSplitTableBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + MappingFragment.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs index ee02ff954c5..a747d19bc10 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs @@ -55,6 +55,17 @@ private OwnedNavigationBuilder OwnedNavigationBu public virtual ColumnBuilder Property(Expression> propertyExpression) => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); + /// + /// Adds or updates an annotation on the table. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationSplitTableBuilder HasAnnotation( + string annotation, object? value) + => (OwnedNavigationSplitTableBuilder)base.HasAnnotation(annotation, value); + OwnedNavigationBuilder IInfrastructure>.Instance => OwnedNavigationBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs index 6ad456007ac..2a0d2e0646c 100644 --- a/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs @@ -114,6 +114,22 @@ public virtual SequenceBuilder IsCyclic(bool cyclic = true) return this; } + /// + /// Adds or updates an annotation on the sequence. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual SequenceBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs index b9f1cf14db7..7b57fa68ed4 100644 --- a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs @@ -96,6 +96,22 @@ public virtual ColumnBuilder Property(string propertyName) public virtual ColumnBuilder Property(string propertyName) => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyName)); + /// + /// Adds or updates an annotation on the table. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual SplitTableBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + MappingFragment.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs index 6d02e378b27..6a244425e45 100644 --- a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs @@ -47,5 +47,15 @@ private EntityTypeBuilder EntityTypeBuilder public virtual ColumnBuilder Property(Expression> propertyExpression) => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyExpression)); + /// + /// Adds or updates an annotation on the table. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual SplitTableBuilder HasAnnotation(string annotation, object? value) + => (SplitTableBuilder)base.HasAnnotation(annotation, value); + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; } diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index c26efe1b249..2f6cf549ec4 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -176,6 +176,13 @@ public override IReadOnlyList GenerateFluentApiCalls( return fragments; } + /// + public override IReadOnlyList GenerateFluentApiCalls( + IRelationalPropertyOverrides overrides, IDictionary annotations) + { + return base.GenerateFluentApiCalls(overrides, annotations); + } + /// /// 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 diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs index c1552ade2f0..67a61fdf330 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs @@ -106,4 +106,17 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod base.Generate(entityType, parameters); } + + /// + public override void Generate(IRelationalPropertyOverrides overrides, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + } + + base.Generate(overrides, parameters); + } } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs index 5bf965a51d1..d9d1f9a8f6b 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs @@ -183,6 +183,32 @@ public static PropertyBuilder UseIdentityColumn( int increment = 1) => propertyBuilder.UseIdentityColumn((long)seed, increment); + /// + /// Configures the key column to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the column being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ColumnBuilder UseIdentityColumn( + this ColumnBuilder columnBuilder, + long seed = 1, + int increment = 1) + { + var overrides = columnBuilder.Overrides; + overrides.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.IdentityColumn); + overrides.SetIdentitySeed(seed); + overrides.SetIdentityIncrement(increment); + + return columnBuilder; + } + /// /// Configures the key property to use the SQL Server IDENTITY feature to generate values for new entities, /// when targeting SQL Server. This method sets the property to be . @@ -223,6 +249,26 @@ public static PropertyBuilder UseIdentityColumn( int increment = 1) => (PropertyBuilder)UseIdentityColumn((PropertyBuilder)propertyBuilder, (long)seed, increment); + /// + /// Configures the key column to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the column being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ColumnBuilder UseIdentityColumn( + this ColumnBuilder columnBuilder, + long seed = 1, + int increment = 1) + => (ColumnBuilder)UseIdentityColumn((ColumnBuilder)columnBuilder, seed, increment); + /// /// Configures the seed for SQL Server IDENTITY. /// @@ -252,6 +298,37 @@ public static PropertyBuilder UseIdentityColumn( return null; } + /// + /// Configures the seed for SQL Server IDENTITY for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnSeed(seed, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentitySeed(seed, storeObject, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + /// /// Returns a value indicating whether the given value can be set as the seed for SQL Server IDENTITY. /// @@ -270,6 +347,32 @@ public static bool CanSetIdentityColumnSeed( bool fromDataAnnotation = false) => propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.IdentitySeed, seed, fromDataAnnotation); + /// + /// Returns a value indicating whether the given value can be set as the seed for SQL Server IDENTITY + /// for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the seed for SQL Server IDENTITY. + public static bool CanSetIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => ((ConventionAnnotatable?)propertyBuilder.Metadata.FindOverrides(storeObject)) + ?.CanSetAnnotation( + SqlServerAnnotationNames.IdentitySeed, + seed, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + ?? true; + /// /// Configures the increment for SQL Server IDENTITY. /// @@ -299,6 +402,37 @@ public static bool CanSetIdentityColumnSeed( return null; } + /// + /// Configures the increment for SQL Server IDENTITY for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnIncrement(increment, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentityIncrement(increment, storeObject, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + /// /// Returns a value indicating whether the given value can be set as the increment for SQL Server IDENTITY. /// @@ -317,6 +451,32 @@ public static bool CanSetIdentityColumnIncrement( bool fromDataAnnotation = false) => propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.IdentityIncrement, increment, fromDataAnnotation); + /// + /// Returns a value indicating whether the given value can be set as the increment for SQL Server IDENTITY + /// for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default increment for SQL Server IDENTITY. + public static bool CanSetIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => ((ConventionAnnotatable?)propertyBuilder.Metadata.FindOverrides(storeObject)) + ?.CanSetAnnotation( + SqlServerAnnotationNames.IdentityIncrement, + increment, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + ?? true; + /// /// Configures the value generation strategy for the key property, when targeting SQL Server. /// @@ -358,6 +518,43 @@ public static bool CanSetIdentityColumnIncrement( return null; } + /// + /// Configures the value generation strategy for the key property, when targeting SQL Server for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value generation strategy. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetValueGenerationStrategy(valueGenerationStrategy, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetValueGenerationStrategy(valueGenerationStrategy, storeObject, fromDataAnnotation); + if (valueGenerationStrategy != SqlServerValueGenerationStrategy.IdentityColumn) + { + propertyBuilder.HasIdentityColumnSeed(null, storeObject, fromDataAnnotation); + propertyBuilder.HasIdentityColumnIncrement(null, storeObject, fromDataAnnotation); + } + + return propertyBuilder; + } + + return null; + } + /// /// Returns a value indicating whether the given value can be set as the value generation strategy. /// @@ -379,6 +576,33 @@ public static bool CanSetValueGenerationStrategy( && propertyBuilder.CanSetAnnotation( SqlServerAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); + /// + /// Returns a value indicating whether the given value can be set as the value generation strategy for a particular table. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value generation strategy. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default value generation strategy. + public static bool CanSetValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => (valueGenerationStrategy == null + || SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + && (((ConventionAnnotatable?)propertyBuilder.Metadata.FindOverrides(storeObject)) + ?.CanSetAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, + valueGenerationStrategy, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + ?? true); + /// /// Configures whether the property's column is created as sparse when targeting SQL Server. /// diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index 2cfa3f3d202..ec60455e0e6 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -231,6 +231,12 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); } + var @override = property.FindOverrides(storeObject)?.FindAnnotation(SqlServerAnnotationNames.IdentitySeed); + if (@override != null) + { + return (long?)@override.Value; + } + var annotation = property.FindAnnotation(SqlServerAnnotationNames.IdentitySeed); if (annotation is not null) { @@ -246,6 +252,16 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? : sharedProperty.GetIdentitySeed(storeObject); } + /// + /// Returns the identity seed. + /// + /// The property overrides. + /// The identity seed. + public static long? GetIdentitySeed(this IReadOnlyRelationalPropertyOverrides overrides) + => overrides is RuntimeRelationalPropertyOverrides + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (long?)overrides.FindAnnotation(SqlServerAnnotationNames.IdentitySeed)?.Value; + /// /// Sets the identity seed. /// @@ -276,6 +292,56 @@ public static void SetIdentitySeed(this IMutableProperty property, long? seed) return seed; } + /// + /// Sets the identity seed for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + public static void SetIdentitySeed( + this IMutableProperty property, + long? seed, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetIdentitySeed(seed); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static long? SetIdentitySeed( + this IConventionProperty property, + long? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetIdentitySeed(seed, fromDataAnnotation); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property overrides. + /// The value to set. + public static void SetIdentitySeed(this IMutableRelationalPropertyOverrides overrides, long? seed) + => overrides.SetOrRemoveAnnotation(SqlServerAnnotationNames.IdentitySeed, seed); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property overrides. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static long? SetIdentitySeed( + this IConventionRelationalPropertyOverrides overrides, + long? seed, + bool fromDataAnnotation = false) + => (long?)overrides.SetOrRemoveAnnotation(SqlServerAnnotationNames.IdentitySeed, seed, fromDataAnnotation)?.Value; + /// /// Returns the for the identity seed. /// @@ -284,6 +350,26 @@ public static void SetIdentitySeed(this IMutableProperty property, long? seed) public static ConfigurationSource? GetIdentitySeedConfigurationSource(this IConventionProperty property) => property.FindAnnotation(SqlServerAnnotationNames.IdentitySeed)?.GetConfigurationSource(); + /// + /// Returns the for the identity seed for a particular table. + /// + /// The property. + /// The identifier of the table containing the column. + /// The for the identity seed. + public static ConfigurationSource? GetIdentitySeedConfigurationSource( + this IConventionProperty property, + in StoreObjectIdentifier storeObject) + => property.FindOverrides(storeObject)?.GetIdentitySeedConfigurationSource(); + + /// + /// Returns the for the identity seed for a particular table. + /// + /// The property overrides. + /// The for the identity seed. + public static ConfigurationSource? GetIdentitySeedConfigurationSource( + this IConventionRelationalPropertyOverrides overrides) + => overrides.FindAnnotation(SqlServerAnnotationNames.IdentitySeed)?.GetConfigurationSource(); + /// /// Returns the identity increment. /// @@ -308,6 +394,12 @@ public static void SetIdentitySeed(this IMutableProperty property, long? seed) throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); } + var @override = property.FindOverrides(storeObject)?.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement); + if (@override != null) + { + return (int?)@override.Value; + } + var annotation = property.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement); if (annotation != null) { @@ -320,6 +412,16 @@ public static void SetIdentitySeed(this IMutableProperty property, long? seed) : sharedProperty.GetIdentityIncrement(storeObject); } + /// + /// Returns the identity increment. + /// + /// The property overrides. + /// The identity increment. + public static int? GetIdentityIncrement(this IReadOnlyRelationalPropertyOverrides overrides) + => overrides is RuntimeRelationalPropertyOverrides + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)overrides.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement)?.Value; + /// /// Sets the identity increment. /// @@ -341,14 +443,61 @@ public static void SetIdentityIncrement(this IMutableProperty property, int? inc this IConventionProperty property, int? increment, bool fromDataAnnotation = false) - { - property.SetOrRemoveAnnotation( + => (int?)property.SetOrRemoveAnnotation( SqlServerAnnotationNames.IdentityIncrement, increment, - fromDataAnnotation); + fromDataAnnotation)?.Value; - return increment; - } + /// + /// Sets the identity increment for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + public static void SetIdentityIncrement( + this IMutableProperty property, + int? increment, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetIdentityIncrement(increment); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetIdentityIncrement( + this IConventionProperty property, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetIdentityIncrement(increment, fromDataAnnotation); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property overrides. + /// The value to set. + public static void SetIdentityIncrement(this IMutableRelationalPropertyOverrides overrides, int? increment) + => overrides.SetOrRemoveAnnotation(SqlServerAnnotationNames.IdentityIncrement, increment); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property overrides. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetIdentityIncrement( + this IConventionRelationalPropertyOverrides overrides, + int? increment, + bool fromDataAnnotation = false) + => (int?)overrides.SetOrRemoveAnnotation(SqlServerAnnotationNames.IdentityIncrement, increment, fromDataAnnotation) + ?.Value; /// /// Returns the for the identity increment. @@ -358,6 +507,26 @@ public static void SetIdentityIncrement(this IMutableProperty property, int? inc public static ConfigurationSource? GetIdentityIncrementConfigurationSource(this IConventionProperty property) => property.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement)?.GetConfigurationSource(); + /// + /// Returns the for the identity increment for a particular table. + /// + /// The property. + /// The identifier of the table containing the column. + /// The for the identity increment. + public static ConfigurationSource? GetIdentityIncrementConfigurationSource( + this IConventionProperty property, + in StoreObjectIdentifier storeObject) + => property.FindOverrides(storeObject)?.GetIdentityIncrementConfigurationSource(); + + /// + /// Returns the for the identity increment for a particular table. + /// + /// The property overrides. + /// The for the identity increment. + public static ConfigurationSource? GetIdentityIncrementConfigurationSource( + this IConventionRelationalPropertyOverrides overrides) + => overrides.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement)?.GetConfigurationSource(); + /// /// Returns the to use for the property. /// @@ -405,6 +574,12 @@ internal static SqlServerValueGenerationStrategy GetValueGenerationStrategy( in StoreObjectIdentifier storeObject, ITypeMappingSource? typeMappingSource) { + var @override = property.FindOverrides(storeObject)?.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); + if (@override != null) + { + return (SqlServerValueGenerationStrategy?)@override.Value ?? SqlServerValueGenerationStrategy.None; + } + var annotation = property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); if (annotation?.Value != null && StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) == storeObject) @@ -455,6 +630,19 @@ is StoreObjectIdentifier principal return defaultStategy; } + /// + /// Returns the to use for the property. + /// + /// + /// If no strategy is set for the property, then the strategy to use will be taken from the . + /// + /// The property overrides. + /// The strategy, or if none was set. + public static SqlServerValueGenerationStrategy? GetValueGenerationStrategy( + this IReadOnlyRelationalPropertyOverrides overrides) + => (SqlServerValueGenerationStrategy?)overrides.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy) + ?.Value; + private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrategy(IReadOnlyProperty property) { var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy(); @@ -517,9 +705,71 @@ public static void SetValueGenerationStrategy( { CheckValueGenerationStrategy(property, value); - property.SetOrRemoveAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation); + return (SqlServerValueGenerationStrategy?)property.SetOrRemoveAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation) + ?.Value; + } + + /// + /// Sets the to use for the property for a particular table. + /// + /// The property. + /// The strategy to use. + /// The identifier of the table containing the column. + public static void SetValueGenerationStrategy( + this IMutableProperty property, + SqlServerValueGenerationStrategy? value, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetValueGenerationStrategy(value); + + /// + /// Sets the to use for the property for a particular table. + /// + /// The property. + /// The strategy to use. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static SqlServerValueGenerationStrategy? SetValueGenerationStrategy( + this IConventionProperty property, + SqlServerValueGenerationStrategy? value, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetValueGenerationStrategy(value, fromDataAnnotation); + + /// + /// Sets the to use for the property for a particular table. + /// + /// The property overrides. + /// The strategy to use. + public static void SetValueGenerationStrategy( + this IMutableRelationalPropertyOverrides overrides, + SqlServerValueGenerationStrategy? value) + { + CheckValueGenerationStrategy(overrides.Property, value); + + overrides.SetOrRemoveAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy, value); + } + + /// + /// Sets the to use for the property for a particular table. + /// + /// The property overrides. + /// The strategy to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static SqlServerValueGenerationStrategy? SetValueGenerationStrategy( + this IConventionRelationalPropertyOverrides overrides, + SqlServerValueGenerationStrategy? value, + bool fromDataAnnotation = false) + { + CheckValueGenerationStrategy(overrides.Property, value); - return value; + return (SqlServerValueGenerationStrategy?)overrides.SetOrRemoveAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation) + ?.Value; } private static void CheckValueGenerationStrategy(IReadOnlyProperty property, SqlServerValueGenerationStrategy? value) @@ -555,6 +805,26 @@ private static void CheckValueGenerationStrategy(IReadOnlyProperty property, Sql this IConventionProperty property) => property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy)?.GetConfigurationSource(); + /// + /// Returns the for the for a particular table. + /// + /// The property. + /// The identifier of the table containing the column. + /// The for the . + public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource( + this IConventionProperty property, + in StoreObjectIdentifier storeObject) + => property.FindOverrides(storeObject)?.GetValueGenerationStrategyConfigurationSource(); + + /// + /// Returns the for the for a particular table. + /// + /// The property overrides. + /// The for the . + public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource( + this IConventionRelationalPropertyOverrides overrides) + => overrides.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy)?.GetConfigurationSource(); + /// /// Returns a value indicating whether the property is compatible with any . /// @@ -566,10 +836,9 @@ public static bool IsCompatibleWithValueGeneration(IReadOnlyProperty property) ?? property.FindTypeMapping()?.Converter; var type = (valueConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); - - return (type.IsInteger() + return type.IsInteger() || type.IsEnum - || type == typeof(decimal)); + || type == typeof(decimal); } private static bool IsCompatibleWithValueGeneration( diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs index 2f0deede3c9..76fb8b3bc92 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs @@ -29,13 +29,7 @@ public SqlServerRuntimeModelConvention( { } - /// - /// Updates the model annotations that will be set on the read-only object. - /// - /// The annotations to be processed. - /// The source model. - /// The target model that will contain the annotations. - /// Indicates whether the given annotations are runtime annotations. + /// protected override void ProcessModelAnnotations( Dictionary annotations, IModel model, @@ -54,13 +48,7 @@ protected override void ProcessModelAnnotations( } } - /// - /// Updates the property annotations that will be set on the read-only object. - /// - /// The annotations to be processed. - /// The source property. - /// The target property that will contain the annotations. - /// Indicates whether the given annotations are runtime annotations. + /// protected override void ProcessPropertyAnnotations( Dictionary annotations, IProperty property, @@ -82,13 +70,23 @@ protected override void ProcessPropertyAnnotations( } } - /// - /// Updates the index annotations that will be set on the read-only object. - /// - /// The annotations to be processed. - /// The source index. - /// The target index that will contain the annotations. - /// Indicates whether the given annotations are runtime annotations. + /// + protected override void ProcessPropertyOverridesAnnotations( + Dictionary annotations, + IRelationalPropertyOverrides propertyOverrides, + RuntimeRelationalPropertyOverrides runtimePropertyOverrides, + bool runtime) + { + base.ProcessPropertyOverridesAnnotations(annotations, propertyOverrides, runtimePropertyOverrides, runtime); + + if (!runtime) + { + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + } + } + + /// protected override void ProcessIndexAnnotations( Dictionary annotations, IIndex index, @@ -106,13 +104,7 @@ protected override void ProcessIndexAnnotations( } } - /// - /// Updates the key annotations that will be set on the read-only object. - /// - /// The annotations to be processed. - /// The source key. - /// The target key that will contain the annotations. - /// Indicates whether the given annotations are runtime annotations. + /// protected override void ProcessKeyAnnotations( IDictionary annotations, IKey key, @@ -127,13 +119,7 @@ protected override void ProcessKeyAnnotations( } } - /// - /// Updates the entity type annotations that will be set on the read-only object. - /// - /// The annotations to be processed. - /// The source entity type. - /// The target entity type that will contain the annotations. - /// Indicates whether the given annotations are runtime annotations. + /// protected override void ProcessEntityTypeAnnotations( IDictionary annotations, IEntityType entityType, diff --git a/src/EFCore/Infrastructure/AnnotatableBuilder.cs b/src/EFCore/Infrastructure/AnnotatableBuilder.cs index 5b857ca90f7..2407046536a 100644 --- a/src/EFCore/Infrastructure/AnnotatableBuilder.cs +++ b/src/EFCore/Infrastructure/AnnotatableBuilder.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Metadata.Internal; - namespace Microsoft.EntityFrameworkCore.Infrastructure; /// @@ -71,7 +69,10 @@ protected AnnotatableBuilder(TMetadata metadata, TModelBuilder modelBuilder) return this; } - if (!CanSetAnnotationValue(existingAnnotation, value, configurationSource, canOverrideSameSource)) + var existingConfigurationSource = existingAnnotation.GetConfigurationSource(); + if (!configurationSource.Overrides(existingConfigurationSource) + || (configurationSource == existingConfigurationSource + && !canOverrideSameSource)) { return null; } @@ -111,28 +112,7 @@ protected AnnotatableBuilder(TMetadata metadata, TModelBuilder modelBuilder) /// The configuration source of the annotation to be set. /// if the annotation can be set, otherwise. public virtual bool CanSetAnnotation(string name, object? value, ConfigurationSource configurationSource) - { - var existingAnnotation = Metadata.FindAnnotation(name); - return existingAnnotation == null - || CanSetAnnotationValue(existingAnnotation, value, configurationSource, canOverrideSameSource: true); - } - - private static bool CanSetAnnotationValue( - ConventionAnnotation annotation, - object? value, - ConfigurationSource configurationSource, - bool canOverrideSameSource) - { - if (Equals(annotation.Value, value)) - { - return true; - } - - var existingConfigurationSource = annotation.GetConfigurationSource(); - return configurationSource.Overrides(existingConfigurationSource) - && (configurationSource != existingConfigurationSource - || canOverrideSameSource); - } + => Metadata.CanSetAnnotation(name, value, configurationSource); /// /// Removes any annotation with the given name. diff --git a/src/EFCore/Infrastructure/ConventionAnnotatable.cs b/src/EFCore/Infrastructure/ConventionAnnotatable.cs index f4fd65eeeb6..7f114ab2cde 100644 --- a/src/EFCore/Infrastructure/ConventionAnnotatable.cs +++ b/src/EFCore/Infrastructure/ConventionAnnotatable.cs @@ -56,6 +56,7 @@ public override void SetAnnotation(string name, object? value) /// The key of the annotation to be added. /// The value to be stored in the annotation. /// The configuration source of the annotation to be set. + /// The new annotation. public virtual ConventionAnnotation? SetAnnotation( string name, object? value, @@ -77,11 +78,14 @@ public override void SetAnnotation(string name, object? value) } /// - /// 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. + /// Sets the annotation with given key and value on this object using given configuration source. + /// Removes the existing annotation if an annotation with the specified name already exists and + /// is . /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + /// The configuration source of the annotation to be set. + /// The new annotation. public virtual ConventionAnnotation? SetOrRemoveAnnotation( string name, object? value, @@ -96,6 +100,38 @@ public override void SetAnnotation(string name, object? value) return SetAnnotation(name, value, configurationSource); } + /// + /// Returns a value indicating whether an annotation with the given name and value can be set + /// from this configuration source. + /// + /// The name of the annotation to be added. + /// The value to be stored in the annotation. + /// The configuration source of the annotation to be set. + /// if the annotation can be set, otherwise. + public virtual bool CanSetAnnotation(string name, object? value, ConfigurationSource configurationSource) + { + var existingAnnotation = FindAnnotation(name); + return existingAnnotation == null + || CanSetAnnotationValue(existingAnnotation, value, configurationSource, canOverrideSameSource: true); + } + + private static bool CanSetAnnotationValue( + ConventionAnnotation annotation, + object? value, + ConfigurationSource configurationSource, + bool canOverrideSameSource) + { + if (Equals(annotation.Value, value)) + { + return true; + } + + var existingConfigurationSource = annotation.GetConfigurationSource(); + return configurationSource.Overrides(existingConfigurationSource) + && (configurationSource != existingConfigurationSource + || canOverrideSameSource); + } + /// /// Called when an annotation was set or removed. /// diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs index 85155be2193..d7f26ba3a71 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs @@ -409,7 +409,9 @@ private bool TryFindMatchingProperties( var referencedProperty = propertiesToReference[i]; var property = TryGetProperty( dependentEntityType, - baseName, referencedProperty.Name); + baseName, + referencedProperty.Name, + matchImplicitProperties: propertiesToReference.Count != 1); if (property == null) { @@ -420,13 +422,20 @@ private bool TryFindMatchingProperties( foreignKeyProperties[i] = property; } + if (propertiesToReference.Count != 1 + && propertiesToReference.All(p => p.IsImplicitlyCreated() + && ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()))) + { + return false; + } + if (!matchFound && propertiesToReference.Count == 1 && baseName.Length > 0) { var property = TryGetProperty( dependentEntityType, - baseName, "Id"); + baseName, "Id", matchImplicitProperties: false); if (property != null) { @@ -504,11 +513,13 @@ private bool TryFindMatchingProperties( return true; } - private static IConventionProperty? TryGetProperty(IConventionEntityType entityType, string prefix, string suffix) + private static IConventionProperty? TryGetProperty( + IConventionEntityType entityType, string prefix, string suffix, bool matchImplicitProperties) { foreach (var property in entityType.GetProperties()) { if ((!property.IsImplicitlyCreated() + || matchImplicitProperties || !ConfigurationSource.Convention.Overrides(property.GetConfigurationSource())) && property.Name.Length == prefix.Length + suffix.Length && property.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 1385e3b7c32..9bf3533ca60 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -668,11 +668,14 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables() b.Property("Shadow").HasColumnName("Shadow"); b.ToTable("Order", tb => { + tb.Property(e => e.Id).UseIdentityColumn(2, 3).HasAnnotation("fii", "arr"); tb.Property("Shadow"); }); b.SplitToTable("SplitOrder", sb => { sb.Property("Shadow"); + sb.HasTrigger("splitTrigger").HasAnnotation("oof", "rab"); + sb.HasAnnotation("foo", "bar"); }); b.OwnsOne(p => p.OrderBillingDetails, od => @@ -725,6 +728,7 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables() b.ToTable(""Order"", null, t => { + SqlServerPropertyBuilderExtensions.UseIdentityColumn(t.Property(""Id""), 2L, 3); t.Property(""Shadow""); }); @@ -852,6 +856,17 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables() var orderEntityType = model.FindEntityType(typeof(Order)); Assert.Equal(nameof(Order), orderEntityType.GetTableName()); + var id = orderEntityType.FindProperty("Id"); + Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, id.GetValueGenerationStrategy()); + Assert.Equal(1, id.GetIdentitySeed()); + Assert.Equal(1, id.GetIdentityIncrement()); + + var overrides = id.FindOverrides(StoreObjectIdentifier.Create(orderEntityType, StoreObjectType.Table).Value)!; + Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, overrides.GetValueGenerationStrategy()); + Assert.Equal(2, overrides.GetIdentitySeed()); + Assert.Equal(3, overrides.GetIdentityIncrement()); + Assert.Equal("arr", overrides["fii"]); + var billingOwnership = orderEntityType.FindNavigation(nameof(Order.OrderBillingDetails)) .ForeignKey; var billingEntityType = billingOwnership.DeclaringEntityType; @@ -884,6 +899,12 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables() var splitTable = relationalModel.FindTable(fragment.StoreObject.Name, fragment.StoreObject.Schema); Assert.Equal(new[] { billingEntityType, orderEntityType }, splitTable.FindColumn("Shadow").PropertyMappings.Select(m => m.TableMapping.EntityType)); + Assert.Equal("bar", fragment["foo"]); + + var trigger = orderEntityType.GetTriggers().Single(); + Assert.Equal(splitTable.Name, trigger.TableName); + Assert.Equal(splitTable.Schema, trigger.TableSchema); + Assert.Equal("bar", trigger["foo"]); var billingFragment = billingEntityType.GetMappingFragments().Single(); var billingTable = relationalModel.FindTable(billingFragment.StoreObject.Name, billingFragment.StoreObject.Schema); @@ -1133,7 +1154,8 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .HasMin(1) .HasMax(3) .IncrementsBy(2) - .IsCyclic(); + .IsCyclic() + .HasAnnotation("foo", "bar"); }, AddBoilerPlate( GetHeading() @@ -1143,10 +1165,19 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IncrementsBy(2) .HasMin(1L) .HasMax(3L) - .IsCyclic();"), - o => + .IsCyclic() + .HasAnnotation(""foo"", ""bar"");"), + model => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(5, model.GetAnnotations().Count()); + + var sequence = model.GetSequences().Single(); + Assert.Equal(2, sequence.StartValue); + Assert.Equal(1, sequence.MinValue); + Assert.Equal(3, sequence.MaxValue); + Assert.Equal(2, sequence.IncrementBy); + Assert.True(sequence.IsCyclic); + Assert.Equal("bar", sequence["foo"]); }); [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index ef23e7ee37d..e7bb3a7ec0a 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1208,8 +1208,18 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba ""PrincipalBaseId"", typeof(long), propertyAccessMode: PropertyAccessMode.Field, + valueGenerated: ValueGenerated.OnAdd, afterSaveBehavior: PropertySaveBehavior.Throw); - principalBaseId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + var overrides = new StoreObjectDictionary(); + var principalBaseIdPrincipalBase = new RuntimeRelationalPropertyOverrides( + principalBaseId, + StoreObjectIdentifier.Table(""PrincipalBase"", ""mySchema""), + false, + null); + principalBaseIdPrincipalBase.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + overrides.Add(StoreObjectIdentifier.Table(""PrincipalBase"", ""mySchema""), principalBaseIdPrincipalBase); + principalBaseId.AddAnnotation(""Relational:RelationalOverrides"", overrides); + principalBaseId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); var principalBaseAlternateId = runtimeEntityType.AddProperty( ""PrincipalBaseAlternateId"", @@ -1226,14 +1236,14 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField(""
k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.Field, nullable: true); - var overrides = new StoreObjectDictionary(); + var overrides0 = new StoreObjectDictionary(); var detailsDetails = new RuntimeRelationalPropertyOverrides( details, StoreObjectIdentifier.Table(""Details"", null), false, null); - overrides.Add(StoreObjectIdentifier.Table(""Details"", null), detailsDetails); - details.AddAnnotation(""Relational:RelationalOverrides"", overrides); + overrides0.Add(StoreObjectIdentifier.Table(""Details"", null), detailsDetails); + details.AddAnnotation(""Relational:RelationalOverrides"", overrides0); details.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); var number = runtimeEntityType.AddProperty( @@ -1883,11 +1893,25 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) CoreStrings.RuntimeModelMissingData, Assert.Throws(() => referenceOwnedType.GetNavigationAccessMode()).Message); + var principalTable = StoreObjectIdentifier.Create(referenceOwnedType, StoreObjectType.Table).Value; + + var ownedId = referenceOwnedType.FindProperty("PrincipalBaseId"); + Assert.True(ownedId.IsPrimaryKey()); + Assert.Equal( + SqlServerValueGenerationStrategy.IdentityColumn, + principalId.GetValueGenerationStrategy(principalTable)); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentityIncrement(principalTable)).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentitySeed(principalTable)).Message); + var ownedFragment = referenceOwnedType.GetMappingFragments().Single(); Assert.Equal(nameof(OwnedType.Details), referenceOwnedType.FindProperty(nameof(OwnedType.Details)).GetColumnName(ownedFragment.StoreObject)); Assert.Null(referenceOwnedType.FindProperty(nameof(OwnedType.Details)) - .GetColumnName(StoreObjectIdentifier.Create(referenceOwnedType, StoreObjectType.Table).Value)); + .GetColumnName(principalTable)); var referenceOwnership = referenceOwnedNavigation.ForeignKey; Assert.Empty(referenceOwnership.GetAnnotations()); @@ -2195,6 +2219,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) ob.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); ob.UsePropertyAccessMode(PropertyAccessMode.Field); + ob.ToTable("PrincipalBase", "mySchema", + t => t.Property("PrincipalBaseId").UseIdentityColumn(2, 3)); + ob.SplitToTable("Details", s => s.Property(e => e.Details)); });