Skip to content

Commit

Permalink
Improved exception messages (#26398)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcvickers authored Oct 19, 2021
1 parent 8e355d3 commit a2073ca
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 45 deletions.
26 changes: 26 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1525,5 +1525,31 @@ protected virtual void ValidateIndexProperties(
}
}
}

/// <summary>
/// Throws an <see cref="InvalidOperationException"/> with a message containing provider-specific information, when
/// available, indicating possible reasons why the property cannot be mapped.
/// </summary>
/// <param name="propertyType">The property CLR type.</param>
/// <param name="entityType">The entity type.</param>
/// <param name="unmappedProperty">The property.</param>
protected override void ThrowPropertyNotMappedException(
string propertyType,
IConventionEntityType entityType,
IConventionProperty unmappedProperty)
{
var storeType = unmappedProperty.GetColumnType();
if (storeType != null)
{
throw new InvalidOperationException(
RelationalStrings.PropertyNotMapped(
propertyType,
entityType.DisplayName(),
unmappedProperty.Name,
storeType));
}

base.ThrowPropertyNotMappedException(propertyType, entityType, unmappedProperty);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,9 @@
<data name="ProjectionMappingCountMismatch" xml:space="preserve">
<value>Unable to translate set operations when both sides don't assign values to the same properties in the nominal type. Please make sure that the same properties are included on both sides, and consider assigning default values if a property doesn't require a specific value.</value>
</data>
<data name="PropertyNotMapped" xml:space="preserve">
<value>The '{propertyType}' property '{entityType}.{property}' could not be mapped to the database type '{storeType}' because the database provider does not support mapping '{propertyType}' properties to '{storeType}' columns. Consider mapping to a different database type or converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternately, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.</value>
</data>
<data name="PropertyNotMappedToTable" xml:space="preserve">
<value>The property '{property}' on entity type '{entityType}' is not mapped to '{table}'.</value>
</data>
Expand Down
4 changes: 4 additions & 0 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,10 @@ public InternalEntityEntry PrepareToSave()
&& property.IsForeignKey()
&& _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown))
{
if (property.GetContainingForeignKeys().Any(fk => fk.IsOwnership))
{
throw new InvalidOperationException(CoreStrings.SaveOwnedWithoutOwner(entityType.DisplayName()));
}
throw new InvalidOperationException(CoreStrings.UnknownKeyValue(entityType.DisplayName(), property.Name));
}
}
Expand Down
25 changes: 21 additions & 4 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ protected virtual void ValidatePropertyMapping(

if (unmappedProperty != null)
{
throw new InvalidOperationException(
CoreStrings.PropertyNotMapped(
entityType.DisplayName(), unmappedProperty.Name,
(unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName()));
ThrowPropertyNotMappedException(
(unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName(),
entityType,
unmappedProperty);
}

if (entityType.ClrType == Model.DefaultPropertyBagType)
Expand Down Expand Up @@ -287,6 +287,23 @@ protected virtual void ValidatePropertyMapping(
}
}

/// <summary>
/// Throws an <see cref="InvalidOperationException"/> with a message containing provider-specific information, when
/// available, indicating possible reasons why the property cannot be mapped.
/// </summary>
/// <param name="propertyType">The property CLR type.</param>
/// <param name="entityType">The entity type.</param>
/// <param name="unmappedProperty">The property.</param>
protected virtual void ThrowPropertyNotMappedException(
string propertyType,
IConventionEntityType entityType,
IConventionProperty unmappedProperty)
=> throw new InvalidOperationException(
CoreStrings.PropertyNotMapped(
propertyType,
entityType.DisplayName(),
unmappedProperty.Name));

/// <summary>
/// Returns a value indicating whether that target CLR type would correspond to an owned entity type.
/// </summary>
Expand Down
16 changes: 6 additions & 10 deletions src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,18 @@ private void GetBindings(
var constructorErrors = bindingFailures.SelectMany(f => f)
.GroupBy(f => (ConstructorInfo)f.Member)
.Select(
x => CoreStrings.ConstructorBindingFailed(
x => " " + CoreStrings.ConstructorBindingFailed(
string.Join("', '", x.Select(f => f.Name)),
entityType.DisplayName()
+ "("
+ string.Join(
", ", x.Key.GetParameters().Select(
y => y.ParameterType.ShortDisplayName() + " " + y.Name)
)
+ ")"
)
$"{entityType.DisplayName()}({string.Join(", ", ConstructConstructor(x))})")
);

IEnumerable<string> ConstructConstructor(IGrouping<ConstructorInfo, ParameterInfo> parameters)
=> parameters.Key.GetParameters().Select(y => $"{y.ParameterType.ShortDisplayName()} {y.Name}");

throw new InvalidOperationException(
CoreStrings.ConstructorNotFound(
entityType.DisplayName(),
string.Join("; ", constructorErrors)));
Environment.NewLine + string.Join(Environment.NewLine, constructorErrors) + Environment.NewLine));
}

if (foundBindings.Count > 1)
Expand Down
20 changes: 14 additions & 6 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,13 @@
<value>Cannot create a relationship between '{newPrincipalNavigationSpecification}' and '{newDependentNavigationSpecification}' because a relationship already exists between '{existingPrincipalNavigationSpecification}' and '{existingDependentNavigationSpecification}'. Navigations can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation '{newDependentNavigationSpecification}' first in 'OnModelCreating'.</value>
</data>
<data name="ConstructorBindingFailed" xml:space="preserve">
<value>cannot bind '{failedBinds}' in '{parameters}'</value>
<value>Cannot bind '{failedBinds}' in '{parameters}'</value>
</data>
<data name="ConstructorConflict" xml:space="preserve">
<value>The constructors '{firstConstructor}' and '{secondConstructor}' have the same number of parameters, and can both be used by Entity Framework. The constructor to be used must be configured in 'OnModelCreating'.</value>
</data>
<data name="ConstructorNotFound" xml:space="preserve">
<value>No suitable constructor was found for entity type '{entityType}'. The following constructors had parameters that could not be bound to properties of the entity type: {constructors}.</value>
<value>No suitable constructor was found for entity type '{entityType}'. The following constructors had parameters that could not be bound to properties of the entity type: {constructors}Note that only mapped properties can be bound to constructor parameters. Navigations to related entities, including references to owned types, cannot be bound.</value>
</data>
<data name="ContextDisposed" xml:space="preserve">
<value>Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.</value>
Expand Down Expand Up @@ -1290,7 +1290,7 @@
<value>The property '{1_entityType}.{0_property}' could not be found. Ensure that the property exists and has been included in the model.</value>
</data>
<data name="PropertyNotMapped" xml:space="preserve">
<value>The property '{entityType}.{property}' is of type '{propertyType}' which is not supported by the current database provider. Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.</value>
<value>The '{propertyType}' property '{entityType}.{property}' could not be mapped because the database provider does not support this type. Consider converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternately, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.</value>
</data>
<data name="PropertyReadOnlyAfterSave" xml:space="preserve">
<value>The property '{1_entityType}.{0_property}' is defined as read-only after it has been saved, but its value has been modified or marked as modified.</value>
Expand Down Expand Up @@ -1373,6 +1373,9 @@
<data name="RuntimeParameterMissingParameter" xml:space="preserve">
<value>While registering a runtime parameter, the lambda expression must have only one parameter which must be same as 'QueryCompilationContext.QueryContextParameter' expression.</value>
</data>
<data name="SaveOwnedWithoutOwner" xml:space="preserve">
<value>Cannot save instance of '{entityType}' because it is an owned entity without any reference to its owner. Owned entities can only be saved as part of an aggregate also including the owner entity.</value>
</data>
<data name="SavepointsNotSupported" xml:space="preserve">
<value>Savepoints are not supported by the database provider in use.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public void Throws_when_added_property_is_not_mapped_to_store()

Assert.Equal(
CoreStrings.PropertyNotMapped(
typeof(NonPrimitiveAsPropertyEntity).ShortDisplayName(), "LongProperty", typeof(Tuple<long>).ShortDisplayName()),
typeof(Tuple<long>).ShortDisplayName(),
typeof(NonPrimitiveAsPropertyEntity).ShortDisplayName(),
"LongProperty"),
Assert.Throws<InvalidOperationException>(() => Validate(modelBuilder)).Message);
}

Expand All @@ -33,9 +35,11 @@ public void Throws_when_added_property_is_not_mapped_to_store_even_if_configured
.HasColumnType("some_int_mapping");

Assert.Equal(
CoreStrings.PropertyNotMapped(
typeof(NonPrimitiveNonNavigationAsPropertyEntity).ShortDisplayName(), "LongProperty",
typeof(Tuple<long>).ShortDisplayName()),
RelationalStrings.PropertyNotMapped(
typeof(Tuple<long>).ShortDisplayName(),
typeof(NonPrimitiveNonNavigationAsPropertyEntity).ShortDisplayName(),
"LongProperty",
"some_int_mapping"),
Assert.Throws<InvalidOperationException>(() => Validate(modelBuilder)).Message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public override void Detects_key_property_which_cannot_be_compared()
});

VerifyError(
CoreStrings.PropertyNotMapped(nameof(WithNonComparableKey), nameof(WithNonComparableKey.Id), nameof(NotComparable)),
CoreStrings.PropertyNotMapped(nameof(NotComparable), nameof(WithNonComparableKey), nameof(WithNonComparableKey.Id)),
modelBuilder);
}

Expand All @@ -47,7 +47,7 @@ public override void Detects_unique_index_property_which_cannot_be_compared()

VerifyError(
CoreStrings.PropertyNotMapped(
nameof(WithNonComparableUniqueIndex), nameof(WithNonComparableUniqueIndex.Index), nameof(NotComparable)),
nameof(NotComparable), nameof(WithNonComparableUniqueIndex), nameof(WithNonComparableUniqueIndex.Index)),
modelBuilder);
}

Expand All @@ -64,7 +64,7 @@ public override void Ignores_normal_property_which_cannot_be_compared()

VerifyError(
CoreStrings.PropertyNotMapped(
nameof(WithNonComparableNormalProperty), nameof(WithNonComparableNormalProperty.Foo), nameof(NotComparable)),
nameof(NotComparable), nameof(WithNonComparableNormalProperty), nameof(WithNonComparableNormalProperty.Foo)),
modelBuilder);
}

Expand Down
Loading

0 comments on commit a2073ca

Please sign in to comment.