diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index 1bb73c75382..98f1c51809f 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
@@ -889,8 +912,7 @@ private void GenerateTableMapping(
if (hasTriggers)
{
- stringBuilder.AppendLine();
- GenerateTriggers("t", entityType, stringBuilder);
+ GenerateTriggers("t", entityType, tableName!, schema, stringBuilder);
}
if (hasOverrides)
@@ -914,13 +936,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 +952,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 +1050,7 @@ private void GenerateSplitViewMapping(
using (stringBuilder.Indent())
{
GenerateOverrides("v", entityType, fragment.StoreObject, stringBuilder);
+ GenerateEntityTypeMappingFragmentAnnotations("v", fragment, stringBuilder);
}
stringBuilder
@@ -1034,6 +1060,26 @@ 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)
+ {
+ GenerateAnnotations(tableBuilderName, fragment, stringBuilder, annotations, inChainedCall: false);
+ }
+ }
+
///
/// Generates code for objects.
///
@@ -1137,14 +1183,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);
}
}
@@ -1161,13 +1216,14 @@ protected virtual void GenerateTrigger(
IndentedStringBuilder stringBuilder)
{
var triggerBuilderName = $"{tableBuilderName}.HasTrigger({Code.Literal(trigger.ModelName)})";
- stringBuilder.Append(triggerBuilderName);
+ stringBuilder
+ .AppendLine()
+ .Append(triggerBuilderName);
// Note that GenerateAnnotations below does the corresponding decrement
stringBuilder.IncrementIndent();
- if (trigger.Name != null
- && trigger.Name != (trigger.GetDefaultName() ?? trigger.ModelName))
+ if (trigger.Name != trigger.GetDefaultName()!)
{
stringBuilder
.AppendLine()
diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
index 41cbee2abbb..45f5999c6b0 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
@@ -433,57 +433,63 @@ private void GenerateEntityType(IEntityType entityType)
{
_builder.AppendLine();
- _builder.Append($"{EntityLambdaIdentifier}.{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}(tb => ");
+ _builder.AppendLine($"{EntityLambdaIdentifier}.{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}(tb =>");
- // Note: no trigger annotation support as of yet
-
- if (triggers.Length == 1)
- {
- var trigger = triggers[0];
- if (trigger.Name is not null)
- {
- _builder.AppendLine($"tb.HasTrigger({_code.Literal(trigger.Name)}));");
- }
- }
- else
+ using (_builder.Indent())
{
- _builder.AppendLine("{");
-
- using (_builder.Indent())
+ _builder.Append("{");
+ foreach (var trigger in entityType.GetTriggers().Where(t => t.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);
}
-
_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..44ce8440296 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 virtual void RemoveAnnotationsHandledByConventions(
+ IEntityTypeMappingFragment fragment,
+ IDictionary annotations)
+ => RemoveConventionalAnnotationsHelper(fragment, annotations, IsHandledByConvention);
+
///
public virtual void RemoveAnnotationsHandledByConventions(
IProperty property,
@@ -185,6 +191,25 @@ 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 void RemoveAnnotationsHandledByConventions(
+ ISequence sequence, IDictionary annotations)
+ => RemoveConventionalAnnotationsHelper(sequence, annotations, IsHandledByConvention);
+
///
public virtual IReadOnlyList GenerateFluentApiCalls(
IModel model,
@@ -244,6 +269,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls(
return methodCallCodeFragments;
}
+ ///
+ public virtual 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 +408,54 @@ 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 GenerateFluentApiCalls(
+ ISequence sequence,
+ IDictionary annotations)
+ {
+ var methodCallCodeFragments = new List();
+
+ methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(sequence, annotations, GenerateFluentApi));
+
+ return methodCallCodeFragments;
+ }
+
///
public virtual IReadOnlyList GenerateDataAnnotationAttributes(
IEntityType entityType,
@@ -434,6 +519,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 +584,58 @@ 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;
+
+ ///
+ /// Checks if the given is handled by convention when
+ /// applied to the given .
+ ///
+ ///
+ /// The default implementation always returns .
+ ///
+ /// The .
+ /// The .
+ /// .
+ protected virtual bool IsHandledByConvention(ISequence sequence, IAnnotation annotation)
+ => false;
+
///
/// Returns a fluent API call for the given , or
/// if no fluent API call exists for it.
@@ -512,6 +662,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 +753,58 @@ 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 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(ISequence sequence, 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..87fdb3aa92f 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.
@@ -118,6 +128,16 @@ void RemoveAnnotationsHandledByConventions(IRelationalPropertyOverrides override
{
}
+ ///
+ /// Removes annotation whose configuration is already applied by convention, and do not need to be
+ /// specified explicitly.
+ ///
+ /// The sequence to which the annotations are applied.
+ /// The set of annotations from which to remove the conventional ones.
+ void RemoveAnnotationsHandledByConventions(ISequence sequence, IDictionary annotations)
+ {
+ }
+
///
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
/// and removes the annotations.
@@ -136,6 +156,10 @@ void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary
RemoveAnnotationsHandledByConventions(entityType, annotations);
return;
+ case IEntityTypeMappingFragment fragment:
+ RemoveAnnotationsHandledByConventions(fragment, annotations);
+ return;
+
case IProperty property:
RemoveAnnotationsHandledByConventions(property, annotations);
return;
@@ -172,6 +196,10 @@ void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary
RemoveAnnotationsHandledByConventions(overrides, annotations);
return;
+ case ISequence sequence:
+ RemoveAnnotationsHandledByConventions(sequence, annotations);
+ return;
+
default:
throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType()));
}
@@ -199,6 +227,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.
@@ -298,6 +337,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 sequence to which the annotations are applied.
+ /// The set of annotations from which to generate fluent API calls.
+ IReadOnlyList GenerateFluentApiCalls(
+ ISequence sequence,
+ IDictionary annotations)
+ => Array.Empty();
+
///
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
/// and removes the annotations.
@@ -309,7 +359,9 @@ IReadOnlyList GenerateFluentApiCalls(IAnnotatable annota
{
IModel model => GenerateFluentApiCalls(model, annotations),
IEntityType entityType => GenerateFluentApiCalls(entityType, annotations),
+ IEntityTypeMappingFragment fragment => GenerateFluentApiCalls(fragment, annotations),
IProperty property => GenerateFluentApiCalls(property, annotations),
+ IRelationalPropertyOverrides overrides => GenerateFluentApiCalls(overrides, annotations),
IKey key => GenerateFluentApiCalls(key, annotations),
IForeignKey foreignKey => GenerateFluentApiCalls(foreignKey, annotations),
INavigation navigation => GenerateFluentApiCalls(navigation, annotations),
@@ -317,7 +369,7 @@ IReadOnlyList GenerateFluentApiCalls(IAnnotatable annota
IIndex index => GenerateFluentApiCalls(index, annotations),
ICheckConstraint checkConstraint => GenerateFluentApiCalls(checkConstraint, annotations),
ITrigger trigger => GenerateFluentApiCalls(trigger, annotations),
- IRelationalPropertyOverrides overrides => GenerateFluentApiCalls(overrides, annotations),
+ ISequence sequence => GenerateFluentApiCalls(sequence, annotations),
_ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType()))
};
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/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
index bef4f64ea75..c17b3127d68 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
@@ -1075,7 +1075,7 @@ public static IConventionEntityTypeMappingFragment GetOrCreateMappingFragment(
public static IMutableEntityTypeMappingFragment? RemoveMappingFragment(
this IMutableEntityType entityType,
in StoreObjectIdentifier storeObject)
- => EntityTypeMappingFragment.Remove(entityType, storeObject, ConfigurationSource.Explicit);
+ => EntityTypeMappingFragment.Remove(entityType, storeObject);
///
///
@@ -1088,17 +1088,14 @@ public static IConventionEntityTypeMappingFragment GetOrCreateMappingFragment(
///
/// The entity type.
/// The identifier of a table-like store object.
- /// Indicates whether the configuration was specified using a data annotation.
///
/// The removed or
/// if no overrides for the given store object were found or the existing overrides were configured from a higher source.
///
public static IConventionEntityTypeMappingFragment? RemoveMappingFragment(
this IConventionEntityType entityType,
- in StoreObjectIdentifier storeObject,
- bool fromDataAnnotation = false)
- => EntityTypeMappingFragment.Remove((IMutableEntityType)entityType, storeObject,
- fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ in StoreObjectIdentifier storeObject)
+ => EntityTypeMappingFragment.Remove((IMutableEntityType)entityType, storeObject);
///
/// Gets the foreign keys for the given entity type that point to other entity types
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index 7dff017dcce..e14f264d4ad 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.
@@ -1440,6 +1439,36 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope
public static IEnumerable GetOverrides(this IReadOnlyProperty property)
=> RelationalPropertyOverrides.Get(property) ?? Enumerable.Empty();
+ ///
+ ///
+ /// Returns all the property facet overrides.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The property.
+ /// The property facet overrides.
+ public static IEnumerable GetOverrides(this IMutableProperty property)
+ => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
+ ?? Enumerable.Empty();
+
+ ///
+ ///
+ /// Returns all the property facet overrides.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The property.
+ /// The property facet overrides.
+ public static IEnumerable GetOverrides(this IConventionProperty property)
+ => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
+ ?? Enumerable.Empty();
+
///
///
/// Returns all the property facet overrides.
@@ -1452,7 +1481,7 @@ public static IEnumerable GetOverrides(thi
/// The property.
/// The property facet overrides.
public static IEnumerable GetOverrides(this IProperty property)
- => RelationalPropertyOverrides.Get(property)?.Cast()
+ => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
?? Enumerable.Empty();
///
@@ -1472,6 +1501,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.
@@ -1544,7 +1607,7 @@ public static IConventionRelationalPropertyOverrides GetOrCreateOverrides(
public static IMutableRelationalPropertyOverrides? RemoveOverrides(
this IMutableProperty property,
in StoreObjectIdentifier storeObject)
- => RelationalPropertyOverrides.Remove(property, storeObject, ConfigurationSource.Explicit);
+ => RelationalPropertyOverrides.Remove(property, storeObject);
///
///
@@ -1557,17 +1620,14 @@ public static IConventionRelationalPropertyOverrides GetOrCreateOverrides(
///
/// The property.
/// The identifier of a table-like store object.
- /// Indicates whether the configuration was specified using a data annotation.
///
/// The removed or
/// if no overrides for the given store object were found or the existing overrides were configured from a higher source.
///
public static IConventionRelationalPropertyOverrides? RemoveOverrides(
this IConventionProperty property,
- in StoreObjectIdentifier storeObject,
- bool fromDataAnnotation = false)
- => RelationalPropertyOverrides.Remove((IMutableProperty)property, storeObject,
- fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ in StoreObjectIdentifier storeObject)
+ => RelationalPropertyOverrides.Remove((IMutableProperty)property, storeObject);
///
///
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 722d9e77ebb..d8bb74f0d5a 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -1864,8 +1864,10 @@ protected virtual void ValidateTriggers(
RelationalStrings.TriggerOnUnmappedEntityType(trigger.ModelName, entityType.DisplayName()));
}
- if ((trigger.TableName != tableName)
- || (trigger.TableSchema is not null && trigger.TableSchema != tableSchema))
+ if ((trigger.TableName != tableName
+ || trigger.TableSchema != tableSchema)
+ && entityType.GetMappingFragments(StoreObjectType.Table)
+ .All(f => trigger.TableName != f.StoreObject.Name || trigger.TableSchema != f.StoreObject.Schema))
{
throw new InvalidOperationException(
RelationalStrings.TriggerWithMismatchedTable(
diff --git a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs
index 484e6fd5e7f..a8979453ae5 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));
+
+ InternalOverrides.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/IConventionEntityTypeMappingFragmentBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionEntityTypeMappingFragmentBuilder.cs
new file mode 100644
index 00000000000..40e7453da64
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionEntityTypeMappingFragmentBuilder.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public interface IConventionEntityTypeMappingFragmentBuilder : IConventionAnnotatableBuilder
+{
+ ///
+ /// The fragment being configured.
+ ///
+ new IConventionEntityTypeMappingFragment Metadata { get; }
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionRelationalPropertyOverridesBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionRelationalPropertyOverridesBuilder.cs
new file mode 100644
index 00000000000..8f445de4d1f
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionRelationalPropertyOverridesBuilder.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public interface IConventionRelationalPropertyOverridesBuilder : IConventionAnnotatableBuilder
+{
+ ///
+ /// The overrides being configured.
+ ///
+ new IConventionRelationalPropertyOverrides Metadata { get; }
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs
index 7e7f7596b1d..04cbba7bcd4 100644
--- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs
@@ -24,7 +24,7 @@ public OwnedNavigationSplitTableBuilder(in StoreObjectIdentifier storeObject, Ow
Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.Table,
"StoreObjectType should be Table, not " + storeObject.StoreObjectType);
- MappingFragment = EntityTypeMappingFragment.GetOrCreate(
+ InternalMappingFragment = EntityTypeMappingFragment.GetOrCreate(
ownedNavigationBuilder.OwnedEntityType, storeObject, ConfigurationSource.Explicit);
OwnedNavigationBuilder = ownedNavigationBuilder;
}
@@ -39,10 +39,19 @@ public OwnedNavigationSplitTableBuilder(in StoreObjectIdentifier storeObject, Ow
///
public virtual string? Schema => MappingFragment.StoreObject.Schema;
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual EntityTypeMappingFragment InternalMappingFragment { get; }
+
///
/// The mapping fragment being configured.
///
- public virtual IMutableEntityTypeMappingFragment MappingFragment { get; }
+ public virtual IMutableEntityTypeMappingFragment MappingFragment => InternalMappingFragment;
private OwnedNavigationBuilder OwnedNavigationBuilder { get; }
@@ -96,6 +105,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));
+
+ InternalMappingFragment.Builder.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..b5dc8eb4d25 100644
--- a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs
@@ -24,7 +24,7 @@ public SplitTableBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder
Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.Table,
"StoreObjectType should be Table, not " + storeObject.StoreObjectType);
- MappingFragment = EntityTypeMappingFragment.GetOrCreate(
+ InternalMappingFragment = EntityTypeMappingFragment.GetOrCreate(
entityTypeBuilder.Metadata, storeObject, ConfigurationSource.Explicit);
EntityTypeBuilder = entityTypeBuilder;
}
@@ -38,11 +38,20 @@ public SplitTableBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder
/// The specified table schema.
///
public virtual string? Schema => MappingFragment.StoreObject.Schema;
-
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual EntityTypeMappingFragment InternalMappingFragment { get; }
+
///
/// The mapping fragment being configured.
///
- public virtual IMutableEntityTypeMappingFragment MappingFragment { get; }
+ public virtual IMutableEntityTypeMappingFragment MappingFragment => InternalMappingFragment;
private EntityTypeBuilder EntityTypeBuilder { get; }
@@ -96,6 +105,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));
+
+ InternalMappingFragment.Builder.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.Relational/Metadata/Conventions/CheckConstraintConvention.cs b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs
index 6c05c901dcc..f99b58c2609 100644
--- a/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs
@@ -144,7 +144,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
foreach (var checkConstraintToBeDetached in checkConstraintsToBeDetached)
{
var baseCheckConstraint = baseCheckConstraints[checkConstraintToBeDetached.ModelName];
- CheckConstraint.Attach(checkConstraintToBeDetached, baseCheckConstraint);
+ CheckConstraint.MergeInto(checkConstraintToBeDetached, baseCheckConstraint);
checkConstraintToBeDetached.EntityType.RemoveCheckConstraint(checkConstraintToBeDetached.ModelName);
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs
index ee8e4fc30d6..78ac818063b 100644
--- a/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs
@@ -1,16 +1,18 @@
// 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.Metadata.Conventions;
///
-/// A convention that creates linking relationships for entity splitting.
+/// A convention that creates linking relationships for entity splitting and manages the mapping fragments.
///
///
/// See Model building conventions and
/// Entity type hierarchy mapping for more information and examples.
///
-public class EntitySplittingConvention : IModelFinalizingConvention
+public class EntitySplittingConvention : IModelFinalizingConvention, IEntityTypeAddedConvention
{
///
/// Creates a new instance of .
@@ -35,6 +37,45 @@ public EntitySplittingConvention(
///
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
+ ///
+ public virtual void ProcessEntityTypeAdded(
+ IConventionEntityTypeBuilder entityTypeBuilder,
+ IConventionContext context)
+ {
+ var entityType = entityTypeBuilder.Metadata;
+ if (!entityType.HasSharedClrType)
+ {
+ return;
+ }
+
+ List? fragmentsToReattach = null;
+ foreach (var fragment in entityType.GetMappingFragments())
+ {
+ if (fragment.EntityType == entityType)
+ {
+ continue;
+ }
+
+ fragmentsToReattach ??= new();
+
+ fragmentsToReattach.Add(fragment);
+ }
+
+ if (fragmentsToReattach == null)
+ {
+ return;
+ }
+
+ foreach (var fragment in fragmentsToReattach)
+ {
+ var removedFragment = entityType.RemoveMappingFragment(fragment.StoreObject);
+ if (removedFragment != null)
+ {
+ EntityTypeMappingFragment.Attach(entityType, removedFragment);
+ }
+ }
+ }
+
///
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index a14ff223ec2..33d2dfcb622 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -64,13 +64,15 @@ public override ConventionSet CreateConventionSet()
conventionSet.PropertyAddedConventions.Add(relationalColumnAttributeConvention);
conventionSet.PropertyAddedConventions.Add(relationalCommentAttributeConvention);
+ var tableNameFromDbSetConvention = new TableNameFromDbSetConvention(Dependencies, RelationalDependencies);
+ var entitySplittingConvention = new EntitySplittingConvention(Dependencies, RelationalDependencies);
var checkConstraintConvention = new CheckConstraintConvention(Dependencies, RelationalDependencies);
var triggerConvention = new TriggerConvention(Dependencies, RelationalDependencies);
- var tableNameFromDbSetConvention = new TableNameFromDbSetConvention(Dependencies, RelationalDependencies);
conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention(Dependencies, RelationalDependencies));
conventionSet.EntityTypeAddedConventions.Add(
new RelationalTableCommentAttributeConvention(Dependencies, RelationalDependencies));
conventionSet.EntityTypeAddedConventions.Add(tableNameFromDbSetConvention);
+ conventionSet.EntityTypeAddedConventions.Add(entitySplittingConvention);
conventionSet.EntityTypeAddedConventions.Add(checkConstraintConvention);
conventionSet.EntityTypeAddedConventions.Add(triggerConvention);
@@ -93,6 +95,8 @@ public override ConventionSet CreateConventionSet()
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, valueGenerationConvention);
+ conventionSet.PropertyAddedConventions.Add(new PropertyOverridesConvention(Dependencies, RelationalDependencies));
+
conventionSet.PropertyFieldChangedConventions.Add(relationalColumnAttributeConvention);
conventionSet.PropertyFieldChangedConventions.Add(relationalCommentAttributeConvention);
@@ -112,7 +116,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention);
conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention);
- conventionSet.ModelFinalizingConventions.Add(new EntitySplittingConvention(Dependencies, RelationalDependencies));
+ conventionSet.ModelFinalizingConventions.Add(entitySplittingConvention);
conventionSet.ModelFinalizingConventions.Add(new EntityTypeHierarchyMappingConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new SequenceUniquificationConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new SharedTableConvention(Dependencies, RelationalDependencies));
diff --git a/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs b/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs
new file mode 100644
index 00000000000..706897b4ce0
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs
@@ -0,0 +1,78 @@
+// 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.Metadata.Conventions;
+
+///
+/// A convention that ensures that the declaring property is current for the property overrides.
+///
+///
+/// See Model building conventions and
+/// Entity type hierarchy mapping for more information and examples.
+///
+public class PropertyOverridesConvention : IPropertyAddedConvention
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public PropertyOverridesConvention(
+ ProviderConventionSetBuilderDependencies dependencies,
+ RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Dependencies for this service.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
+
+ ///
+ public virtual void ProcessPropertyAdded(
+ IConventionPropertyBuilder propertyBuilder,
+ IConventionContext context)
+ {
+ var property = propertyBuilder.Metadata;
+ if (!property.DeclaringEntityType.HasSharedClrType)
+ {
+ return;
+ }
+
+ List? overridesToReattach = null;
+ foreach (var overrides in property.GetOverrides())
+ {
+ if (overrides.Property == property)
+ {
+ continue;
+ }
+
+ overridesToReattach ??= new();
+
+ overridesToReattach.Add(overrides);
+ }
+
+ if (overridesToReattach == null)
+ {
+ return;
+ }
+
+ foreach (var overrides in overridesToReattach)
+ {
+ var removedOverrides = property.RemoveOverrides(overrides.StoreObject);
+ if (removedOverrides != null)
+ {
+ RelationalPropertyOverrides.Attach(property, removedOverrides);
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs
index 01cd41333e7..fd2ed5f9371 100644
--- a/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs
@@ -49,14 +49,14 @@ public virtual void ProcessEntityTypeAdded(
}
List? triggersToReattach = null;
- foreach (var trigger in entityType.GetTriggers())
+ foreach (var trigger in entityType.GetDeclaredTriggers())
{
if (trigger.EntityType == entityType)
{
continue;
}
- triggersToReattach ??= new List();
+ triggersToReattach ??= new();
triggersToReattach.Add(trigger);
}
@@ -71,7 +71,7 @@ public virtual void ProcessEntityTypeAdded(
var removedTrigger = entityType.RemoveTrigger(trigger.ModelName);
if (removedTrigger != null)
{
- Trigger.MergeInto(entityType, removedTrigger);
+ Trigger.Attach(entityType, removedTrigger);
}
}
}
diff --git a/src/EFCore.Relational/Metadata/ICheckConstraint.cs b/src/EFCore.Relational/Metadata/ICheckConstraint.cs
index d353cfe9c88..3814c656be2 100644
--- a/src/EFCore.Relational/Metadata/ICheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/ICheckConstraint.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 System.Text;
-
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -17,41 +15,4 @@ public interface ICheckConstraint : IReadOnlyCheckConstraint, IAnnotatable
/// Gets the entity type on which this check constraint is defined.
///
new IEntityType EntityType { get; }
-
- ///
- ///
- /// Creates a human-readable representation of the given metadata.
- ///
- ///
- /// Warning: Do not rely on the format of the returned string.
- /// It is designed for debugging only and may change arbitrarily between releases.
- ///
- ///
- /// Options for generating the string.
- /// The number of indent spaces to use before each new line.
- /// A human-readable representation.
- string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
- {
- var builder = new StringBuilder();
- var indentString = new string(' ', indent);
-
- builder
- .Append(indentString)
- .Append("Check: ");
-
- builder.Append(ModelName)
- .Append(" \"")
- .Append(Sql)
- .Append('"');
-
- if ((options & MetadataDebugStringOptions.SingleLine) == 0)
- {
- if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
- {
- builder.Append(AnnotationsToDebugString(indent: indent + 2));
- }
- }
-
- return builder.ToString();
- }
}
diff --git a/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs
index fb7553795f4..ff3f453c233 100644
--- a/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs
+++ b/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs
@@ -16,6 +16,12 @@ public interface IConventionEntityTypeMappingFragment : IReadOnlyEntityTypeMappi
///
new IConventionEntityType EntityType { get; }
+ ///
+ /// Gets the builder that can be used to configure this fragment.
+ ///
+ /// If the fragment has been removed from the model.
+ new IConventionEntityTypeMappingFragmentBuilder Builder { get; }
+
///
/// Returns the configuration source for this fragment.
///
diff --git a/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs
index 4122aa05172..fff6a25c68a 100644
--- a/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs
@@ -16,6 +16,12 @@ public interface IConventionRelationalPropertyOverrides : IReadOnlyRelationalPro
///
new IConventionProperty Property { get; }
+ ///
+ /// Gets the builder that can be used to configure this function.
+ ///
+ /// If the function has been removed from the model.
+ new IConventionRelationalPropertyOverridesBuilder Builder { get; }
+
///
/// Returns the configuration source for these overrides.
///
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs b/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
index a9f2f811206..02036b2fe63 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -57,4 +59,47 @@ public interface IReadOnlyCheckConstraint : IReadOnlyAnnotatable
/// Gets the constraint sql used in a check constraint in the database.
///
string Sql { get; }
+
+ ///
+ ///
+ /// Creates a human-readable representation of the given metadata.
+ ///
+ ///
+ /// Warning: Do not rely on the format of the returned string.
+ /// It is designed for debugging only and may change arbitrarily between releases.
+ ///
+ ///
+ /// Options for generating the string.
+ /// The number of indent spaces to use before each new line.
+ /// A human-readable representation.
+ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
+ {
+ var builder = new StringBuilder();
+ var indentString = new string(' ', indent);
+
+ builder
+ .Append(indentString)
+ .Append("Check: ");
+
+ builder.Append(ModelName);
+ if (Name != ModelName)
+ {
+ builder.Append('*');
+ }
+
+ builder
+ .Append(" \"")
+ .Append(Sql)
+ .Append('"');
+
+ if ((options & MetadataDebugStringOptions.SingleLine) == 0)
+ {
+ if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
+ {
+ builder.Append(AnnotationsToDebugString(indent: indent + 2));
+ }
+ }
+
+ return builder.ToString();
+ }
}
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyDbFunction.cs b/src/EFCore.Relational/Metadata/IReadOnlyDbFunction.cs
index 100532fc9c6..f7c13715218 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyDbFunction.cs
@@ -119,6 +119,11 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
builder.Append(Name);
+ if (Name != ModelName)
+ {
+ builder.Append('*');
+ }
+
if ((options & MetadataDebugStringOptions.SingleLine) == 0)
{
var parameters = Parameters.ToList();
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs
index ae79f90cfac..591f6e55291 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -26,4 +28,42 @@ public interface IReadOnlyEntityTypeMappingFragment : IReadOnlyAnnotatable
///
/// A value indicating whether the associated table is ignored by Migrations.
bool? IsTableExcludedFromMigrations { get; }
+
+ ///
+ ///
+ /// Creates a human-readable representation of the given metadata.
+ ///
+ ///
+ /// Warning: Do not rely on the format of the returned string.
+ /// It is designed for debugging only and may change arbitrarily between releases.
+ ///
+ ///
+ /// Options for generating the string.
+ /// The number of indent spaces to use before each new line.
+ /// A human-readable representation.
+ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
+ {
+ var builder = new StringBuilder();
+ var indentString = new string(' ', indent);
+
+ builder
+ .Append(indentString)
+ .Append("Fragment: ")
+ .Append(StoreObject.DisplayName());
+
+ if (IsTableExcludedFromMigrations == true)
+ {
+ builder.Append("ExcludedFromMigrations");
+ }
+
+ if ((options & MetadataDebugStringOptions.SingleLine) == 0)
+ {
+ if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
+ {
+ builder.Append(AnnotationsToDebugString(indent: indent + 2));
+ }
+ }
+
+ return builder.ToString();
+ }
}
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
index 76f806bcf44..5fe8987bf95 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -30,4 +32,43 @@ public interface IReadOnlyRelationalPropertyOverrides : IReadOnlyAnnotatable
/// Gets a value indicating whether the column name is overriden.
///
bool ColumnNameOverridden { get; }
+
+ ///
+ ///
+ /// Creates a human-readable representation of the given metadata.
+ ///
+ ///
+ /// Warning: Do not rely on the format of the returned string.
+ /// It is designed for debugging only and may change arbitrarily between releases.
+ ///
+ ///
+ /// Options for generating the string.
+ /// The number of indent spaces to use before each new line.
+ /// A human-readable representation.
+ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
+ {
+ var builder = new StringBuilder();
+ var indentString = new string(' ', indent);
+
+ builder
+ .Append(indentString)
+ .Append("Override: ")
+ .Append(StoreObject.DisplayName());
+
+ if (ColumnNameOverridden)
+ {
+ builder.Append(" ColumnName: ")
+ .Append(ColumnName);
+ }
+
+ if ((options & MetadataDebugStringOptions.SingleLine) == 0)
+ {
+ if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
+ {
+ builder.Append(AnnotationsToDebugString(indent: indent + 2));
+ }
+ }
+
+ return builder.ToString();
+ }
}
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs b/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs
index 9b27689154f..a03fdff0e6e 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -67,4 +69,53 @@ public interface IReadOnlyTrigger : IReadOnlyAnnotatable
/// Gets the entity type on which this trigger is defined.
///
IReadOnlyEntityType EntityType { get; }
+
+ ///
+ ///
+ /// Creates a human-readable representation of the given metadata.
+ ///
+ ///
+ /// Warning: Do not rely on the format of the returned string.
+ /// It is designed for debugging only and may change arbitrarily between releases.
+ ///
+ ///
+ /// Options for generating the string.
+ /// The number of indent spaces to use before each new line.
+ /// A human-readable representation.
+ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
+ {
+ var builder = new StringBuilder();
+ var indentString = new string(' ', indent);
+
+ builder
+ .Append(indentString)
+ .Append("Trigger: ")
+ .Append(ModelName);
+
+ if (Name != ModelName)
+ {
+ builder.Append('*');
+ }
+
+ builder.Append(" ");
+
+ if (TableSchema != null)
+ {
+ builder
+ .Append('.')
+ .Append(TableSchema);
+ }
+
+ builder.Append(TableName);
+
+ if ((options & MetadataDebugStringOptions.SingleLine) == 0)
+ {
+ if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
+ {
+ builder.Append(AnnotationsToDebugString(indent: indent + 2));
+ }
+ }
+
+ return builder.ToString();
+ }
}
diff --git a/src/EFCore.Relational/Metadata/ITableMapping.cs b/src/EFCore.Relational/Metadata/ITableMapping.cs
index bc9c6fa382c..71bceeb10a0 100644
--- a/src/EFCore.Relational/Metadata/ITableMapping.cs
+++ b/src/EFCore.Relational/Metadata/ITableMapping.cs
@@ -48,13 +48,36 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
builder.Append("TableMapping: ");
}
- builder.Append(EntityType.Name).Append(" - ");
+ builder
+ .Append(EntityType.Name)
+ .Append(" - ")
+ .Append(Table.Name);
- builder.Append(Table.Name);
+ builder.Append(" ");
+ if (!IncludesDerivedTypes)
+ {
+ builder.Append("!");
+ }
+ builder.Append("IncludesDerivedTypes");
+
+ if (IsSharedTablePrincipal != null)
+ {
+ builder.Append(" ");
+ if (!IsSharedTablePrincipal.Value)
+ {
+ builder.Append("!");
+ }
+ builder.Append("IsSharedTablePrincipal");
+ }
- if (IncludesDerivedTypes)
+ if (IsSplitEntityTypePrincipal != null)
{
- builder.Append(" IncludesDerivedTypes");
+ builder.Append(" ");
+ if (!IsSplitEntityTypePrincipal.Value)
+ {
+ builder.Append("!");
+ }
+ builder.Append("IsSplitEntityTypePrincipal");
}
if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
diff --git a/src/EFCore.Relational/Metadata/ITrigger.cs b/src/EFCore.Relational/Metadata/ITrigger.cs
index 75434b0c9de..ab5eee6ec59 100644
--- a/src/EFCore.Relational/Metadata/ITrigger.cs
+++ b/src/EFCore.Relational/Metadata/ITrigger.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 System.Text;
-
namespace Microsoft.EntityFrameworkCore.Metadata;
///
@@ -24,35 +22,7 @@ public interface ITrigger : IReadOnlyTrigger, IAnnotatable
new IEntityType EntityType { get; }
///
- ///
- /// Creates a human-readable representation of the given metadata.
- ///
- ///
- /// Warning: Do not rely on the format of the returned string.
- /// It is designed for debugging only and may change arbitrarily between releases.
- ///
+ /// Gets the database name of the trigger.
///
- /// Options for generating the string.
- /// The number of indent spaces to use before each new line.
- /// A human-readable representation.
- string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
- {
- var builder = new StringBuilder();
- var indentString = new string(' ', indent);
-
- builder
- .Append(indentString)
- .Append("Trigger: ")
- .Append(ModelName);
-
- if ((options & MetadataDebugStringOptions.SingleLine) == 0)
- {
- if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
- {
- builder.Append(AnnotationsToDebugString(indent: indent + 2));
- }
- }
-
- return builder.ToString();
- }
+ new string Name { get; }
}
diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
index e21dc834c69..91cdf62e3f9 100644
--- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
@@ -169,7 +169,7 @@ public static void Attach(IConventionEntityType entityType, IConventionCheckCons
detachedCheckConstraint.Sql,
detachedCheckConstraint.GetConfigurationSource());
- Attach(detachedCheckConstraint, newCheckConstraint);
+ MergeInto(detachedCheckConstraint, newCheckConstraint);
}
///
@@ -178,7 +178,7 @@ public static void Attach(IConventionEntityType entityType, IConventionCheckCons
/// 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 static void Attach(IConventionCheckConstraint detachedCheckConstraint, IConventionCheckConstraint existingCheckConstraint)
+ public static void MergeInto(IConventionCheckConstraint detachedCheckConstraint, IConventionCheckConstraint existingCheckConstraint)
{
var nameConfigurationSource = detachedCheckConstraint.GetNameConfigurationSource();
if (nameConfigurationSource != null)
@@ -416,6 +416,18 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS
public override string ToString()
=> ((ICheckConstraint)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((ICheckConstraint)this).ToDebugString(),
+ () => ((ICheckConstraint)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
/// 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.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
index 6ddcf2fea6b..2c76de5e509 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
@@ -651,6 +651,18 @@ public virtual IReadOnlyList Parameters
public override string ToString()
=> ((IDbFunction)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IDbFunction)this).ToDebugString(),
+ () => ((IDbFunction)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IConventionDbFunctionBuilder IConventionDbFunction.Builder
{
diff --git a/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs
index fbe0f4e2141..8bd23181316 100644
--- a/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs
+++ b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs
@@ -16,10 +16,11 @@ public class EntityTypeMappingFragment :
IConventionEntityTypeMappingFragment
{
private bool? _isTableExcludedFromMigrations;
+ private InternalEntityTypeMappingFragmentBuilder? _builder;
private ConfigurationSource _configurationSource;
private ConfigurationSource? _isTableExcludedFromMigrationsConfigurationSource;
-
+
///
/// 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
@@ -34,8 +35,39 @@ public EntityTypeMappingFragment(
EntityType = entityType;
StoreObject = storeObject;
_configurationSource = configurationSource;
+ _builder = new InternalEntityTypeMappingFragmentBuilder(this, ((IConventionModel)entityType.Model).Builder);
+ }
+
+ ///
+ /// 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 virtual InternalEntityTypeMappingFragmentBuilder Builder
+ {
+ [DebuggerStepThrough]
+ get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel);
}
+ ///
+ /// 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 virtual bool IsInModel
+ => _builder is not null;
+
+ ///
+ /// 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 virtual void SetRemovedFromModel()
+ => _builder = null;
+
///
/// 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
@@ -79,6 +111,44 @@ public virtual ConfigurationSource GetConfigurationSource()
public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource)
=> _configurationSource = configurationSource.Max(_configurationSource);
+ ///
+ /// 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 static void Attach(IConventionEntityType entityType, IConventionEntityTypeMappingFragment detachedFragment)
+ {
+ var newFragment = GetOrCreate(
+ (IMutableEntityType)entityType,
+ detachedFragment.StoreObject,
+ detachedFragment.GetConfigurationSource());
+
+ MergeInto(detachedFragment, newFragment);
+ }
+
+ ///
+ /// 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 static EntityTypeMappingFragment MergeInto(
+ IConventionEntityTypeMappingFragment detachedFragment, IConventionEntityTypeMappingFragment existingFragment)
+ {
+ var isTableExcludedFromMigrationsConfigurationSource = detachedFragment.GetIsTableExcludedFromMigrationsConfigurationSource();
+ if (isTableExcludedFromMigrationsConfigurationSource != null)
+ {
+ existingFragment = ((InternalEntityTypeMappingFragmentBuilder)existingFragment.Builder).ExcludeTableFromMigrations(
+ detachedFragment.IsTableExcludedFromMigrations, isTableExcludedFromMigrationsConfigurationSource.Value)
+ !.Metadata;
+ }
+
+ return ((InternalEntityTypeMappingFragmentBuilder)existingFragment.Builder)
+ .MergeAnnotationsFrom((EntityTypeMappingFragment)detachedFragment)
+ .Metadata;
+ }
+
///
public virtual bool? IsTableExcludedFromMigrations
{
@@ -98,7 +168,7 @@ public virtual bool? IsTableExcludedFromMigrations
{
return null;
}
-
+
_isTableExcludedFromMigrations = excluded;
_isTableExcludedFromMigrationsConfigurationSource =
excluded == null
@@ -160,7 +230,7 @@ public static EntityTypeMappingFragment GetOrCreate(
var fragment = fragments.Find(storeObject);
if (fragment == null)
{
- fragment = new (entityType, storeObject, configurationSource);
+ fragment = new(entityType, storeObject, configurationSource);
fragments.Add(storeObject, fragment);
}
else
@@ -170,7 +240,7 @@ public static EntityTypeMappingFragment GetOrCreate(
return fragment;
}
-
+
///
/// 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
@@ -179,8 +249,7 @@ public static EntityTypeMappingFragment GetOrCreate(
///
public static EntityTypeMappingFragment? Remove(
IMutableEntityType entityType,
- in StoreObjectIdentifier storeObject,
- ConfigurationSource configurationSource)
+ in StoreObjectIdentifier storeObject)
{
var fragments = (StoreObjectDictionary?)
entityType[RelationalAnnotationNames.MappingFragments];
@@ -195,23 +264,40 @@ public static EntityTypeMappingFragment GetOrCreate(
return null;
}
- if (configurationSource.Overrides(fragment.GetConfigurationSource()))
- {
- fragments.Remove(storeObject);
-
- return fragment;
- }
+ fragments.Remove(storeObject);
+ fragment.SetRemovedFromModel();
- return null;
+ return fragment;
}
+ ///
+ /// 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 override string ToString()
+ => ((IEntityTypeMappingFragment)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IEntityTypeMappingFragment)this).ToDebugString(),
+ () => ((IEntityTypeMappingFragment)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IEntityType IEntityTypeMappingFragment.EntityType
{
[DebuggerStepThrough]
get => (IEntityType)EntityType;
}
-
+
///
IMutableEntityType IMutableEntityTypeMappingFragment.EntityType
{
@@ -229,5 +315,15 @@ IConventionEntityType IConventionEntityTypeMappingFragment.EntityType
bool? IConventionEntityTypeMappingFragment.SetIsTableExcludedFromMigrations(bool? excluded, bool fromDataAnnotation)
=> SetIsTableExcludedFromMigrations(excluded, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
- bool? IReadOnlyEntityTypeMappingFragment.IsTableExcludedFromMigrations => IsTableExcludedFromMigrations;
+ bool? IReadOnlyEntityTypeMappingFragment.IsTableExcludedFromMigrations
+ {
+ [DebuggerStepThrough]
+ get => IsTableExcludedFromMigrations;
+ }
+
+ IConventionEntityTypeMappingFragmentBuilder IConventionEntityTypeMappingFragment.Builder
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
}
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs
index 727c1147f29..5ef202fa24f 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs
@@ -123,7 +123,7 @@ public virtual bool CanSetName(string? name, ConfigurationSource configurationSo
{
foreach (var detachedCheckConstraint in detachedCheckConstraints)
{
- CheckConstraint.Attach(detachedCheckConstraint, constraint);
+ CheckConstraint.MergeInto(detachedCheckConstraint, constraint);
}
}
}
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalEntityTypeMappingFragmentBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalEntityTypeMappingFragmentBuilder.cs
new file mode 100644
index 00000000000..ce48ddd90ff
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/InternalEntityTypeMappingFragmentBuilder.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+///
+/// 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 class InternalEntityTypeMappingFragmentBuilder :
+ AnnotatableBuilder,
+ IConventionEntityTypeMappingFragmentBuilder
+{
+ ///
+ /// 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 InternalEntityTypeMappingFragmentBuilder(
+ EntityTypeMappingFragment fragment, IConventionModelBuilder modelBuilder)
+ : base(fragment, modelBuilder)
+ {
+ }
+
+ ///
+ /// 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 virtual InternalEntityTypeMappingFragmentBuilder? ExcludeTableFromMigrations(
+ bool? excludedFromMigrations,
+ ConfigurationSource configurationSource)
+ {
+ if (!CanExcludeTableFromMigrations(excludedFromMigrations, configurationSource))
+ {
+ return null;
+ }
+
+ Metadata.SetIsTableExcludedFromMigrations(excludedFromMigrations, configurationSource);
+ return this;
+ }
+
+ ///
+ /// 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 virtual bool CanExcludeTableFromMigrations(
+ bool? excludedFromMigrations,
+ ConfigurationSource configurationSource)
+ => configurationSource.Overrides(Metadata.GetIsTableExcludedFromMigrationsConfigurationSource())
+ || Metadata.IsTableExcludedFromMigrations == excludedFromMigrations;
+
+ ///
+ IConventionEntityTypeMappingFragment IConventionEntityTypeMappingFragmentBuilder.Metadata
+ {
+ [DebuggerStepThrough]
+ get => Metadata;
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalRelationalPropertyOverridesBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalRelationalPropertyOverridesBuilder.cs
new file mode 100644
index 00000000000..3b5e1caaf3f
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/InternalRelationalPropertyOverridesBuilder.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+///
+/// 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 class InternalRelationalPropertyOverridesBuilder :
+ AnnotatableBuilder,
+ IConventionRelationalPropertyOverridesBuilder
+{
+ ///
+ /// 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 InternalRelationalPropertyOverridesBuilder(
+ RelationalPropertyOverrides overrides, IConventionModelBuilder modelBuilder)
+ : base(overrides, modelBuilder)
+ {
+ }
+
+ ///
+ /// 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 virtual InternalRelationalPropertyOverridesBuilder? HasColumnName(
+ string? name,
+ ConfigurationSource configurationSource)
+ {
+ if (!CanSetColumnName(name, configurationSource))
+ {
+ return null;
+ }
+
+ Metadata.SetColumnName(name, configurationSource);
+
+ return this;
+ }
+
+ ///
+ /// 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 virtual bool CanSetColumnName(
+ string? name,
+ ConfigurationSource configurationSource)
+ => configurationSource.Overrides(Metadata.GetColumnNameConfigurationSource())
+ || Metadata.ColumnName == name;
+
+ ///
+ IConventionRelationalPropertyOverrides IConventionRelationalPropertyOverridesBuilder.Metadata
+ {
+ [DebuggerStepThrough]
+ get => Metadata;
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs
index 2a1850f0602..7c38a619788 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs
@@ -66,7 +66,8 @@ public virtual bool CanSetName(string? name, ConfigurationSource configurationSo
var trigger = entityType.FindTrigger(name);
if (trigger != null)
{
- if (trigger.TableName == tableName && trigger.TableSchema == tableSchema)
+ if ((tableName == null && tableSchema == null)
+ || (trigger.TableName == tableName && trigger.TableSchema == tableSchema))
{
((Trigger)trigger).UpdateConfigurationSource(configurationSource);
return trigger;
@@ -83,14 +84,14 @@ public virtual bool CanSetName(string? name, ConfigurationSource configurationSo
{
foreach (var derivedType in entityType.GetDerivedTypes())
{
- var derivedTrigger =
- (IConventionTrigger?)Trigger.FindDeclaredTrigger(derivedType, name);
+ var derivedTrigger = (IConventionTrigger?)Trigger.FindDeclaredTrigger(derivedType, name);
if (derivedTrigger == null)
{
continue;
}
- if ((derivedTrigger.TableName != tableName || derivedTrigger.TableSchema != tableSchema)
+ if ((tableName != null || tableSchema != null)
+ && (derivedTrigger.TableName != tableName || derivedTrigger.TableSchema != tableSchema)
&& !configurationSource.Overrides(derivedTrigger.GetConfigurationSource()))
{
return null;
@@ -139,6 +140,12 @@ public static bool CanHaveTrigger(
string? tableSchema,
ConfigurationSource configurationSource)
{
+ if (tableName == null
+ && tableSchema == null)
+ {
+ return true;
+ }
+
if (entityType.FindTrigger(name) is IConventionTrigger trigger)
{
return (trigger.TableName == tableName
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
index 6bce5b2c861..d01f84c0462 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
@@ -970,7 +970,8 @@ private static void PopulateTableConfiguration(Table table, bool designTime)
}
}
- foreach (var trigger in entityType.GetTriggers())
+ // Triggers cannot be inherited
+ foreach (var trigger in entityType.GetDeclaredTriggers())
{
var name = trigger.GetName(storeObject);
if (name == null)
@@ -979,7 +980,7 @@ private static void PopulateTableConfiguration(Table table, bool designTime)
}
Check.DebugAssert(trigger.TableName == table.Name, "Mismatch in trigger table name");
- Check.DebugAssert(trigger.TableSchema is null || trigger.TableSchema == table.Schema, "Mismatch in trigger table schema");
+ Check.DebugAssert(trigger.TableSchema == table.Schema, "Mismatch in trigger table schema");
if (!table.Triggers.ContainsKey(name))
{
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
index 8058c2ab173..aeec0461e23 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
@@ -16,6 +16,7 @@ public class RelationalPropertyOverrides :
IRelationalPropertyOverrides
{
private string? _columnName;
+ private InternalRelationalPropertyOverridesBuilder? _builder;
private ConfigurationSource _configurationSource;
private ConfigurationSource? _columnNameConfigurationSource;
@@ -34,6 +35,8 @@ public RelationalPropertyOverrides(
Property = property;
StoreObject = storeObject;
_configurationSource = configurationSource;
+ _builder = new InternalRelationalPropertyOverridesBuilder(
+ this, ((IConventionModel)property.DeclaringEntityType.Model).Builder);
}
///
@@ -60,6 +63,74 @@ public RelationalPropertyOverrides(
///
public override bool IsReadOnly
=> ((Annotatable)Property).IsReadOnly;
+
+ ///
+ /// 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 virtual InternalRelationalPropertyOverridesBuilder Builder
+ {
+ [DebuggerStepThrough]
+ get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel);
+ }
+
+ ///
+ /// 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 virtual bool IsInModel
+ => _builder is not null;
+
+ ///
+ /// 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 virtual void SetRemovedFromModel()
+ => _builder = null;
+
+ ///
+ /// 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 static void Attach(IConventionProperty property, IConventionRelationalPropertyOverrides detachedOverrides)
+ {
+ var newOverrides = GetOrCreate(
+ (IMutableProperty)property,
+ detachedOverrides.StoreObject,
+ detachedOverrides.GetConfigurationSource());
+
+ MergeInto(detachedOverrides, newOverrides);
+ }
+
+ ///
+ /// 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 static RelationalPropertyOverrides MergeInto(
+ IConventionRelationalPropertyOverrides detachedOverrides, IConventionRelationalPropertyOverrides existingOverrides)
+ {
+ var columnNameConfigurationSource = detachedOverrides.GetColumnNameConfigurationSource();
+ if (columnNameConfigurationSource != null)
+ {
+ existingOverrides = ((InternalRelationalPropertyOverridesBuilder)existingOverrides.Builder)
+ .HasColumnName(detachedOverrides.ColumnName, columnNameConfigurationSource.Value)
+ !.Metadata;
+ }
+
+ return ((InternalRelationalPropertyOverridesBuilder)existingOverrides.Builder)
+ .MergeAnnotationsFrom((RelationalPropertyOverrides)detachedOverrides)
+ .Metadata;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -206,8 +277,7 @@ public static RelationalPropertyOverrides GetOrCreate(
///
public static RelationalPropertyOverrides? Remove(
IMutableProperty property,
- in StoreObjectIdentifier storeObject,
- ConfigurationSource configurationSource)
+ in StoreObjectIdentifier storeObject)
{
var tableOverrides = (StoreObjectDictionary?)
property[RelationalAnnotationNames.RelationalOverrides];
@@ -222,16 +292,33 @@ public static RelationalPropertyOverrides GetOrCreate(
return null;
}
- if (configurationSource.Overrides(overrides.GetConfigurationSource()))
- {
- tableOverrides.Remove(storeObject);
+ tableOverrides.Remove(storeObject);
+ overrides.SetRemovedFromModel();
- return overrides;
- }
-
- return null;
+ return overrides;
}
+ ///
+ /// 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 override string ToString()
+ => ((IRelationalPropertyOverrides)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IRelationalPropertyOverrides)this).ToDebugString(),
+ () => ((IRelationalPropertyOverrides)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IProperty IRelationalPropertyOverrides.Property
{
@@ -253,6 +340,13 @@ IConventionProperty IConventionRelationalPropertyOverrides.Property
get => (IConventionProperty)Property;
}
+ ///
+ IConventionRelationalPropertyOverridesBuilder IConventionRelationalPropertyOverrides.Builder
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
+
string? IConventionRelationalPropertyOverrides.SetColumnName(string? name, bool fromDataAnnotation)
=> SetColumnName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
diff --git a/src/EFCore.Relational/Metadata/Internal/Trigger.cs b/src/EFCore.Relational/Metadata/Internal/Trigger.cs
index 58600cb3db9..40807b0871a 100644
--- a/src/EFCore.Relational/Metadata/Internal/Trigger.cs
+++ b/src/EFCore.Relational/Metadata/Internal/Trigger.cs
@@ -159,7 +159,7 @@ public static IEnumerable GetTriggers(IReadOnlyEntityType enti
/// 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 static void MergeInto(IConventionEntityType entityType, IConventionTrigger detachedTrigger)
+ public static void Attach(IConventionEntityType entityType, IConventionTrigger detachedTrigger)
{
var newTrigger = new Trigger(
(IMutableEntityType)entityType,
@@ -257,22 +257,11 @@ public virtual string? Name
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual string? GetName(in StoreObjectIdentifier storeObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- foreach (var containingType in EntityType.GetDerivedTypesInclusive())
- {
- if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject)
- {
- return _name ?? ((IReadOnlyTrigger)this).GetDefaultName(storeObject);
- }
- }
-
- return null;
- }
+ => storeObject.StoreObjectType == StoreObjectType.Table
+ && TableName == storeObject.Name
+ && TableSchema == storeObject.Schema
+ ? _name ?? ((IReadOnlyTrigger)this).GetDefaultName(storeObject)
+ : null;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -346,7 +335,7 @@ public virtual string TableName
///
public virtual string? TableSchema
{
- get => _tableSchema;
+ get => _tableSchema ?? EntityType.GetSchema();
set => SetTableSchema(value, ConfigurationSource.Explicit);
}
@@ -406,6 +395,18 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS
public override string ToString()
=> ((ITrigger)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((ITrigger)this).ToDebugString(),
+ () => ((ITrigger)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
/// 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
@@ -442,6 +443,18 @@ IEntityType ITrigger.EntityType
get => (IEntityType)EntityType;
}
+ ///
+ /// 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.
+ ///
+ string ITrigger.Name
+ {
+ [DebuggerStepThrough]
+ get => Name!;
+ }
+
///
/// 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.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs
index ccd5d231505..15e0ae4c661 100644
--- a/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs
+++ b/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs
@@ -43,6 +43,22 @@ public RuntimeEntityTypeMappingFragment(
///
public virtual bool? IsTableExcludedFromMigrations => (bool?)this[RelationalAnnotationNames.IsTableExcludedFromMigrations];
+ ///
+ public override string ToString()
+ => ((IEntityTypeMappingFragment)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IEntityTypeMappingFragment)this).ToDebugString(),
+ () => ((IEntityTypeMappingFragment)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IEntityType IEntityTypeMappingFragment.EntityType
{
diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
index 224554159eb..43f8fb11100 100644
--- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
@@ -40,6 +40,22 @@ public RuntimeRelationalPropertyOverrides(
///
public virtual StoreObjectIdentifier StoreObject { get; }
+ ///
+ public override string ToString()
+ => ((IRelationalPropertyOverrides)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IRelationalPropertyOverrides)this).ToDebugString(),
+ () => ((IRelationalPropertyOverrides)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IProperty IRelationalPropertyOverrides.Property
{
diff --git a/src/EFCore.Relational/Metadata/RuntimeTrigger.cs b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs
index ed95ba9e6e3..e5ad7097b4b 100644
--- a/src/EFCore.Relational/Metadata/RuntimeTrigger.cs
+++ b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs
@@ -20,7 +20,7 @@ public class RuntimeTrigger : AnnotatableBase, ITrigger
public RuntimeTrigger(
RuntimeEntityType entityType,
string modelName,
- string? name,
+ string name,
string tableName,
string? tableSchema)
{
@@ -37,11 +37,15 @@ public RuntimeTrigger(
///
/// Gets the database name of the trigger.
///
- public virtual string? Name { get; }
+ public virtual string Name { get; }
///
public virtual string? GetName(in StoreObjectIdentifier storeObject)
- => Name;
+ => storeObject.StoreObjectType == StoreObjectType.Table
+ && TableName == storeObject.Name
+ && TableSchema == storeObject.Schema
+ ? Name
+ : null;
///
public virtual string TableName { get; }
@@ -52,6 +56,22 @@ public RuntimeTrigger(
///
public virtual IEntityType EntityType { get; }
+ ///
+ public override string ToString()
+ => ((ITrigger)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((ITrigger)this).ToDebugString(),
+ () => ((ITrigger)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
///
IReadOnlyEntityType IReadOnlyTrigger.EntityType
=> EntityType;
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..ee6a0003c04 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)
+ => propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder
+ .CanSetAnnotation(
+ SqlServerAnnotationNames.IdentitySeed,
+ seed,
+ fromDataAnnotation)
+ ?? 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)
+ => propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder
+ .CanSetAnnotation(
+ SqlServerAnnotationNames.IdentityIncrement,
+ increment,
+ fromDataAnnotation)
+ ?? 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))
+ && (propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder
+ .CanSetAnnotation(
+ SqlServerAnnotationNames.ValueGenerationStrategy,
+ valueGenerationStrategy,
+ fromDataAnnotation)
+ ?? 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.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs
index c5c3624ee25..ed30a2eb3dd 100644
--- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs
+++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs
@@ -846,5 +846,5 @@ private static bool HasAnyTriggers(IReadOnlyModificationCommand command)
// Data seeding doesn't provide any entries, so we we don't know if the table has triggers; assume it does to generate SQL
// that works everywhere.
=> command.Entries.Count == 0
- || command.Entries[0].EntityType.Model.GetRelationalModel().FindTable(command.TableName, command.Schema)!.Triggers.Any();
+ || command.Table!.Triggers.Any();
}
diff --git a/src/EFCore/Infrastructure/AnnotatableBuilder.cs b/src/EFCore/Infrastructure/AnnotatableBuilder.cs
index 5b857ca90f7..0889d2eadf3 100644
--- a/src/EFCore/Infrastructure/AnnotatableBuilder.cs
+++ b/src/EFCore/Infrastructure/AnnotatableBuilder.cs
@@ -71,7 +71,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;
}
@@ -170,7 +173,7 @@ public virtual bool CanRemoveAnnotation(string name, ConfigurationSource configu
/// Copies all the explicitly configured annotations from the given object overwriting any existing ones.
///
/// The object to copy annotations from.
- public virtual void MergeAnnotationsFrom(TMetadata annotatable)
+ public virtual AnnotatableBuilder MergeAnnotationsFrom(TMetadata annotatable)
=> MergeAnnotationsFrom(annotatable, ConfigurationSource.Explicit);
///
@@ -178,20 +181,24 @@ public virtual void MergeAnnotationsFrom(TMetadata annotatable)
///
/// The object to copy annotations from.
/// The minimum configuration source for an annotation to be copied.
- public virtual void MergeAnnotationsFrom(TMetadata annotatable, ConfigurationSource minimalConfigurationSource)
+ public virtual AnnotatableBuilder MergeAnnotationsFrom(
+ TMetadata annotatable, ConfigurationSource minimalConfigurationSource)
{
+ var builder = this;
foreach (var annotation in annotatable.GetAnnotations())
{
var configurationSource = annotation.GetConfigurationSource();
if (configurationSource.Overrides(minimalConfigurationSource))
{
- HasAnnotation(
+ builder = builder.HasAnnotation(
annotation.Name,
annotation.Value,
configurationSource,
- canOverrideSameSource: false);
+ canOverrideSameSource: false) ?? builder;
}
}
+
+ return builder;
}
///
diff --git a/src/EFCore/Infrastructure/ConventionAnnotatable.cs b/src/EFCore/Infrastructure/ConventionAnnotatable.cs
index f4fd65eeeb6..188128ad3c7 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,
diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs
index 85155be2193..795e1a2adf2 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,21 @@ private bool TryFindMatchingProperties(
foreignKeyProperties[i] = property;
}
+ if (matchFound
+ && foreignKeyProperties.Length != 1
+ && foreignKeyProperties.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 +514,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..3645ca93e4f 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,12 +728,23 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables()
b.ToTable(""Order"", null, t =>
{
+ t.Property(""Id"")
+ .HasAnnotation(""fii"", ""arr"")
+ .HasAnnotation(""SqlServer:IdentityIncrement"", 3)
+ .HasAnnotation(""SqlServer:IdentitySeed"", 2L)
+ .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);
+
t.Property(""Shadow"");
});
b.SplitToTable(""SplitOrder"", null, t =>
{
+ t.HasTrigger(""splitTrigger"")
+ .HasAnnotation(""oof"", ""rab"");
+
t.Property(""Shadow"");
+
+ t.HasAnnotation(""foo"", ""bar"");
});
});
@@ -852,6 +866,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 +909,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("rab", trigger["oof"]);
var billingFragment = billingEntityType.GetMappingFragments().Single();
var billingTable = relationalModel.FindTable(billingFragment.StoreObject.Name, billingFragment.StoreObject.Schema);
@@ -1133,7 +1164,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 +1175,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]
@@ -1308,6 +1349,7 @@ public virtual void Triggers_and_ExcludeFromMigrations_are_stored_in_snapshot()
t.ExcludeFromMigrations();
t.HasTrigger(""SomeTrigger1"");
+
t.HasTrigger(""SomeTrigger2"");
});
});"),
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs
index 9f88365b5a0..0265be6861f 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs
@@ -1252,10 +1252,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
entity.Property(e => e.Id).UseIdentityColumn();
- entity.ToTable(tb => {
- tb.HasTrigger(""Trigger1"");
- tb.HasTrigger(""Trigger2"");
- });
+ entity.ToTable(tb =>
+ {
+ tb.HasTrigger(""Trigger1"");
+
+ tb.HasTrigger(""Trigger2"");
+ });
});
OnModelCreatingPartial(modelBuilder);
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));
});
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
index 30d27f5130d..82a36d3e519 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
@@ -568,6 +568,7 @@ public void Base_check_constraint_overrides_derived_one()
var derivedBuilder = modelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention);
IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata;
+ derivedBuilder.HasBaseType((EntityType)null, ConfigurationSource.DataAnnotation);
Assert.NotNull(
derivedBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
@@ -581,17 +582,13 @@ public void Base_check_constraint_overrides_derived_one()
Assert.False(derivedBuilder.CanHaveCheckConstraint("Splew", "s > p"));
Assert.True(derivedBuilder.CanHaveCheckConstraint("Splot", "s > p"));
+ Assert.Null(derivedBuilder.HasCheckConstraint("Splew", "s > p"));
+ Assert.Equal("s < p", derivedEntityType.GetCheckConstraints().Single().Sql);
+
var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.DataAnnotation);
IReadOnlyEntityType baseEntityType = baseBuilder.Metadata;
-
- Assert.True(baseBuilder.CanHaveCheckConstraint("Splew", "s < p"));
- Assert.True(baseBuilder.CanHaveCheckConstraint("Splew", "s > p", fromDataAnnotation: true));
- Assert.False(baseBuilder.CanHaveCheckConstraint("Splew", "s > p"));
- Assert.True(baseBuilder.CanHaveCheckConstraint("Splot", "s > p"));
-
- Assert.Null(baseBuilder.HasCheckConstraint("Splew", "s > p"));
+ Assert.Null(derivedEntityType.BaseType);
Assert.Empty(baseEntityType.GetCheckConstraints());
- Assert.Equal("s < p", derivedEntityType.GetCheckConstraints().Single().Sql);
Assert.NotNull(
baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
@@ -600,7 +597,7 @@ public void Base_check_constraint_overrides_derived_one()
Assert.Equal("s < p", baseEntityType.GetCheckConstraints().Single().Sql);
Assert.Equal("CK_Splot", baseEntityType.GetCheckConstraints().Single().Name);
- derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.Convention);
+ Assert.NotNull(derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.DataAnnotation));
Assert.Null(
baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
@@ -776,31 +773,36 @@ public void Base_trigger_overrides_derived_one_after_base_is_set()
IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata;
Assert.NotNull(
- derivedBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true)
+ derivedBuilder.HasTrigger("Splew", nameof(Splow), null, fromDataAnnotation: true)
.HasName("Splow_Trigger", fromDataAnnotation: true));
Assert.Equal("Splew", derivedEntityType.GetTriggers().Single().ModelName);
- Assert.Equal("Table1", derivedEntityType.GetTriggers().Single().TableName);
+ Assert.Equal(nameof(Splow), derivedEntityType.GetTriggers().Single().TableName);
Assert.Equal("Splow_Trigger", derivedEntityType.GetTriggers().Single().Name);
+ Assert.Equal("Splow_Trigger", derivedEntityType.GetTriggers().Single()
+ .GetName(StoreObjectIdentifier.Create(derivedEntityType, StoreObjectType.Table).Value));
var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
IReadOnlyEntityType baseEntityType = baseBuilder.Metadata;
Assert.Null(derivedEntityType.BaseType);
Assert.NotNull(
- baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true)
+ baseBuilder.HasTrigger("Splew", nameof(Splot), null, fromDataAnnotation: true)
.HasName("Splot_Trigger", fromDataAnnotation: true));
Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName);
- Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName);
+ Assert.Equal(nameof(Splot), baseEntityType.GetTriggers().Single().TableName);
Assert.Equal("Splot_Trigger", baseEntityType.GetTriggers().Single().Name);
Assert.NotNull(derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.DataAnnotation));
+ Assert.Null(baseBuilder.HasTrigger("Splew", "Table1", "dbo"));
Assert.Null(
- baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true)
+ baseBuilder.HasTrigger("Splew", nameof(Splot), null, fromDataAnnotation: true)
.HasName("Splew_Trigger"));
Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName);
- Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName);
+ Assert.Equal(nameof(Splot), baseEntityType.GetTriggers().Single().TableName);
Assert.Equal("Splot_Trigger", baseEntityType.GetTriggers().Single().Name);
+ Assert.Equal("Splot_Trigger", baseEntityType.GetTriggers().Single()
+ .GetName(StoreObjectIdentifier.Create(baseEntityType, StoreObjectType.Table).Value));
Assert.Empty(derivedEntityType.GetDeclaredTriggers());
Assert.Same(baseEntityType.GetTriggers().Single(), derivedEntityType.GetTriggers().Single());
}
diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
index 908599ab78a..b9c4b25fcc1 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
@@ -166,6 +166,132 @@ public abstract class RelationalManyToManyTestBase : ManyToManyTestBase
public abstract class RelationalOwnedTypesTestBase : OwnedTypesTestBase
{
+ [ConditionalFact]
+ public virtual void Can_use_table_splitting_with_owned_reference()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity().OwnsOne(
+ b => b.Label, lb =>
+ {
+ lb.Ignore(l => l.Book);
+ lb.Property("ShadowProp");
+
+ lb.SplitToTable("BookLabelDetails", s =>
+ {
+ var propertyBuilder = s.Property(o => o.Id);
+ var columnBuilder = propertyBuilder.HasColumnName("bid");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
+ });
+ modelBuilder.Entity()
+ .OwnsOne(b => b.AlternateLabel);
+
+ var model = modelBuilder.Model;
+
+ Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel)));
+ Assert.Equal(3, model.GetEntityTypes().Count());
+
+ var book = model.FindEntityType(typeof(Book))!;
+ var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey;
+ var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey;
+ Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType);
+
+ var splitTable = StoreObjectIdentifier.Table("BookLabelDetails");
+ var fragment = bookOwnership1.DeclaringEntityType.GetMappingFragments().Single();
+ Assert.Same(fragment, bookOwnership1.DeclaringEntityType.FindMappingFragment(splitTable));
+ Assert.Same(fragment, bookOwnership1.DeclaringEntityType.GetMappingFragments(StoreObjectType.Table).Single());
+
+ Assert.True(((IConventionEntityTypeMappingFragment)fragment).IsInModel);
+ Assert.Same(bookOwnership1.DeclaringEntityType, fragment.EntityType);
+
+ var bookId = bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))!;
+ Assert.Equal("Id", bookId.GetColumnName());
+ Assert.Null(bookId.GetColumnName(StoreObjectIdentifier.Table("Book")));
+ Assert.Equal("bid", bookId.GetColumnName(splitTable));
+
+ var overrides = bookId.GetOverrides().Single();
+ Assert.Same(overrides, bookId.FindOverrides(splitTable));
+ Assert.True(((IConventionRelationalPropertyOverrides)overrides).IsInModel);
+ Assert.Same(bookId, overrides.Property);
+
+ var readOnlyModel = modelBuilder.FinalizeModel();
+
+ Assert.Equal(2, readOnlyModel.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel)));
+ Assert.Equal(3, readOnlyModel.GetEntityTypes().Count());
+ }
+
+ [ConditionalFact]
+ public virtual void Can_use_view_splitting_with_owned_collection()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Ignore();
+ modelBuilder.Entity().OwnsMany(
+ c => c.Orders,
+ r =>
+ {
+ r.Ignore(o => o.OrderCombination);
+ r.Ignore(o => o.Details);
+ r.Property("ShadowProp");
+
+ r.ToView("Order");
+ r.SplitToView("OrderDetails", s =>
+ {
+ var propertyBuilder = s.Property(o => o.AnotherCustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("cid");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
+ });
+
+ var model = modelBuilder.FinalizeModel();
+
+ var ownership = model.FindEntityType(typeof(Customer))!.FindNavigation(nameof(Customer.Orders))!.ForeignKey;
+ var owned = ownership.DeclaringEntityType;
+ Assert.True(ownership.IsOwnership);
+
+ var splitView = StoreObjectIdentifier.View("OrderDetails");
+ var fragment = owned.GetMappingFragments().Single();
+ Assert.Same(fragment, owned.FindMappingFragment(splitView));
+ Assert.Same(fragment, owned.GetMappingFragments(StoreObjectType.View).Single());
+
+ Assert.True(((IConventionEntityTypeMappingFragment)fragment).IsInModel);
+ Assert.Same(owned, fragment.EntityType);
+
+ var anotherCustomerId = owned.FindProperty(nameof(Order.AnotherCustomerId))!;
+ Assert.Equal("AnotherCustomerId", anotherCustomerId.GetColumnName());
+ Assert.Null(anotherCustomerId.GetColumnName(StoreObjectIdentifier.View("Order")));
+ Assert.Equal("cid", anotherCustomerId.GetColumnName(splitView));
+
+ var overrides = anotherCustomerId.GetOverrides().Single();
+ Assert.Same(overrides, anotherCustomerId.FindOverrides(splitView));
+ Assert.True(((IConventionRelationalPropertyOverrides)overrides).IsInModel);
+ Assert.Same(anotherCustomerId, overrides.Property);
+ }
}
public abstract class TestTableBuilder