Skip to content

Commit

Permalink
Throw a better exception when the model is read-only.
Browse files Browse the repository at this point in the history
Fixes #15812
  • Loading branch information
AndriySvyryd committed Jan 15, 2021
1 parent b5c0bb9 commit 3c8a0f3
Show file tree
Hide file tree
Showing 36 changed files with 615 additions and 99 deletions.
23 changes: 23 additions & 0 deletions src/EFCore/Infrastructure/Annotatable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ public class Annotatable : IMutableAnnotatable
public virtual IEnumerable<Annotation> GetAnnotations()
=> _annotations?.Values ?? Enumerable.Empty<Annotation>();

/// <summary>
/// <para>Indicates whether the current object is read-only.</para>
/// <para>
/// Annotations cannot be changed when the object is read-only.
/// Runtime annotations cannot be changed when the object is not read-only.
/// </para>
/// </summary>
protected virtual bool IsReadonly => false;

/// <summary>
/// Throws if the model is not read-only.
/// </summary>
protected virtual void EnsureReadonly(bool shouldBeReadonly = true)
{
if (!shouldBeReadonly && IsReadonly)
{
throw new InvalidOperationException(CoreStrings.ModelReadOnly);
}
}

/// <summary>
/// Adds an annotation to this object. Throws if an annotation with the specified name already exists.
/// </summary>
Expand Down Expand Up @@ -97,6 +117,8 @@ public virtual void SetAnnotation(string name, object? value)
[NotNull] Annotation annotation,
[CanBeNull] Annotation? oldAnnotation)
{
EnsureReadonly(false);

if (_annotations == null)
{
_annotations = new SortedDictionary<string, Annotation>();
Expand Down Expand Up @@ -146,6 +168,7 @@ public virtual void SetAnnotation(string name, object? value)
public virtual Annotation? RemoveAnnotation([NotNull] string name)
{
Check.NotNull(name, nameof(name));
EnsureReadonly(false);

var annotation = FindAnnotation(name);
if (annotation == null)
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ protected virtual IMutableSkipNavigation WithLeftManyNavigation([NotNull] string
foreignKey, DeclaringEntityType, RelatedEntityType, navigationMember.Name, inverseName);
}

using (foreignKey.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions())
using (foreignKey.DeclaringEntityType.Model.DelayConventions())
{
foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Explicit);
Builder = null;
Expand Down Expand Up @@ -348,7 +348,7 @@ private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigation
foreignKey, DeclaringEntityType, RelatedEntityType, inverseName, navigationName);
}

using (((EntityType)RelatedEntityType).Model.ConventionDispatcher.DelayConventions())
using (((EntityType)RelatedEntityType).Model.DelayConventions())
{
if (conflictingNavigation != null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames)
private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string navigationName)
{
IMutableForeignKey foreignKey;
using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions())
using (var batch = Builder.Metadata.Model.DelayConventions())
{
var navigationMember = new MemberIdentity(navigationName);
var relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit);
Expand Down Expand Up @@ -700,7 +700,7 @@ private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string
private OwnedNavigationBuilder OwnsManyBuilder(in TypeIdentity ownedType, string navigationName)
{
IMutableForeignKey foreignKey;
using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions())
using (var batch = Builder.Metadata.Model.DelayConventions())
{
var navigationMember = new MemberIdentity(navigationName);
var relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit);
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
where TRelatedEntity : class
{
InternalForeignKeyBuilder relationship;
using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions())
using (var batch = Builder.Metadata.Model.DelayConventions())
{
relationship = Builder.HasOwnership(ownedType, navigation, ConfigurationSource.Explicit);
relationship.IsUnique(true, ConfigurationSource.Explicit);
Expand Down Expand Up @@ -1081,7 +1081,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
where TRelatedEntity : class
{
InternalForeignKeyBuilder relationship;
using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions())
using (var batch = Builder.Metadata.Model.DelayConventions())
{
relationship = Builder.HasOwnership(ownedType, navigation, ConfigurationSource.Explicit);

Expand Down
18 changes: 9 additions & 9 deletions src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ protected virtual InternalForeignKeyBuilder Builder
protected virtual T UpdateBuilder<T>([NotNull] Func<T> configure)
{
IConventionForeignKey foreignKey = _builder.Metadata;
var result = DependentEntityType.Model.ConventionDispatcher.Run(configure, ref foreignKey);
var result = DependentEntityType.Model.Track(configure, ref foreignKey);
if (foreignKey != null)
{
_builder = ((ForeignKey)foreignKey).Builder;
Expand Down Expand Up @@ -429,7 +429,7 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames)
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
Expand Down Expand Up @@ -472,7 +472,7 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames)
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName, ownedType), navigationName));
return this;
Expand Down Expand Up @@ -512,7 +512,7 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames)
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)OwnedEntityType.Model), navigationName));
return this;
Expand All @@ -522,7 +522,7 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames)
private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string navigationName)
{
IMutableForeignKey foreignKey;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (var batch = DependentEntityType.Model.DelayConventions())
{
var navigationMember = MemberIdentity.Create(navigationName);
var relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit);
Expand Down Expand Up @@ -654,7 +654,7 @@ private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsManyBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
Expand Down Expand Up @@ -696,7 +696,7 @@ private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsManyBuilder(new TypeIdentity(ownedTypeName, ownedType), navigationName));
return this;
Expand Down Expand Up @@ -735,7 +735,7 @@ private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(OwnsManyBuilder(new TypeIdentity(ownedType, DependentEntityType.Model), navigationName));
return this;
Expand All @@ -745,7 +745,7 @@ private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string
private OwnedNavigationBuilder OwnsManyBuilder(in TypeIdentity ownedType, string navigationName)
{
IMutableForeignKey foreignKey;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (var batch = DependentEntityType.Model.DelayConventions())
{
var navigationMember = MemberIdentity.Create(navigationName);
var relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit);
Expand Down
12 changes: 6 additions & 6 deletions src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
where TNewDependentEntity : class
{
InternalForeignKeyBuilder relationship;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (var batch = DependentEntityType.Model.DelayConventions())
{
relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigation, ConfigurationSource.Explicit);
relationship.IsUnique(true, ConfigurationSource.Explicit);
Expand Down Expand Up @@ -799,7 +799,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(
OwnsManyBuilder<TNewDependentEntity>(
Expand Down Expand Up @@ -932,7 +932,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(
OwnsManyBuilder<TNewDependentEntity>(
Expand Down Expand Up @@ -973,7 +973,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
Check.NotNull(navigationExpression, nameof(navigationExpression));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(
OwnsManyBuilder<TNewDependentEntity>(
Expand Down Expand Up @@ -1018,7 +1018,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
Check.NotNull(navigationExpression, nameof(navigationExpression));
Check.NotNull(buildAction, nameof(buildAction));

using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (DependentEntityType.Model.DelayConventions())
{
buildAction(
OwnsManyBuilder<TNewDependentEntity>(
Expand All @@ -1034,7 +1034,7 @@ public new virtual IndexBuilder<TEntity> HasIndex([NotNull] params string[] prop
where TNewRelatedEntity : class
{
InternalForeignKeyBuilder relationship;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
using (var batch = DependentEntityType.Model.DelayConventions())
{
relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigation, ConfigurationSource.Explicit);
relationship.IsUnique(false, ConfigurationSource.Explicit);
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Builders/ReferenceNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference)
InternalForeignKeyBuilder.ThrowForConflictingNavigation(Builder.Metadata, referenceName, false);
}

using var batch = Builder.Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions();
using var batch = Builder.Metadata.DeclaringEntityType.Model.DelayConventions();
var builder = Builder.IsUnique(true, ConfigurationSource.Explicit);
var foreignKey = builder.Metadata;
if (foreignKey.IsSelfReferencing()
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public virtual ReferenceReferenceBuilder HasAnnotation([NotNull] string annotati
dependentEntityTypeName));
}

using var batch = dependentEntityType.Model.ConventionDispatcher.DelayConventions();
using var batch = dependentEntityType.Model.DelayConventions();
var builder = Builder.HasEntityTypes(
GetOtherEntityType(dependentEntityType), dependentEntityType, ConfigurationSource.Explicit);
builder = hasForeignKey(builder, dependentEntityType);
Expand Down Expand Up @@ -301,7 +301,7 @@ public virtual ReferenceReferenceBuilder HasAnnotation([NotNull] string annotati
principalEntityTypeName));
}

using var batch = principalEntityType.Model.ConventionDispatcher.DelayConventions();
using var batch = principalEntityType.Model.DelayConventions();
var builder = Builder.HasEntityTypes(
principalEntityType, GetOtherEntityType(principalEntityType), ConfigurationSource.Explicit);
builder = hasPrincipalKey(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ public virtual IConventionBatch DelayConventions()
/// 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.
/// </summary>
public virtual T Run<T>([NotNull] Func<T> func, [CanBeNull] ref IConventionForeignKey foreignKey)
public virtual T Track<T>([NotNull] Func<T> func, [CanBeNull] ref IConventionForeignKey foreignKey)
{
var batch = DelayConventions();
using var foreignKeyReference = Tracker.Track(foreignKey);
Expand Down
13 changes: 13 additions & 0 deletions src/EFCore/Metadata/IConventionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;

#nullable enable

Expand All @@ -29,6 +30,18 @@ public interface IConventionModel : IModel, IConventionAnnotatable
/// </summary>
new IConventionModelBuilder Builder { get; }

/// <summary>
/// <para>
/// Prevents conventions from being executed immediately when a metadata aspect is modified. All the delayed conventions
/// will be executed after the returned object is disposed.
/// </para>
/// <para>
/// This is useful when performing multiple operations that depend on each other.
/// </para>
/// </summary>
/// <returns> An object that should be disposed to execute the delayed conventions. </returns>
IConventionBatch DelayConventions();

/// <summary>
/// <para>
/// Adds a shadow state entity type to the model.
Expand Down
13 changes: 13 additions & 0 deletions src/EFCore/Metadata/IMutableModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;

#nullable enable

Expand All @@ -23,6 +24,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata
/// </summary>
public interface IMutableModel : IModel, IMutableAnnotatable
{
/// <summary>
/// <para>
/// Prevents conventions from being executed immediately when a metadata aspect is modified. All the delayed conventions
/// will be executed after the returned object is disposed.
/// </para>
/// <para>
/// This is useful when performing multiple operations that depend on each other.
/// </para>
/// </summary>
/// <returns> An object that should be disposed to execute the delayed conventions. </returns>
IConventionBatch DelayConventions();

/// <summary>
/// <para>
/// Adds a shadow state entity type to the model.
Expand Down

0 comments on commit 3c8a0f3

Please sign in to comment.