From 17510f52ae568045ab946c7c795b90eefa9e2885 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 22 Oct 2020 16:28:56 -0700 Subject: [PATCH] Improve exception messages Part of #7201 --- .../Properties/CosmosStrings.Designer.cs | 32 ++--- .../Properties/CosmosStrings.resx | 20 ++-- .../RelationalOptionsExtension.cs | 6 +- .../Properties/RelationalStrings.Designer.cs | 92 +++++++++------ .../Properties/RelationalStrings.resx | 111 +++++++++--------- .../Storage/RelationalConnection.cs | 2 +- .../Update/ModificationCommand.cs | 14 ++- .../Internal/SqlServerModelValidator.cs | 23 ++-- .../Properties/SqlServerStrings.Designer.cs | 40 +++---- .../Properties/SqlServerStrings.resx | 24 ++-- .../SqlServerModificationCommandBatch.cs | 2 +- src/EFCore/Properties/CoreStrings.resx | 4 +- .../RelationalOptionsExtensionTest.cs | 4 +- .../Update/ModificationCommandTest.cs | 8 +- .../DataAnnotationTestBase.cs | 8 +- .../SqlServerModelValidatorTest.cs | 6 +- 16 files changed, 217 insertions(+), 179 deletions(-) diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs index ca04b618a8a..9532da4b1ab 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs @@ -63,7 +63,7 @@ public static string InvalidDerivedTypeInEntityProjection([CanBeNull] object der derivedType, entityType); /// - /// Unable to generate a valid 'id' value to execute a ReadItem query. This usually happens when the value provided for one of the properties is null or an empty string. Please supply a value that's not null or empty. + /// Unable to generate a valid 'id' value to execute a 'ReadItem' query. This usually happens when the value provided for one of the properties is 'null' or an empty string. Please supply a value that's not 'null' or an empty string. /// public static string InvalidResourceId => GetString("InvalidResourceId"); @@ -91,7 +91,7 @@ public static string NavigationPropertyIsNotAnEmbeddedEntity([CanBeNull] object entityType, navigationName); /// - /// The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator property configured. + /// The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator property configured. Configure a discriminator property and assign a unique value for this entity type. /// public static string NoDiscriminatorProperty([CanBeNull] object entityType, [CanBeNull] object container) => string.Format( @@ -99,7 +99,7 @@ public static string NoDiscriminatorProperty([CanBeNull] object entityType, [Can entityType, container); /// - /// The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator value configured. + /// The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator value configured. Configure a unique discriminator value for this entity type. /// public static string NoDiscriminatorValue([CanBeNull] object entityType, [CanBeNull] object container) => string.Format( @@ -115,7 +115,7 @@ public static string NoIdKey([CanBeNull] object entityType, [CanBeNull] object i entityType, idProperty); /// - /// The entity type '{entityType}' does not have a property mapped to the 'id' property in the database. Add a property mapped as 'id'. + /// The entity type '{entityType}' does not have a property mapped to the 'id' property in the database. Add a property mapped to 'id'. /// public static string NoIdProperty([CanBeNull] object entityType) => string.Format( @@ -131,7 +131,7 @@ public static string NonEmbeddedIncludeNotSupported([CanBeNull] object navigatio navigation); /// - /// The entity type '{entityType}' has property '{property}' as its concurrency token, but only '_etag' is supported. Consider using 'EntityTypeBuilder.UseETagConcurrency'. + /// The entity type '{entityType}' has property '{property}' configured as a concurrency token, but only a property mapped to '_etag' is supported as a concurrency token. Consider using 'PropertyBuilder.IsETagConcurrency'. /// public static string NonETagConcurrencyToken([CanBeNull] object entityType, [CanBeNull] object property) => string.Format( @@ -139,7 +139,7 @@ public static string NonETagConcurrencyToken([CanBeNull] object entityType, [Can entityType, property); /// - /// The entity type '{entityType}' does not have a partition key set, but is mapped to the container '{container}' shared by entity types with partition keys. Configure a partition key on '{entityType}'. + /// The entity type '{entityType}' does not have a partition key set, but is mapped to the container '{container}' shared by entity types with partition keys. Configure a compatible partition key on '{entityType}'. /// public static string NoPartitionKey([CanBeNull] object entityType, [CanBeNull] object container) => string.Format( @@ -171,7 +171,7 @@ public static string NullTypeMappingInSqlTree([CanBeNull] object sqlExpression) sqlExpression); /// - /// Cosmos SQL does not allow Offset without Limit. Consider specifying 'Take' operation on the query. + /// Cosmos SQL does not allow Offset without Limit. Consider specifying a 'Take' operation on the query. /// public static string OffsetRequiresLimit => GetString("OffsetRequiresLimit"); @@ -193,19 +193,19 @@ public static string OrphanedNestedDocumentSensitive([CanBeNull] object entityTy entityType, missingEntityType, keyValue); /// - /// Unable to execute a ReadItem query since the partition key value is missing. Consider using the 'WithPartitionKey' method on the query to specify partition key to use. - /// - public static string PartitionKeyMissing - => GetString("PartitionKeyMissing"); - - /// - /// The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical. Remove one of them. + /// The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical to return any results. Remove one of them. /// public static string PartitionKeyMismatch([CanBeNull] object partitionKey1, [CanBeNull] object partitionKey2) => string.Format( GetString("PartitionKeyMismatch", nameof(partitionKey1), nameof(partitionKey2)), partitionKey1, partitionKey2); + /// + /// Unable to execute a 'ReadItem' query since the partition key value is missing. Consider using the 'WithPartitionKey' method on the query to specify partition key to use. + /// + public static string PartitionKeyMissing + => GetString("PartitionKeyMissing"); + /// /// The partition key for entity type '{entityType}' is set to '{property}', but there is no property with that name. /// @@ -223,7 +223,7 @@ public static string PartitionKeyNonStringStoreType([CanBeNull] object property, property, entityType, propertyType); /// - /// The partition key property '{property1}' on '{entityType1}' is mapped as '{storeName1}', but the partition key property '{property2}' on '{entityType2}' is mapped as '{storeName2}'. All partition key properties need to be mapped to the same store property. + /// The partition key property '{property1}' on '{entityType1}' is mapped as '{storeName1}', but the partition key property '{property2}' on '{entityType2}' is mapped as '{storeName2}'. All partition key properties need to be mapped to the same store property for entity types mapped to the same container. /// public static string PartitionKeyStoreNameMismatch([CanBeNull] object property1, [CanBeNull] object entityType1, [CanBeNull] object storeName1, [CanBeNull] object property2, [CanBeNull] object entityType2, [CanBeNull] object storeName2) => string.Format( @@ -231,7 +231,7 @@ public static string PartitionKeyStoreNameMismatch([CanBeNull] object property1, property1, entityType1, storeName1, property2, entityType2, storeName2); /// - /// Unable to execute a ReadItem query since the 'id' value is missing and cannot be generated. + /// Unable to execute a 'ReadItem' query since the 'id' value is missing and cannot be generated. /// public static string ResourceIdMissing => GetString("ResourceIdMissing"); diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx index ab44efafd87..9cac2d72769 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx @@ -148,25 +148,25 @@ Navigation '{entityType}.{navigationName}' doesn't point to an embedded entity. - The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator property configured. + The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator property configured. Configure a discriminator property and assign a unique value for this entity type. - The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator value configured. + The entity type '{entityType}' is sharing the container '{container}' with other types, but does not have a discriminator value configured. Configure a unique discriminator value for this entity type. The entity type '{entityType}' does not have a key declared on the '{idProperty}' property. Add a key to '{entityType}' that contains '{idProperty}'. - The entity type '{entityType}' does not have a property mapped to the 'id' property in the database. Add a property mapped as 'id'. + The entity type '{entityType}' does not have a property mapped to the 'id' property in the database. Add a property mapped to 'id'. Including navigation '{navigation}' is not supported as the navigation is not embedded in same resource. - The entity type '{entityType}' has property '{property}' as its concurrency token, but only '_etag' is supported. Consider using 'EntityTypeBuilder.UseETagConcurrency'. + The entity type '{entityType}' has property '{property}' configured as a concurrency token, but only a property mapped to '_etag' is supported as a concurrency token. Consider using 'PropertyBuilder.IsETagConcurrency'. - The entity type '{entityType}' does not have a partition key set, but is mapped to the container '{container}' shared by entity types with partition keys. Configure a partition key on '{entityType}'. + The entity type '{entityType}' does not have a partition key set, but is mapped to the container '{container}' shared by entity types with partition keys. Configure a compatible partition key on '{entityType}'. The entity type '{entityType}' does not have a key declared on '{partitionKey}' and '{idProperty}' properties. Add a key to '{entityType}' that contains '{partitionKey}' and '{idProperty}'. @@ -186,12 +186,12 @@ The entity of type '{entityType}' is mapped as part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the key value '{keyValue}'. + + The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical to return any results. Remove one of them. + Unable to execute a 'ReadItem' query since the partition key value is missing. Consider using the 'WithPartitionKey' method on the query to specify partition key to use. - - The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical. Remove one of them. - The partition key for entity type '{entityType}' is set to '{property}', but there is no property with that name. @@ -199,7 +199,7 @@ The type of the partition key property '{property}' on '{entityType}' is '{propertyType}'. All partition key properties need to be strings or have a string value converter. - The partition key property '{property1}' on '{entityType1}' is mapped as '{storeName1}', but the partition key property '{property2}' on '{entityType2}' is mapped as '{storeName2}'. All partition key properties need to be mapped to the same store property. + The partition key property '{property1}' on '{entityType1}' is mapped as '{storeName1}', but the partition key property '{property2}' on '{entityType2}' is mapped as '{storeName2}'. All partition key properties need to be mapped to the same store property for entity types mapped to the same container. Unable to execute a 'ReadItem' query since the 'id' value is missing and cannot be generated. @@ -219,4 +219,4 @@ 'VisitChildren' must be overridden in the class deriving from 'SqlExpression'. - + \ No newline at end of file diff --git a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs index d9337c9080f..1cd67ee8af7 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs @@ -144,7 +144,7 @@ public virtual RelationalOptionsExtension WithCommandTimeout(int? commandTimeout if (commandTimeout.HasValue && commandTimeout <= 0) { - throw new InvalidOperationException(RelationalStrings.InvalidCommandTimeout); + throw new InvalidOperationException(RelationalStrings.InvalidCommandTimeout(commandTimeout)); } var clone = Clone(); @@ -172,7 +172,7 @@ public virtual RelationalOptionsExtension WithMaxBatchSize(int? maxBatchSize) if (maxBatchSize.HasValue && maxBatchSize <= 0) { - throw new InvalidOperationException(RelationalStrings.InvalidMaxBatchSize); + throw new InvalidOperationException(RelationalStrings.InvalidMaxBatchSize(maxBatchSize)); } var clone = Clone(); @@ -200,7 +200,7 @@ public virtual RelationalOptionsExtension WithMinBatchSize(int? minBatchSize) if (minBatchSize.HasValue && minBatchSize <= 0) { - throw new InvalidOperationException(RelationalStrings.InvalidMinBatchSize); + throw new InvalidOperationException(RelationalStrings.InvalidMinBatchSize(minBatchSize)); } var clone = Clone(); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 0246c315396..86a32726831 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -63,7 +63,7 @@ public static string ConflictingAmbientTransaction => GetString("ConflictingAmbientTransaction"); /// - /// {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these values. + /// {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these configurations. /// public static string ConflictingColumnServerGeneration([CanBeNull] object conflictingConfiguration, [CanBeNull] object property, [CanBeNull] object existingConfiguration) => string.Format( @@ -125,7 +125,7 @@ public static string ConflictingRowValuesSensitive([CanBeNull] object firstEntit firstEntityType, secondEntityType, keyValue, firstConflictingValue, secondConflictingValue, column); /// - /// The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed. + /// The database model hasn't been initialized. The model needs to be finalized and processed with 'RelationalModelConvention' before the database model can be accessed. /// public static string DatabaseModelMissing => GetString("DatabaseModelMissing"); @@ -292,7 +292,7 @@ public static string DuplicateColumnNameCollationMismatch([CanBeNull] object ent entityType1, property1, entityType2, property2, columnName, table, collation1, collation2); /// - /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different comments ('{comment1}' and '{comment2}'). + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different comments ('{comment1}' and '{comment2}'). /// public static string DuplicateColumnNameCommentMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object comment1, [CanBeNull] object comment2) => string.Format( @@ -308,7 +308,7 @@ public static string DuplicateColumnNameComputedSqlMismatch([CanBeNull] object e entityType1, property1, entityType2, property2, columnName, table, value1, value2); /// - /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different concurrency token configurations. + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different concurrency token configurations. /// public static string DuplicateColumnNameConcurrencyTokenMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) => string.Format( @@ -332,7 +332,7 @@ public static string DuplicateColumnNameDefaultSqlMismatch([CanBeNull] object en entityType1, property1, entityType2, property2, columnName, table, value1, value2); /// - /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different fixed length configuration. + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different fixed length configuration. /// public static string DuplicateColumnNameFixedLengthMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) => string.Format( @@ -380,7 +380,7 @@ public static string DuplicateColumnNameScaleMismatch([CanBeNull] object entityT entityType1, property1, entityType2, property2, columnName, table, scale1, scale2); /// - /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different unicode configurations. + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different unicode configurations. /// public static string DuplicateColumnNameUnicodenessMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) => string.Format( @@ -396,7 +396,7 @@ public static string DuplicateForeignKeyColumnMismatch([CanBeNull] object foreig foreignKeyProperties1, entityType1, foreignKeyProperties2, entityType2, table, foreignKeyName, columnNames1, columnNames2); /// - /// The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different delete behavior ('{deleteBehavior1}' and '{deleteBehavior2}'). + /// The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but configured with different delete behavior ('{deleteBehavior1}' and '{deleteBehavior2}'). /// public static string DuplicateForeignKeyDeleteBehaviorMismatch([CanBeNull] object foreignKeyProperties1, [CanBeNull] object entityType1, [CanBeNull] object foreignKeyProperties2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object foreignKeyName, [CanBeNull] object deleteBehavior1, [CanBeNull] object deleteBehavior2) => string.Format( @@ -428,7 +428,7 @@ public static string DuplicateForeignKeyTableMismatch([CanBeNull] object foreign foreignKeyProperties1, entityType1, foreignKeyProperties2, entityType2, foreignKeyName, table1, table2); /// - /// The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different uniqueness settings. + /// The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different uniqueness configurations. /// public static string DuplicateForeignKeyUniquenessMismatch([CanBeNull] object foreignKeyProperties1, [CanBeNull] object entityType1, [CanBeNull] object foreignKeyProperties2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object foreignKeyName) => string.Format( @@ -452,7 +452,7 @@ public static string DuplicateIndexTableMismatch([CanBeNull] object indexPropert indexProperties1, entityType1, indexProperties2, entityType2, indexName, table1, table2); /// - /// The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different uniqueness settings. + /// The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different uniqueness configurations. /// public static string DuplicateIndexUniquenessMismatch([CanBeNull] object indexProperties1, [CanBeNull] object entityType1, [CanBeNull] object indexProperties2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object indexName) => string.Format( @@ -576,7 +576,7 @@ public static string IncompatibleTableKeyNameMismatch([CanBeNull] object table, table, entityType, otherEntityType, keyName, primaryKey, otherName, otherPrimaryKey); /// - /// Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and potentially other entity types, but there is no linking relationship. Add a foreign key to '{entityType}' on the primary key properties and pointing to the primary key on another entity typed mapped to '{table}'. + /// Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and potentially other entity types, but there is no linking relationship. Add a foreign key to '{entityType}' on the primary key properties and pointing to the primary key on another entity type mapped to '{table}'. /// public static string IncompatibleTableNoRelationship([CanBeNull] object table, [CanBeNull] object entityType, [CanBeNull] object otherEntityType) => string.Format( @@ -638,10 +638,12 @@ public static string InsufficientInformationToIdentifyOuterElementOfCollectionJo => GetString("InsufficientInformationToIdentifyOuterElementOfCollectionJoin"); /// - /// The specified CommandTimeout value is not valid. It must be a positive number. + /// The specified 'CommandTimeout' value '{value}' is not valid. It must be a positive number. /// - public static string InvalidCommandTimeout - => GetString("InvalidCommandTimeout"); + public static string InvalidCommandTimeout([CanBeNull] object value) + => string.Format( + GetString("InvalidCommandTimeout", nameof(value)), + value); /// /// The specified entity type '{derivedType}' is not derived from '{entityType}'. @@ -660,7 +662,7 @@ public static string InvalidKeySelectorForGroupBy([CanBeNull] object keySelector keySelector, keyType); /// - /// The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a function. + /// The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}', but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a function. /// public static string InvalidMappedFunctionDerivedType([CanBeNull] object entityType, [CanBeNull] object functionName, [CanBeNull] object baseEntityType) => string.Format( @@ -668,7 +670,7 @@ public static string InvalidMappedFunctionDerivedType([CanBeNull] object entityT entityType, functionName, baseEntityType); /// - /// The entity type '{entityType}' is mapped to the DbFunction named '{functionName}' with return type '{returnType}'. Ensure that the mapped function returns `IQueryable<{clrType}>`. + /// The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}' with return type '{returnType}'. Ensure that the mapped function returns 'IQueryable<{clrType}>'. /// public static string InvalidMappedFunctionUnmatchedReturn([CanBeNull] object entityType, [CanBeNull] object functionName, [CanBeNull] object returnType, [CanBeNull] object clrType) => string.Format( @@ -676,7 +678,7 @@ public static string InvalidMappedFunctionUnmatchedReturn([CanBeNull] object ent entityType, functionName, returnType, clrType); /// - /// The entity type '{entityType}' is mapped to the DbFunction named '{functionName}' with parameters {parameters}. Ensure that the mapped function doesn't have any parameters. + /// The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}' with parameters {parameters}. Ensure that the mapped function doesn't have any parameters. /// public static string InvalidMappedFunctionWithParameters([CanBeNull] object entityType, [CanBeNull] object functionName, [CanBeNull] object parameters) => string.Format( @@ -684,7 +686,7 @@ public static string InvalidMappedFunctionWithParameters([CanBeNull] object enti entityType, functionName, parameters); /// - /// The entity type '{entityType}' is mapped to a SQL query, but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query. + /// The entity type '{entityType}' is mapped to a SQL query, but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query than the base entity type. /// public static string InvalidMappedSqlQueryDerivedType([CanBeNull] object entityType, [CanBeNull] object baseEntityType) => string.Format( @@ -692,16 +694,20 @@ public static string InvalidMappedSqlQueryDerivedType([CanBeNull] object entityT entityType, baseEntityType); /// - /// The specified MaxBatchSize value is not valid. It must be a positive number. + /// The specified 'MaxBatchSize' value '{value}' is not valid. It must be a positive number. /// - public static string InvalidMaxBatchSize - => GetString("InvalidMaxBatchSize"); + public static string InvalidMaxBatchSize([CanBeNull] object value) + => string.Format( + GetString("InvalidMaxBatchSize", nameof(value)), + value); /// - /// The specified MinBatchSize value is not valid. It must be a positive number. + /// The specified 'MinBatchSize' value '{value}' is not valid. It must be a positive number. /// - public static string InvalidMinBatchSize - => GetString("InvalidMinBatchSize"); + public static string InvalidMinBatchSize([CanBeNull] object value) + => string.Format( + GetString("InvalidMinBatchSize", nameof(value)), + value); /// /// Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'. @@ -752,7 +758,7 @@ public static string MissingIdentifyingProjectionInDistinctGroupBySubquery([CanB column); /// - /// 'Reverse' could not be translated because there is no ordering on the server side. + /// 'Reverse' could not be translated to the server because there is no ordering on the server side. /// public static string MissingOrderingInSelectExpression => GetString("MissingOrderingInSelectExpression"); @@ -766,12 +772,20 @@ public static string MissingParameterValue([CanBeNull] object parameter) parameter); /// - /// Cannot save changes for an entity in state '{entityState}'. + /// Cannot save changes for an entity of type '{entityType}' in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values of the entity. + /// + public static string ModificationCommandInvalidEntityState([CanBeNull] object entityType, [CanBeNull] object entityState) + => string.Format( + GetString("ModificationCommandInvalidEntityState", nameof(entityType), nameof(entityState)), + entityType, entityState); + + /// + /// Cannot save changes for an entity of type '{entityType}' with primary key values {keyValues} in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. /// - public static string ModificationCommandInvalidEntityState([CanBeNull] object entityState) + public static string ModificationCommandInvalidEntityStateSensitive([CanBeNull] object entityType, [CanBeNull] object keyValues, [CanBeNull] object entityState) => string.Format( - GetString("ModificationCommandInvalidEntityState", nameof(entityState)), - entityState); + GetString("ModificationCommandInvalidEntityStateSensitive", nameof(entityType), nameof(keyValues), nameof(entityState)), + entityType, keyValues, entityState); /// /// Multiple relational database provider configurations found. A context can only be configured to use a single database provider. @@ -788,7 +802,7 @@ public static string NamedConnectionStringNotFound([CanBeNull] object name) name); /// - /// A root ambient transaction was completed before the nested transaction. The more nested transactions should be completed first. + /// A root ambient transaction was completed before the nested transaction. The nested transactions should be completed first. /// public static string NestedAmbientTransactionError => GetString("NestedAmbientTransactionError"); @@ -892,7 +906,7 @@ public static string PropertyNotMappedToTable([CanBeNull] object property, [CanB property, entityType, table); /// - /// The entity type '{entityType}' is not mapped to a table, therefore the entities cannot be persisted to the database. Use 'ToTable' in 'OnModelCreating' to map it. + /// The entity type '{entityType}' is not mapped to a table, therefore the entities cannot be persisted to the database. Call 'ToTable' in 'OnModelCreating' to map it to a table. /// public static string ReadonlyEntitySaved([CanBeNull] object entityType) => string.Format( @@ -1064,7 +1078,7 @@ public static string UnsupportedOperatorForSqlExpression([CanBeNull] object node nodeType, expressionType); /// - /// No mapping to a relational type can be found for property '{entity}.{property}' with the CLR type '{clrType}'. + /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. /// public static string UnsupportedPropertyType([CanBeNull] object entity, [CanBeNull] object property, [CanBeNull] object clrType) => string.Format( @@ -1080,7 +1094,7 @@ public static string UnsupportedStoreType([CanBeNull] object type) type); /// - /// No mapping to a relational type could be found for the CLR type '{clrType}'. + /// The current provider doesn't have a store type mapping for properties of type '{clrType}'. /// public static string UnsupportedType([CanBeNull] object clrType) => string.Format( @@ -1144,18 +1158,18 @@ public static string UpdateDataOperationValuesCountMismatch([CanBeNull] object v valuesCount, columnsCount, table); /// - /// An error occurred while updating the entries. See the inner exception for details. + /// An error occurred while saving the entity changes. See the inner exception for details. /// public static string UpdateStoreException => GetString("UpdateStoreException"); /// - /// The property '{propertySpecification}' has specific configuration for the view '{table}', but isn't mapped to a column on that view. Remove the specific configuration, or map an entity type that contains this property to '{table}'. + /// The property '{propertySpecification}' has specific configuration for the view '{view}', however it isn't mapped to a column on that view. Remove the specific configuration or map an entity type that contains this property to '{view}'. /// - public static string ViewOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object table) + public static string ViewOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object view) => string.Format( - GetString("ViewOverrideMismatch", nameof(propertySpecification), nameof(table)), - propertySpecification, table); + GetString("ViewOverrideMismatch", nameof(propertySpecification), nameof(view)), + propertySpecification, view); /// /// 'VisitChildren' must be overridden in the class deriving from 'SqlExpression'. @@ -1262,7 +1276,7 @@ public static EventDefinition LogApplyingMigration([NotNull] IDiagnostic } /// - /// An error occurred while the batch executor was releasing a transaction savepoint. + /// An error occurred while releasing a transaction savepoint during `SaveChanges`. /// public static EventDefinition LogBatchExecutorFailedToReleaseSavepoint([NotNull] IDiagnosticsLogger logger) { @@ -1286,7 +1300,7 @@ public static EventDefinition LogBatchExecutorFailedToReleaseSavepoint([NotNull] } /// - /// An error occurred while the batch executor was rolling back the transaction to a savepoint, after an exception occured. + /// An error occurred while rolling back the transaction to a savepoint, after an exception occured during `SaveChanges`. /// public static EventDefinition LogBatchExecutorFailedToRollbackToSavepoint([NotNull] IDiagnosticsLogger logger) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 0c5fe29c176..91382438648 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -1,17 +1,17 @@  - @@ -136,7 +136,7 @@ An ambient transaction has been detected. The ambient transaction needs to be completed before starting a new transaction on this connection. - {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these values. + {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these configurations. The connection is currently enlisted in a transaction. The enlisted transaction needs to be completed before starting a new transaction. @@ -160,7 +160,7 @@ Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'. - The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed. + The database model hasn't been initialized. The model needs to be finalized and processed with 'RelationalModelConvention' before the database model can be accessed. There is no property mapped to the column '{table}.{column}' which is used in a data operation. Either add a property mapped to this column, or specify the column types in the data operation. @@ -224,13 +224,13 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different collations ('{collation1}' and '{collation2}'). - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different comments ('{comment1}' and '{comment2}'). + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different comments ('{comment1}' and '{comment2}'). '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different computed values ('{value1}' and '{value2}'). - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different concurrency token configurations. + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different concurrency token configurations. '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different data types ('{dataType1}' and '{dataType2}'). @@ -239,7 +239,7 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different default values ('{value1}' and '{value2}'). - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different fixed length configuration. + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different fixed length configuration. '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different stored computed column settings ('{value1}' and '{value2}'). @@ -257,13 +257,13 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different scales ('{scale1}' and '{scale2}'). - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different unicode configurations. + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but have different unicode configurations. The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but use different columns ({columnNames1} and {columnNames2}). - The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different delete behavior ('{deleteBehavior1}' and '{deleteBehavior2}'). + The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but configured with different delete behavior ('{deleteBehavior1}' and '{deleteBehavior2}'). The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but referencing different principal columns ({principalColumnNames1} and {principalColumnNames2}). @@ -275,7 +275,7 @@ The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{foreignKeyName}', but are declared on different tables ('{table1}' and '{table2}'). - The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different uniqueness settings. + The foreign keys {foreignKeyProperties1} on '{entityType1}' and {foreignKeyProperties2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}', but with different uniqueness configurations. The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different columns ({columnNames1} and {columnNames2}). @@ -284,7 +284,7 @@ The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{indexName}', but are declared on different tables ('{table1}' and '{table2}'). - The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different uniqueness settings. + The indexes {indexProperties1} on '{entityType1}' and {indexProperties2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different uniqueness configurations. The keys {keyProperties1} on '{entityType1}' and {keyProperties2} on '{entityType2}' are both mapped to '{table}.{keyName}', but with different columns ({columnNames1} and {columnNames2}). @@ -332,7 +332,7 @@ Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and the name '{keyName}' of the primary key {primaryKey} does not match the name '{otherName}' of the primary key {otherPrimaryKey}. - Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and potentially other entity types, but there is no linking relationship. Add a foreign key to '{entityType}' on the primary key properties and pointing to the primary key on another entity typed mapped to '{table}'. + Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and potentially other entity types, but there is no linking relationship. Add a foreign key to '{entityType}' on the primary key properties and pointing to the primary key on another entity type mapped to '{table}'. Cannot use view '{view}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and there is a relationship between their primary keys in which '{entityType}' is the dependent, but '{entityType}' has a base entity type mapped to a different view. Either map '{otherEntityType}' to a different view, or invert the relationship between '{entityType}' and '{otherEntityType}'. @@ -356,7 +356,7 @@ Unable to translate a collection subquery in a projection since the parent query doesn't project the key columns of all tables required to generate results on the client side. This can happen when trying to correlate on keyless entity or when using 'Distinct' or 'GroupBy' operations without projecting all of the key columns. - The specified CommandTimeout value is not valid. It must be a positive number. + The specified 'CommandTimeout' value '{value}' is not valid. It must be a positive number. The specified entity type '{derivedType}' is not derived from '{entityType}'. @@ -365,22 +365,22 @@ The grouping key '{keySelector}' is of type '{keyType}' which is not valid key. - The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a function. + The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}', but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a function. - The entity type '{entityType}' is mapped to the DbFunction named '{functionName}' with return type '{returnType}'. Ensure that the mapped function returns 'IQueryable<{clrType}>'. + The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}' with return type '{returnType}'. Ensure that the mapped function returns 'IQueryable<{clrType}>'. - The entity type '{entityType}' is mapped to the DbFunction named '{functionName}' with parameters {parameters}. Ensure that the mapped function doesn't have any parameters. + The entity type '{entityType}' is mapped to the 'DbFunction' named '{functionName}' with parameters {parameters}. Ensure that the mapped function doesn't have any parameters. - The entity type '{entityType}' is mapped to a SQL query, but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query. + The entity type '{entityType}' is mapped to a SQL query, but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query than the base entity type. - The specified MaxBatchSize value is not valid. It must be a positive number. + The specified 'MaxBatchSize' value '{value}' is not valid. It must be a positive number. - The specified MinBatchSize value is not valid. It must be a positive number. + The specified 'MinBatchSize' value '{value}' is not valid. It must be a positive number. Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'. @@ -398,11 +398,11 @@ Information RelationalEventId.MigrationApplying string - An error occurred while the batch executor was releasing a transaction savepoint. + An error occurred while releasing a transaction savepoint during `SaveChanges`. Debug RelationalEventId.BatchExecutorFailedToReleaseSavepoint - An error occurred while the batch executor was rolling back the transaction to a savepoint, after an exception occured. + An error occurred while rolling back the transaction to a savepoint, after an exception occured during `SaveChanges`. Debug RelationalEventId.BatchExecutorFailedToRollbackToSavepoint @@ -623,7 +623,10 @@ No value was provided for the required parameter '{parameter}'. - Cannot save changes for an entity in state '{entityState}'. + Cannot save changes for an entity of type '{entityType}' in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values of the entity. + + + Cannot save changes for an entity of type '{entityType}' with primary key values {keyValues} in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. Multiple relational database provider configurations found. A context can only be configured to use a single database provider. @@ -632,7 +635,7 @@ A named connection string was used, but the name '{name}' was not found in the application's configuration. Note that named connection strings are only supported when using 'IConfiguration' and a service provider, such as in a typical ASP.NET Core application. See https://go.microsoft.com/fwlink/?linkid=850912 for more information. - A root ambient transaction was completed before the nested transaction. The more nested transactions should be completed first. + A root ambient transaction was completed before the nested transaction. The nested transactions should be completed first. The connection does not have any active transactions. @@ -677,7 +680,7 @@ The property '{property}' on entity type '{entityType}' is not mapped to the table '{table}'. - The entity type '{entityType}' is not mapped to a table, therefore the entities cannot be persisted to the database. Use 'ToTable' in 'OnModelCreating' to map it. + The entity type '{entityType}' is not mapped to a table, therefore the entities cannot be persisted to the database. Call 'ToTable' in 'OnModelCreating' to map it to a table. Relational-specific methods can only be used when the context is using a relational database provider. @@ -746,13 +749,13 @@ Unsupported operator '{nodeType}' specified for expression of type '{expressionType}'. - No mapping to a relational type can be found for property '{entity}.{property}' with the CLR type '{clrType}'. + No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. The store type '{type}' is not supported by the current provider. - No mapping to a relational type could be found for the CLR type '{clrType}'. + The current provider doesn't have a store type mapping for properties of type '{clrType}'. The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. @@ -776,12 +779,12 @@ The number of values ({valuesCount}) doesn't match the number of columns ({columnsCount}) for the data modification operation on '{table}'. Provide the same number of values and columns. - An error occurred while updating the entries. See the inner exception for details. + An error occurred while saving the entity changes. See the inner exception for details. - The property '{propertySpecification}' has specific configuration for the view '{table}', however it isn't mapped to a column on that view. Remove the specific configuration or map an entity type that contains this property to '{table}'. + The property '{propertySpecification}' has specific configuration for the view '{view}', however it isn't mapped to a column on that view. Remove the specific configuration or map an entity type that contains this property to '{view}'. 'VisitChildren' must be overridden in the class deriving from 'SqlExpression'. - + \ No newline at end of file diff --git a/src/EFCore.Relational/Storage/RelationalConnection.cs b/src/EFCore.Relational/Storage/RelationalConnection.cs index 838a31bb96d..47c6d79b428 100644 --- a/src/EFCore.Relational/Storage/RelationalConnection.cs +++ b/src/EFCore.Relational/Storage/RelationalConnection.cs @@ -257,7 +257,7 @@ public virtual int? CommandTimeout if (value.HasValue && value < 0) { - throw new ArgumentException(RelationalStrings.InvalidCommandTimeout); + throw new ArgumentException(RelationalStrings.InvalidCommandTimeout(value)); } _commandTimeout = value; diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 8384290de5d..a5d027d1db6 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -180,7 +180,19 @@ public virtual void AddEntry([NotNull] IUpdateEntry entry, bool mainEntry) case EntityState.Added: break; default: - throw new ArgumentException(RelationalStrings.ModificationCommandInvalidEntityState(entry.EntityState)); + if (_sensitiveLoggingEnabled) + { + throw new InvalidOperationException( + RelationalStrings.ModificationCommandInvalidEntityStateSensitive( + entry.EntityType.DisplayName(), + entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties), + entry.EntityState)); + } + + throw new InvalidOperationException( + RelationalStrings.ModificationCommandInvalidEntityState( + entry.EntityType.DisplayName(), + entry.EntityState)); } if (mainEntry) diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index c513ecda923..ac529646b01 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -174,28 +174,37 @@ protected virtual void ValidateIndexIncludeProperties( if (notFound != null) { throw new InvalidOperationException( - SqlServerStrings.IncludePropertyNotFound(index.DeclaringEntityType.DisplayName(), notFound)); + SqlServerStrings.IncludePropertyNotFound( + notFound, + index.Name == null ? index.Properties.Format() : "'" + index.Name + "'", + index.DeclaringEntityType.DisplayName())); } - var duplicate = includeProperties + var duplicateProperty = includeProperties .GroupBy(i => i) .Where(g => g.Count() > 1) .Select(y => y.Key) .FirstOrDefault(); - if (duplicate != null) + if (duplicateProperty != null) { throw new InvalidOperationException( - SqlServerStrings.IncludePropertyDuplicated(index.DeclaringEntityType.DisplayName(), duplicate)); + SqlServerStrings.IncludePropertyDuplicated( + index.DeclaringEntityType.DisplayName(), + duplicateProperty, + index.Name == null ? index.Properties.Format() : "'" + index.Name + "'")); } - var inIndex = includeProperties + var coveredProperty = includeProperties .FirstOrDefault(i => index.Properties.Any(p => i == p.Name)); - if (inIndex != null) + if (coveredProperty != null) { throw new InvalidOperationException( - SqlServerStrings.IncludePropertyInIndex(index.DeclaringEntityType.DisplayName(), inIndex)); + SqlServerStrings.IncludePropertyInIndex( + index.DeclaringEntityType.DisplayName(), + coveredProperty, + index.Name == null ? index.Properties.Format() : "'" + index.Name + "'")); } } } diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 3b1849793c8..95273289c09 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -74,7 +74,7 @@ public static string DuplicateColumnSequenceMismatch([CanBeNull] object entityTy entityType1, property1, entityType2, property2, columnName, table); /// - /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different clustered configurations. + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different clustered configurations. /// public static string DuplicateIndexClusteredMismatch([CanBeNull] object index1, [CanBeNull] object entityType1, [CanBeNull] object index2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object indexName) => string.Format( @@ -82,7 +82,7 @@ public static string DuplicateIndexClusteredMismatch([CanBeNull] object index1, index1, entityType1, index2, entityType2, table, indexName); /// - /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different fill factor configurations. + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different fill factor configurations. /// public static string DuplicateIndexFillFactorMismatch([CanBeNull] object index1, [CanBeNull] object entityType1, [CanBeNull] object index2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object indexName) => string.Format( @@ -90,7 +90,7 @@ public static string DuplicateIndexFillFactorMismatch([CanBeNull] object index1, index1, entityType1, index2, entityType2, table, indexName); /// - /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different included columns: {includedColumns1} and {includedColumns2}. + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different included columns: {includedColumns1} and {includedColumns2}. /// public static string DuplicateIndexIncludedMismatch([CanBeNull] object index1, [CanBeNull] object entityType1, [CanBeNull] object index2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object indexName, [CanBeNull] object includedColumns1, [CanBeNull] object includedColumns2) => string.Format( @@ -98,7 +98,7 @@ public static string DuplicateIndexIncludedMismatch([CanBeNull] object index1, [ index1, entityType1, index2, entityType2, table, indexName, includedColumns1, includedColumns2); /// - /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different online configurations. + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different online configurations. /// public static string DuplicateIndexOnlineMismatch([CanBeNull] object index1, [CanBeNull] object entityType1, [CanBeNull] object index2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object indexName) => string.Format( @@ -106,7 +106,7 @@ public static string DuplicateIndexOnlineMismatch([CanBeNull] object index1, [Ca index1, entityType1, index2, entityType2, table, indexName); /// - /// The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but with different clustering configurations. + /// The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but have different clustering configurations. /// public static string DuplicateKeyMismatchedClustering([CanBeNull] object key1, [CanBeNull] object entityType1, [CanBeNull] object key2, [CanBeNull] object entityType2, [CanBeNull] object table, [CanBeNull] object keyName) => string.Format( @@ -122,28 +122,28 @@ public static string IdentityBadType([CanBeNull] object property, [CanBeNull] ob property, entityType, propertyType); /// - /// Include property '{entityType}.{property}' cannot be defined multiple times. + /// The include property '{entityType}.{property}' was specified multiple times for the index {index}. /// - public static string IncludePropertyDuplicated([CanBeNull] object entityType, [CanBeNull] object property) + public static string IncludePropertyDuplicated([CanBeNull] object entityType, [CanBeNull] object property, [CanBeNull] object index) => string.Format( - GetString("IncludePropertyDuplicated", nameof(entityType), nameof(property)), - entityType, property); + GetString("IncludePropertyDuplicated", nameof(entityType), nameof(property), nameof(index)), + entityType, property, index); /// - /// Include property '{entityType}.{property}' is already included in the index. + /// The include property '{entityType}.{property}' is already part of the index {index}. /// - public static string IncludePropertyInIndex([CanBeNull] object entityType, [CanBeNull] object property) + public static string IncludePropertyInIndex([CanBeNull] object entityType, [CanBeNull] object property, [CanBeNull] object index) => string.Format( - GetString("IncludePropertyInIndex", nameof(entityType), nameof(property)), - entityType, property); + GetString("IncludePropertyInIndex", nameof(entityType), nameof(property), nameof(index)), + entityType, property, index); /// - /// Include property '{entityType}.{property}' not found. + /// The include property '{property}' specified on the index {index} was not found on entity type '{entityType}'. /// - public static string IncludePropertyNotFound([CanBeNull] object entityType, [CanBeNull] object property) + public static string IncludePropertyNotFound([CanBeNull] object property, [CanBeNull] object index, [CanBeNull] object entityType) => string.Format( - GetString("IncludePropertyNotFound", nameof(entityType), nameof(property)), - entityType, property); + GetString("IncludePropertyNotFound", nameof(property), nameof(index), nameof(entityType)), + property, index, entityType); /// /// Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and entity type '{memoryOptimizedEntityType}' is marked as memory-optimized, but entity type '{nonMemoryOptimizedEntityType}' is not. @@ -174,7 +174,7 @@ public static string InvalidTableToIncludeInScaffolding([CanBeNull] object table table); /// - /// The properties {properties} are configured to use 'Identity' value generation and are mapped to the same table '{table}', but only one column per table can be configured as 'Identity'. Call 'ValueGeneratedNever' for properties that should not use 'Identity'. + /// The properties {properties} are configured to use 'Identity' value generation and are mapped to the same table '{table}', but only one column per table can be configured as 'Identity'. Call 'ValueGeneratedNever' in 'OnModelCreating' for properties that should not use 'Identity'. /// public static string MultipleIdentityColumns([CanBeNull] object properties, [CanBeNull] object table) => string.Format( @@ -260,7 +260,7 @@ public static EventDefinition LogByteIdentityColumn([NotNull] ID } /// - /// Both the SqlServerValueGenerationStrategy '{generationStrategy}' and '{otherGenerationStrategy}' have been set on property '{propertyName}' on entity type '{entityName}'. Configuring two strategies is usually unintentional, only do this if you are sure you understand the consequences. + /// Both the SqlServerValueGenerationStrategy '{generationStrategy}' and '{otherGenerationStrategy}' have been set on property '{propertyName}' on entity type '{entityName}'. Configuring two strategies is usually unintentional and will likely result in a database error. /// public static EventDefinition LogConflictingValueGenerationStrategies([NotNull] IDiagnosticsLogger logger) { @@ -308,7 +308,7 @@ public static EventDefinition LogDecimalTypeKey([NotNull] IDiagn } /// - /// No type was specified for the decimal property '{property}' on entity type '{entityType}'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'. + /// No store type was specified for the decimal property '{property}' on entity type '{entityType}'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'. /// public static EventDefinition LogDefaultDecimalTypeColumn([NotNull] IDiagnosticsLogger logger) { diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index a9f61c32880..134ad00dde5 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -139,31 +139,31 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different hi-lo sequences. - The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different clustered configurations. + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different clustered configurations. - The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different fill factor configurations. + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different fill factor configurations. - The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different included columns: {includedColumns1} and {includedColumns2}. + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different included columns: {includedColumns1} and {includedColumns2}. - The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but with different online configurations. + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different online configurations. - The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but with different clustering configurations. + The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but have different clustering configurations. Identity value generation cannot be used for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Identity value generation can only be used with signed integer properties. - Include property '{entityType}.{property}' cannot be defined multiple times. + The include property '{entityType}.{property}' was specified multiple times for the index {index}. - Include property '{entityType}.{property}' is already included in the index. + The include property '{entityType}.{property}' is already part of the index {index}. - Include property '{entityType}.{property}' not found. + The include property '{property}' specified on the index {index} was not found on entity type '{entityType}'. Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and entity type '{memoryOptimizedEntityType}' is marked as memory-optimized, but entity type '{nonMemoryOptimizedEntityType}' is not. @@ -182,7 +182,7 @@ Warning SqlServerEventId.ByteIdentityColumnWarning string string - Both the SqlServerValueGenerationStrategy '{generationStrategy}' and '{otherGenerationStrategy}' have been set on property '{propertyName}' on entity type '{entityName}'. Configuring two strategies is usually unintentional, only do this if you are sure you understand the consequences. + Both the SqlServerValueGenerationStrategy '{generationStrategy}' and '{otherGenerationStrategy}' have been set on property '{propertyName}' on entity type '{entityName}'. Configuring two strategies is usually unintentional and will likely result in a database error. Warning SqlServerEventId.ConflictingValueGenerationStrategiesWarning string string string string @@ -190,7 +190,7 @@ Warning SqlServerEventId.DecimalTypeKeyWarning string string - No type was specified for the decimal property '{property}' on entity type '{entityType}'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'. + No store type was specified for the decimal property '{property}' on entity type '{entityType}'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'. Warning SqlServerEventId.DecimalTypeDefaultWarning string string @@ -250,7 +250,7 @@ Debug SqlServerEventId.ReflexiveConstraintIgnored string string - The properties {properties} are configured to use 'Identity' value generation and are mapped to the same table '{table}', but only one column per table can be configured as 'Identity'. Call 'ValueGeneratedNever' for properties that should not use 'Identity'. + The properties {properties} are configured to use 'Identity' value generation and are mapped to the same table '{table}', but only one column per table can be configured as 'Identity'. Call 'ValueGeneratedNever' in 'OnModelCreating' for properties that should not use 'Identity'. The database name could not be determined. To use 'EnsureDeleted', the connection string must specify 'Initial Catalog'. @@ -264,4 +264,4 @@ An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure' to the 'UseSqlServer' call. - + \ No newline at end of file diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs index 6d9ad8c5b26..d81f76e1b21 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs @@ -42,7 +42,7 @@ public SqlServerModificationCommandBatch( if (maxBatchSize.HasValue && maxBatchSize.Value <= 0) { - throw new ArgumentOutOfRangeException(nameof(maxBatchSize), RelationalStrings.InvalidMaxBatchSize); + throw new ArgumentOutOfRangeException(nameof(maxBatchSize), RelationalStrings.InvalidMaxBatchSize(maxBatchSize.Value)); } _maxBatchSize = Math.Min(maxBatchSize ?? 42, MaxRowCount); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 753fe7c41b6..17368a3e71b 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -473,7 +473,7 @@ The foreign keys on entity type '{dependentType}' cannot target the same entity type because it has a defining navigation. - The types of the properties specified for the foreign key {foreignKeyProperties} on entity type '{dependentType}' do not match the types of the properties in the principal key {principalKeyProperties} on entity type '{principalType}'. Provide properties that use the same types in the same order. + The types of the properties specified for the foreign key {foreignKeyProperties} on entity type '{dependentType}' do not match the types of the properties in the principal key {principalKeyProperties} on entity type '{principalType}'. Provide properties that use the same types in the same order. The foreign key {foreignKeyProperties} targeting the key {keyProperties} on '{principalType}' cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. @@ -1457,4 +1457,4 @@ Cannot start tracking the entry for entity type '{entityType}' because it was created by a different StateManager instance. - + \ No newline at end of file diff --git a/test/EFCore.Relational.Tests/RelationalOptionsExtensionTest.cs b/test/EFCore.Relational.Tests/RelationalOptionsExtensionTest.cs index 29dbbfb3dc1..9e7fe2eca8d 100644 --- a/test/EFCore.Relational.Tests/RelationalOptionsExtensionTest.cs +++ b/test/EFCore.Relational.Tests/RelationalOptionsExtensionTest.cs @@ -54,7 +54,7 @@ public void Can_set_CommandTimeout() public void Throws_if_CommandTimeout_out_of_range() { Assert.Equal( - RelationalStrings.InvalidCommandTimeout, + RelationalStrings.InvalidCommandTimeout(-1), Assert.Throws( () => new FakeRelationalOptionsExtension().WithCommandTimeout(-1)).Message); } @@ -75,7 +75,7 @@ public void Can_set_MaxBatchSize() public void Throws_if_MaxBatchSize_out_of_range() { Assert.Equal( - RelationalStrings.InvalidMaxBatchSize, + RelationalStrings.InvalidMaxBatchSize(-1), Assert.Throws( () => new FakeRelationalOptionsExtension().WithMaxBatchSize(-1)).Message); } diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs index 6ebd435667e..38bba3d4785 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs @@ -357,8 +357,8 @@ public void ModificationCommand_throws_for_unchanged_entities() var command = new ModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); Assert.Equal( - RelationalStrings.ModificationCommandInvalidEntityState(EntityState.Unchanged), - Assert.Throws(() => command.AddEntry(entry, true)).Message); + RelationalStrings.ModificationCommandInvalidEntityState("T1", EntityState.Unchanged), + Assert.Throws(() => command.AddEntry(entry, true)).Message); } [ConditionalFact] @@ -369,8 +369,8 @@ public void ModificationCommand_throws_for_unknown_entities() var command = new ModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); Assert.Equal( - RelationalStrings.ModificationCommandInvalidEntityState(EntityState.Detached), - Assert.Throws(() => command.AddEntry(entry, true)).Message); + RelationalStrings.ModificationCommandInvalidEntityState("T1", EntityState.Detached), + Assert.Throws(() => command.AddEntry(entry, true)).Message); } [ConditionalFact] diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index d1b7f39c5a8..39e42d9b418 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -1541,7 +1541,7 @@ public virtual void MaxLengthAttribute_throws_while_inserting_value_longer_than_ }); Assert.Equal( - "An error occurred while updating the entries. See the inner exception for details.", + "An error occurred while saving the entity changes. See the inner exception for details.", Assert.Throws(() => context.SaveChanges()).Message); }); } @@ -2293,7 +2293,7 @@ public virtual void RequiredAttribute_for_navigation_throws_while_inserting_null context.Set().Add(new BookDetails()); Assert.Equal( - "An error occurred while updating the entries. See the inner exception for details.", + "An error occurred while saving the entity changes. See the inner exception for details.", Assert.Throws(() => context.SaveChanges()).Message); }); } @@ -2329,7 +2329,7 @@ public virtual void RequiredAttribute_for_property_throws_while_inserting_null_v }); Assert.Equal( - "An error occurred while updating the entries. See the inner exception for details.", + "An error occurred while saving the entity changes. See the inner exception for details.", Assert.Throws(() => context.SaveChanges()).Message); }); } @@ -2353,7 +2353,7 @@ public virtual void StringLengthAttribute_throws_while_inserting_value_longer_th new Two { Data = "ValidButLongString" }); Assert.Equal( - "An error occurred while updating the entries. See the inner exception for details.", + "An error occurred while saving the entity changes. See the inner exception for details.", Assert.Throws(() => context.SaveChanges()).Message); }); } diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index f5ec8a9d6a2..03e99046f5d 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -301,7 +301,7 @@ public void Detects_missing_include_properties() modelBuilder.Entity().Property(c => c.Type); modelBuilder.Entity().HasIndex(nameof(Dog.Name)).IncludeProperties(nameof(Dog.Type), "Tag"); - VerifyError(SqlServerStrings.IncludePropertyNotFound(nameof(Dog), "Tag"), modelBuilder.Model); + VerifyError(SqlServerStrings.IncludePropertyNotFound("Tag", "{'Name'}", nameof(Dog)), modelBuilder.Model); } [ConditionalFact] @@ -311,7 +311,7 @@ public void Detects_duplicate_include_properties() modelBuilder.Entity().Property(c => c.Type); modelBuilder.Entity().HasIndex(nameof(Dog.Name)).IncludeProperties(nameof(Dog.Type), nameof(Dog.Type)); - VerifyError(SqlServerStrings.IncludePropertyDuplicated(nameof(Dog), nameof(Dog.Type)), modelBuilder.Model); + VerifyError(SqlServerStrings.IncludePropertyDuplicated(nameof(Dog), nameof(Dog.Type), "{'Name'}"), modelBuilder.Model); } [ConditionalFact] @@ -321,7 +321,7 @@ public void Detects_indexed_include_properties() modelBuilder.Entity().Property(c => c.Type); modelBuilder.Entity().HasIndex(nameof(Dog.Name)).IncludeProperties(nameof(Dog.Name)); - VerifyError(SqlServerStrings.IncludePropertyInIndex(nameof(Dog), nameof(Dog.Name)), modelBuilder.Model); + VerifyError(SqlServerStrings.IncludePropertyInIndex(nameof(Dog), nameof(Dog.Name), "{'Name'}"), modelBuilder.Model); } [ConditionalFact]