diff --git a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs index 2301692f0c6..7aaf05edc7a 100644 --- a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs @@ -54,7 +54,7 @@ private static Expression CreateGetValueExpression( var storeName = property.GetCosmosPropertyName(); if (storeName.Length == 0) { - var type = property.FindMapping()?.Converter?.ProviderClrType + var type = property.GetTypeMapping().Converter?.ProviderClrType ?? property.ClrType; Expression calculatedExpression = Expression.Default(type); @@ -80,7 +80,7 @@ public static Expression CreateGetStoreValueExpression(Expression jObjectExpress Expression.Constant(storeName)); var modelClrType = property.ClrType; - var converter = property.FindMapping().Converter; + var converter = property.GetTypeMapping().Converter; if (converter != null) { var nullableExpression = valueExpression; diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs index fa76f01ecb3..f1f9b41a6e8 100644 --- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs +++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs @@ -170,7 +170,7 @@ private static JToken ConvertPropertyValue(IProperty property, object value) return null; } - var converter = property.FindMapping().Converter; + var converter = property.GetTypeMapping().Converter; if (converter != null) { value = converter.ConvertToProvider(value); diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index af1807b2663..2b6bed481fa 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -521,8 +521,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property, } private static ValueConverter FindValueConverter(IProperty property) - => property.FindMapping()?.Converter - ?? property.GetValueConverter(); + => property.GetTypeMapping().Converter; /// /// Generates code for objects. diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index 0efd9d59a47..db846d83969 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -181,7 +181,7 @@ private static IEnumerable GetAnnotatables(IEnumerable GetNamespaces([NotNull] IModel model) => model.GetEntityTypes().SelectMany( e => e.GetDeclaredProperties() - .SelectMany(p => (p.FindMapping()?.Converter?.ProviderClrType ?? p.ClrType).GetNamespaces())) + .SelectMany(p => (p.GetTypeMapping().Converter?.ProviderClrType ?? p.ClrType).GetNamespaces())) .Concat(GetAnnotationNamespaces(GetAnnotatables(model))); private static IEnumerable GetAnnotatables(IModel model) @@ -264,7 +264,7 @@ private static IEnumerable GetAnnotationNamespaces(IEnumerable annotatable is IProperty property && valueType.UnwrapNullableType() == property.ClrType.UnwrapNullableType() - ? property.FindMapping()?.Converter?.ProviderClrType ?? valueType + ? property.GetTypeMapping().Converter?.ProviderClrType ?? valueType : valueType; } } diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs index 9674535fb08..71208bf0416 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs @@ -89,7 +89,7 @@ private static List GetStructuralComparers(IEnumerable => properties.Select(GetStructuralComparer).ToList(); private static ValueComparer GetStructuralComparer(IProperty p) - => p.GetStructuralValueComparer() ?? p.FindMapping()?.StructuralComparer; + => p.GetStructuralValueComparer() ?? p.GetTypeMapping().StructuralComparer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs b/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs index 6736b8950ee..307cdeb0d24 100644 --- a/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs +++ b/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs @@ -57,6 +57,7 @@ public override ValueGenerator Select(IProperty property, IEntityType entityType return property.GetValueGeneratorFactory() == null && property.ClrType.IsInteger() + && property.ClrType.UnwrapNullableType() != typeof(char) ? GetOrCreate(property) : base.Select(property, entityType); } diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index dd8e6b7200c..ca0e2c240ed 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -397,13 +397,22 @@ public static void SetIsFixedLength([NotNull] this IConventionProperty property, => property.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.GetConfigurationSource(); /// - /// Returns the for the given property. + /// Returns the for the given property on a finalized model. + /// + /// The property. + /// The type mapping. + [DebuggerStepThrough] + public static RelationalTypeMapping GetRelationalTypeMapping([NotNull] this IProperty property) + => (RelationalTypeMapping)property.GetTypeMapping(); + + /// + /// Returns the for the given property on a finalized model. /// /// The property. /// The type mapping, or null if none was found. [DebuggerStepThrough] public static RelationalTypeMapping FindRelationalMapping([NotNull] this IProperty property) - => property[CoreAnnotationNames.TypeMapping] as RelationalTypeMapping; + => (RelationalTypeMapping)property.FindMapping(); /// /// diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 33b313d8add..1833536e541 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -359,9 +359,9 @@ protected virtual void ValidateSharedColumnsCompatibility( } var currentTypeString = property.GetColumnType() - ?? property.FindRelationalMapping()?.StoreType; + ?? property.GetRelationalTypeMapping().StoreType; var previousTypeString = duplicateProperty.GetColumnType() - ?? duplicateProperty.FindRelationalMapping()?.StoreType; + ?? duplicateProperty.GetRelationalTypeMapping().StoreType; if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs index 6ed0e626f5c..d26f9fdcada 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs @@ -58,7 +58,7 @@ public virtual void ProcessPropertyAnnotationChanged( /// The property. /// The store value generation strategy to set for the given property. protected override ValueGenerated? GetValueGenerated(IConventionProperty property) - => GetValueGenerated((IProperty)property); + => GetValueGenerated(property); /// /// Returns the store value generation strategy to set for the given property. diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index 7e54688d990..bb70c124047 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -105,7 +105,7 @@ private IModel EnsureModel() x.ToTable(TableName, TableSchema); }); - _model = modelBuilder.Model; + _model = modelBuilder.FinalizeModel(); } return _model; diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 821295e3fc2..b52e6310225 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1788,8 +1788,8 @@ protected virtual void DiffData( var targetValue = entry.GetCurrentValue(targetProperty); var comparer = targetProperty.GetValueComparer() ?? sourceProperty.GetValueComparer() ?? - targetProperty.FindMapping()?.Comparer ?? - sourceProperty.FindMapping()?.Comparer; + targetProperty.GetTypeMapping().Comparer ?? + sourceProperty.GetTypeMapping().Comparer; var modelValuesChanged = sourceProperty.ClrType.UnwrapNullableType() == targetProperty.ClrType.UnwrapNullableType() diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs index 1081f946896..410a34d6140 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs @@ -78,7 +78,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return CreateGetValueExpression( projectionIndex, IsNullableProjection(projection), - property.FindRelationalMapping(), + property.GetRelationalTypeMapping(), methodCallExpression.Type); } diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/ColumnExpression.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/ColumnExpression.cs index fb0aa90549c..ba52f8bd0b1 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/ColumnExpression.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/ColumnExpression.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions public class ColumnExpression : SqlExpression { internal ColumnExpression(IProperty property, TableExpressionBase table, bool nullable) - : this(property.GetColumnName(), table, property.ClrType, property.FindRelationalMapping(), + : this(property.GetColumnName(), table, property.ClrType, property.GetRelationalTypeMapping(), nullable || property.IsNullable || property.DeclaringEntityType.BaseType != null) { } diff --git a/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs b/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs index cbe1e165636..edfb5352c21 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs @@ -186,7 +186,7 @@ public static IRelationalCommandBuilder AddParameter( new TypeMappedRelationalParameter( invariantName, name, - property.FindRelationalMapping(), + property.GetRelationalTypeMapping(), property.IsNullable)); } @@ -277,7 +277,7 @@ public static IRelationalCommandBuilder AddPropertyParameter( new TypeMappedPropertyRelationalParameter( invariantName, name, - property.FindRelationalMapping(), + property.GetRelationalTypeMapping(), property)); } diff --git a/src/EFCore.Relational/Storage/TypeMaterializationInfo.cs b/src/EFCore.Relational/Storage/TypeMaterializationInfo.cs index e4db3a1aa55..f4170684376 100644 --- a/src/EFCore.Relational/Storage/TypeMaterializationInfo.cs +++ b/src/EFCore.Relational/Storage/TypeMaterializationInfo.cs @@ -78,7 +78,7 @@ public TypeMaterializationInfo( if (mapping == null) { - mapping = property?.FindRelationalMapping() + mapping = property?.GetRelationalTypeMapping() ?? typeMappingSource?.GetMapping(modelClrType); } diff --git a/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteMigrationsAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteMigrationsAnnotationProvider.cs index 171452f83ff..e16a833377c 100644 --- a/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteMigrationsAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteMigrationsAnnotationProvider.cs @@ -84,7 +84,6 @@ public override IEnumerable For(IProperty property) } private static bool HasConverter(IProperty property) - => (property.FindMapping()?.Converter - ?? property.GetValueConverter()) != null; + => property.GetTypeMapping().Converter != null; } } diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs index db5c5d14b74..720e348f1ec 100644 --- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs +++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs @@ -198,7 +198,7 @@ private void LocalDetectChanges(InternalEntityEntry entry) var current = entry[property]; var original = entry.GetOriginalValue(property); - var comparer = property.GetValueComparer() ?? property.FindMapping()?.Comparer; + var comparer = property.GetValueComparer() ?? property.GetTypeMapping().Comparer; if (comparer == null) { @@ -253,7 +253,7 @@ private void DetectKeyChange(InternalEntityEntry entry, IProperty property) var currentValue = entry[property]; var comparer = property.GetKeyValueComparer() - ?? property.FindMapping()?.KeyComparer; + ?? property.GetTypeMapping().KeyComparer; // Note that mutation of a byte[] key is not supported or detected, but two different instances // of byte[] with the same content must be detected as equal. diff --git a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs index 9ed2a59d500..2f273b25b26 100644 --- a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs @@ -133,7 +133,7 @@ protected virtual bool TryCreateFromEntry( /// protected static IEqualityComparer CreateEqualityComparer([NotNull] IReadOnlyList properties) { - var comparers = properties.Select(p => p.GetKeyValueComparer() ?? p.FindMapping()?.KeyComparer).ToList(); + var comparers = properties.Select(p => p.GetKeyValueComparer() ?? p.GetTypeMapping().KeyComparer).ToList(); return comparers.All(c => c != null) ? new CompositeCustomComparer(comparers) diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index be72a7a63a5..5cc7e0d2c4d 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -1184,7 +1184,7 @@ private void SetProperty( private static Func ValuesEqualFunc(IProperty property) { var comparer = property.GetValueComparer() - ?? property.FindMapping()?.Comparer; + ?? property.GetTypeMapping().Comparer; return comparer != null ? (Func)((l, r) => comparer.Equals(l, r)) diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index f6ef81dcaa0..e346306f6e2 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -859,7 +859,7 @@ private static bool PrincipalValueEqualsDependentValue( object dependentValue, object principalValue) => (principalProperty.GetKeyValueComparer() - ?? principalProperty.FindMapping()?.KeyComparer) + ?? principalProperty.GetTypeMapping().KeyComparer) ?.Equals(dependentValue, principalValue) ?? StructuralComparisons.StructuralEqualityComparer.Equals( dependentValue, diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValues.cs b/src/EFCore/ChangeTracking/Internal/OriginalValues.cs index 2f5ea654501..6f88b22cfaf 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValues.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValues.cs @@ -107,7 +107,7 @@ public void AcceptChanges(InternalEntityEntry entry) private static object SnapshotValue(IProperty property, object value) { - var comparer = property.GetValueComparer() ?? property.FindMapping()?.Comparer; + var comparer = property.GetValueComparer() ?? property.GetTypeMapping().Comparer; return comparer == null ? value : comparer.Snapshot(value); } diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs index 4747e9002cb..9ac0cb5c540 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs @@ -39,6 +39,6 @@ protected override int GetPropertyCount(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override ValueComparer GetValueComparer(IProperty property) - => property.GetValueComparer() ?? property.FindMapping()?.Comparer; + => property.GetValueComparer() ?? property.GetTypeMapping().Comparer; } } diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs index 941f5dd7255..d3ba89dadee 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs @@ -39,6 +39,6 @@ protected override int GetPropertyCount(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override ValueComparer GetValueComparer(IProperty property) - => property.GetKeyValueComparer() ?? property.FindMapping()?.KeyComparer; + => property.GetKeyValueComparer() ?? property.GetTypeMapping().KeyComparer; } } diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs b/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs index af482cd471b..73abf3d4cdf 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs @@ -49,7 +49,7 @@ private static object SnapshotValue(IPropertyBase propertyBase, object value) { if (propertyBase is IProperty property) { - var comparer = property.GetKeyValueComparer() ?? property.FindMapping()?.KeyComparer; + var comparer = property.GetKeyValueComparer() ?? property.GetTypeMapping().KeyComparer; if (comparer != null) { diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs index 5fca5c63e55..6084d2d1cec 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs @@ -53,7 +53,7 @@ public void SetValue(IProperty property, object value, int index) private static object SnapshotValue(IProperty property, object value) { - var comparer = property.GetValueComparer() ?? property.FindMapping()?.Comparer; + var comparer = property.GetValueComparer() ?? property.GetTypeMapping().Comparer; return comparer == null ? value : comparer.Snapshot(value); } diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs index 54415874865..e069f626475 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs @@ -39,6 +39,6 @@ protected override int GetPropertyCount(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override ValueComparer GetValueComparer(IProperty property) - => property.GetValueComparer() ?? property.FindMapping()?.Comparer; + => property.GetValueComparer() ?? property.GetTypeMapping().Comparer; } } diff --git a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs index e5cb378a4fe..c0142b0ad95 100644 --- a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs @@ -35,7 +35,7 @@ public SimplePrincipalKeyValueFactory([NotNull] IProperty property) _propertyAccessors = _property.GetPropertyAccessors(); var comparer = property.GetKeyValueComparer() - ?? property.FindMapping()?.KeyComparer; + ?? property.GetTypeMapping().KeyComparer; EqualityComparer = comparer != null diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 913a4cfd554..861d8496303 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -1078,7 +1078,7 @@ private static bool KeysEqual(InternalEntityEntry entry, IForeignKey fk, Interna private static bool KeyValuesEqual(IProperty property, object value, object currentValue) => (property.GetKeyValueComparer() - ?? property.FindMapping()?.KeyComparer) + ?? property.GetTypeMapping().KeyComparer) ?.Equals(currentValue, value) ?? Equals(currentValue, value); diff --git a/src/EFCore/Extensions/PropertyExtensions.cs b/src/EFCore/Extensions/PropertyExtensions.cs index 4ed31a14e0d..fb6e727ae94 100644 --- a/src/EFCore/Extensions/PropertyExtensions.cs +++ b/src/EFCore/Extensions/PropertyExtensions.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -22,6 +24,25 @@ namespace Microsoft.EntityFrameworkCore /// public static class PropertyExtensions { + /// + /// Returns the for the given property from a finalized model. + /// + /// The property. + /// The type mapping. + public static CoreTypeMapping GetTypeMapping( + [NotNull] this IProperty property) + { + var mapping = (CoreTypeMapping)property[CoreAnnotationNames.TypeMapping]; + + if (mapping == null) + { + throw new InvalidOperationException( + CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); + } + + return mapping; + } + /// /// Returns the for the given property. /// diff --git a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs index 3b08d38dea2..75eda218d65 100644 --- a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs @@ -93,7 +93,7 @@ protected override IClrPropertyGetter CreateGeneric).MakeGenericType(typeof(TValue)), new object[] diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index c7597b86a0f..6d15480a7bd 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2355,7 +2355,7 @@ public virtual IEnumerable> GetSeedData(bool provide { if (propertyBase is IProperty property) { - valueConverter = property.FindMapping()?.Converter + valueConverter = property.GetTypeMapping().Converter ?? property.GetValueConverter(); } diff --git a/src/EFCore/Metadata/Internal/IndexedPropertyGetterFactory.cs b/src/EFCore/Metadata/Internal/IndexedPropertyGetterFactory.cs index 12339a34f1d..043b5c29287 100644 --- a/src/EFCore/Metadata/Internal/IndexedPropertyGetterFactory.cs +++ b/src/EFCore/Metadata/Internal/IndexedPropertyGetterFactory.cs @@ -54,7 +54,7 @@ protected override IClrPropertyGetter CreateGeneric).MakeGenericType(typeof(TValue)), new object[] diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 32222b9d1a1..d22fe12f36b 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Reflection; @@ -32,6 +32,14 @@ public static string CircularDependency([CanBeNull] object cycle) GetString("CircularDependency", nameof(cycle)), cycle); + /// + /// The model must be finalized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'FinalizeModel' has been called. + /// + public static string ModelNotFinalized([CanBeNull] object method) + => string.Format( + GetString("ModelNotFinalized", nameof(method)), + method); + /// /// The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index e9a6efb62e9..73a6de11d78 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -120,6 +120,9 @@ Unable to save changes because a circular dependency was detected in the data to be saved: '{cycle}'. + + The model must be finalized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'FinalizeModel' has been called. + The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'. diff --git a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs b/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs index 262e25c0cd1..21fb0bd96f3 100644 --- a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs +++ b/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs @@ -73,7 +73,7 @@ private static ValueGenerator CreateFromFactory(IProperty property, IEntityType if (factory == null) { - var mapping = property.FindMapping(); + var mapping = property.GetTypeMapping(); factory = mapping?.ValueGeneratorFactory; if (factory == null) diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index c27c79ae99c..4f5566af3cf 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -39,9 +39,6 @@ public class CSharpMigrationsGeneratorTest [ConditionalFact] public void Test_new_annotations_handled_for_entity_types() { - var model = RelationalTestHelpers.Instance.CreateConventionBuilder(); - var entityType = model.Entity().Metadata; - // Only add the annotation here if it will never be present on IEntityType var notForEntityType = new HashSet { @@ -110,7 +107,8 @@ public void Test_new_annotations_handled_for_entity_types() }; MissingAnnotationCheck( - entityType, notForEntityType, forEntityType, + b => b.Entity().Metadata, + notForEntityType, forEntityType, _toTable, (g, m, b) => g.TestGenerateEntityTypeAnnotations("modelBuilder", (IEntityType)m, b)); } @@ -118,9 +116,6 @@ public void Test_new_annotations_handled_for_entity_types() [ConditionalFact] public void Test_new_annotations_handled_for_properties() { - var model = RelationalTestHelpers.Instance.CreateConventionBuilder(); - var property = model.Entity().Property(e => e.Id).Metadata; - // Only add the annotation here if it will never be present on IProperty var notForProperty = new HashSet { @@ -203,13 +198,14 @@ public void Test_new_annotations_handled_for_properties() }; MissingAnnotationCheck( - property, notForProperty, forProperty, + b => b.Entity().Property(e => e.Id).Metadata, + notForProperty, forProperty, "", (g, m, b) => g.TestGeneratePropertyAnnotations((IProperty)m, b)); } private static void MissingAnnotationCheck( - IMutableAnnotatable metadataItem, + Func createMetadataItem, HashSet invalidAnnotations, Dictionary validAnnotations, string generationDefault, @@ -240,9 +236,13 @@ private static void MissingAnnotationCheck( if (!invalidAnnotations.Contains(annotationName)) { + var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var metadataItem = createMetadataItem(modelBuilder); metadataItem[annotationName] = validAnnotations.ContainsKey(annotationName) ? validAnnotations[annotationName].Value - : new Random(); // Something that cannot be scaffolded by default + : null; + + modelBuilder.FinalizeModel(); var sb = new IndentedStringBuilder(); @@ -261,8 +261,6 @@ private static void MissingAnnotationCheck( ? validAnnotations[annotationName].Expected : generationDefault, sb.ToString()); - - metadataItem[annotationName] = null; } } } @@ -703,6 +701,8 @@ public void Snapshot_with_default_values_are_round_tripped() eb.HasKey(e => e.Boolean); }); + modelBuilder.FinalizeModel(); + var modelSnapshotCode = generator.GenerateSnapshot( "MyNamespace", typeof(MyContext), @@ -715,8 +715,26 @@ public void Snapshot_with_default_values_are_round_tripped() foreach (var property in modelBuilder.Model.GetEntityTypes().Single().GetProperties()) { - var snapshotProperty = entityType.FindProperty(property.Name); - Assert.Equal(property.GetDefaultValue(), snapshotProperty.GetDefaultValue()); + var expected = property.GetDefaultValue(); + var actual = entityType.FindProperty(property.Name).GetDefaultValue(); + + if (actual != null + && expected != null) + { + if (expected.GetType().IsEnum) + { + actual = actual is string actualString + ? Enum.Parse(expected.GetType(), actualString) + : Enum.ToObject(expected.GetType(), actual); + } + + if (actual.GetType() != expected.GetType()) + { + actual = Convert.ChangeType(actual, expected.GetType()); + } + } + + Assert.Equal(expected, actual); } } diff --git a/test/EFCore.InMemory.FunctionalTests/ShadowStateUpdateTest.cs b/test/EFCore.InMemory.FunctionalTests/ShadowStateUpdateTest.cs index b0235b25eba..00b5a0e9317 100644 --- a/test/EFCore.InMemory.FunctionalTests/ShadowStateUpdateTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ShadowStateUpdateTest.cs @@ -4,7 +4,9 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; @@ -15,11 +17,9 @@ public class ShadowStateUpdateTest : IClassFixture [ConditionalFact] public async Task Can_add_update_delete_end_to_end_using_partial_shadow_state() { - IMutableModel model = new Model(); + IMutableModel model = new Model(InMemoryConventionSetBuilder.Build()); var customerType = model.AddEntityType(typeof(Customer)); - var property1 = customerType.AddProperty("Id", typeof(int)); - customerType.SetPrimaryKey(property1); customerType.AddProperty("Name", typeof(string)); var optionsBuilder = new DbContextOptionsBuilder() diff --git a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs index 6a826343564..ae80cd13ccc 100644 --- a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.InMemory.Internal; +using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -177,7 +178,7 @@ public async Task Should_log_writes() private static IModel CreateModel() { - var modelBuilder = new ModelBuilder(new ConventionSet()); + var modelBuilder = new ModelBuilder(InMemoryConventionSetBuilder.Build()); modelBuilder.Entity( b => diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs index 9e734168c5f..d997b2c5f20 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMappingTest.cs @@ -532,8 +532,8 @@ public virtual void Primary_key_type_mapping_is_picked_up_by_FK_without_going_th using (var context = new FruityContext(ContextOptions)) { Assert.Same( - context.Model.FindEntityType(typeof(Banana)).FindProperty("Id").FindMapping(), - context.Model.FindEntityType(typeof(Kiwi)).FindProperty("BananaId").FindMapping()); + context.Model.FindEntityType(typeof(Banana)).FindProperty("Id").GetTypeMapping(), + context.Model.FindEntityType(typeof(Kiwi)).FindProperty("BananaId").GetTypeMapping()); } } @@ -555,8 +555,8 @@ public virtual void Primary_key_type_mapping_can_differ_from_FK() { Assert.Equal( typeof(short), - context.Model.FindEntityType(typeof(Banana)).FindProperty("Id").FindMapping().Converter.ProviderClrType); - Assert.Null(context.Model.FindEntityType(typeof(Kiwi)).FindProperty("Id").FindMapping().Converter); + context.Model.FindEntityType(typeof(Banana)).FindProperty("Id").GetTypeMapping().Converter.ProviderClrType); + Assert.Null(context.Model.FindEntityType(typeof(Kiwi)).FindProperty("Id").GetTypeMapping().Converter); } } diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs index 1465cf753bb..255f8d9768d 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; namespace Microsoft.EntityFrameworkCore.TestUtilities @@ -13,5 +14,8 @@ public TestRelationalConventionSetBuilder( : base(dependencies, relationalDependencies) { } + + public static ConventionSet Build() + => ConventionSet.CreateConventionSet(RelationalTestHelpers.Instance.CreateContext()); } } diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs index 4dcd9539298..b0cdd01527e 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs @@ -24,6 +24,9 @@ private static readonly RelationalTypeMapping _rowversion private static readonly RelationalTypeMapping _defaultIntMapping = new IntTypeMapping("default_int_mapping", dbType: DbType.Int32); + private static readonly RelationalTypeMapping _defaultCharMapping + = new CharTypeMapping("default_char_mapping", dbType: DbType.Int32); + private static readonly RelationalTypeMapping _defaultLongMapping = new LongTypeMapping("default_long_mapping", dbType: DbType.Int64); @@ -99,7 +102,7 @@ private readonly IReadOnlyDictionary _simpleMapping { typeof(byte), _defaultByteMapping }, { typeof(double), _defaultDoubleMapping }, { typeof(DateTimeOffset), _defaultDateTimeOffsetMapping }, - { typeof(char), _defaultIntMapping }, + { typeof(char), _defaultCharMapping }, { typeof(short), _defaultShortMapping }, { typeof(float), _defaultFloatMapping }, { typeof(decimal), _defaultDecimalMapping }, diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs index 21201994587..778ab148378 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.Update.Internal; using Xunit; @@ -18,7 +19,7 @@ public class ModificationCommandComparerTest [ConditionalFact] public void Compare_returns_0_only_for_commands_that_are_equal() { - IMutableModel model = new Model(); + IMutableModel model = new Model(TestRelationalConventionSetBuilder.Build()); var entityType = model.AddEntityType(typeof(object)); var key = entityType.AddProperty("Id", typeof(int)); entityType.SetPrimaryKey(key); @@ -152,7 +153,7 @@ public void Compare_returns_0_only_for_entries_that_have_same_key_values() private void Compare_returns_0_only_for_entries_that_have_same_key_values_generic(T value1, T value2) { - IMutableModel model = new Model(); + IMutableModel model = new Model(TestRelationalConventionSetBuilder.Build()); var entityType = model.AddEntityType(typeof(object)); var keyProperty = entityType.AddProperty("Id", typeof(T)); diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs index 130b160fa64..3115beb6351 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs @@ -440,21 +440,21 @@ private class T1 private static IModel BuildModel(bool generateKeyValues, bool computeNonKeyValue) { - IMutableModel model = new Model(); + IMutableModel model = new Model(TestRelationalConventionSetBuilder.Build()); var entityType = model.AddEntityType(typeof(T1)); - var key = entityType.AddProperty("Id", typeof(int)); + var key = entityType.FindProperty("Id"); key.ValueGenerated = generateKeyValues ? ValueGenerated.OnAdd : ValueGenerated.Never; key.SetColumnName("Col1"); entityType.SetPrimaryKey(key); - var nonKey1 = entityType.AddProperty("Name1", typeof(string)); + var nonKey1 = entityType.FindProperty("Name1"); nonKey1.IsConcurrencyToken = computeNonKeyValue; nonKey1.SetColumnName("Col2"); nonKey1.ValueGenerated = computeNonKeyValue ? ValueGenerated.OnAddOrUpdate : ValueGenerated.Never; - var nonKey2 = entityType.AddProperty("Name2", typeof(string)); + var nonKey2 = entityType.FindProperty("Name2"); nonKey2.IsConcurrencyToken = computeNonKeyValue; nonKey2.SetColumnName("Col3"); diff --git a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs index bb9065b59a6..4d23624e936 100644 --- a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -3,11 +3,9 @@ using System; using System.Linq; -using System.Reflection; using System.Text; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; @@ -411,14 +409,9 @@ protected ModificationCommand CreateDeleteCommand(bool concurrencyToken = true) private IMutableEntityType GetDuckType() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(Duck)); - var id = entityType.AddProperty(typeof(Duck).GetTypeInfo().GetDeclaredProperty(nameof(Duck.Id))); - entityType.AddProperty(typeof(Duck).GetTypeInfo().GetDeclaredProperty(nameof(Duck.Name))); - entityType.AddProperty(typeof(Duck).GetTypeInfo().GetDeclaredProperty(nameof(Duck.Quacks))); - entityType.AddProperty(typeof(Duck).GetTypeInfo().GetDeclaredProperty(nameof(Duck.Computed))); - entityType.AddProperty(typeof(Duck).GetTypeInfo().GetDeclaredProperty(nameof(Duck.ConcurrencyToken))); - entityType.SetPrimaryKey(id); - return entityType; + var modelBuilder = TestHelpers.CreateConventionBuilder(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + return modelBuilder.Model.FindEntityType(typeof(Duck)); } protected class Duck diff --git a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs index d246f61e22f..1b0840a0328 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs @@ -85,7 +85,7 @@ public override ModelBuilder Key_and_MaxLength_64_produce_nvarchar_64() var property = GetProperty(modelBuilder, "PersonFirstName"); - var storeType = property.FindRelationalMapping().StoreType; + var storeType = property.GetRelationalTypeMapping().StoreType; Assert.Equal("nvarchar(64)", storeType); @@ -98,7 +98,7 @@ public override ModelBuilder Timestamp_takes_precedence_over_MaxLength() var property = GetProperty(modelBuilder, "MaxTimestamp"); - var storeType = property.FindRelationalMapping().StoreType; + var storeType = property.GetRelationalTypeMapping().StoreType; Assert.Equal("rowversion", storeType); diff --git a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs index 6ab17b6de53..cc35000ab87 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs @@ -115,12 +115,16 @@ private static FakeRelationalConnection CreateConnection( [ConditionalFact] public void Block_size_is_obtained_from_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo() .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(10, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -129,12 +133,16 @@ public void Block_size_is_obtained_from_default_sequence() [ConditionalFact] public void Block_size_is_obtained_from_named_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(10, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -143,12 +151,16 @@ public void Block_size_is_obtained_from_named_sequence() [ConditionalFact] public void Block_size_is_obtained_from_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo() .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(10, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -157,12 +169,16 @@ public void Block_size_is_obtained_from_model_default_sequence() [ConditionalFact] public void Block_size_is_obtained_from_named_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(10, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -171,13 +187,17 @@ public void Block_size_is_obtained_from_named_model_default_sequence() [ConditionalFact] public void Block_size_is_obtained_from_specified_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .HasSequence("DaneelOlivaw", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(11, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -186,7 +206,9 @@ public void Block_size_is_obtained_from_specified_sequence() [ConditionalFact] public void Non_positive_block_sizes_are_not_allowed() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .HasSequence("DaneelOlivaw", b => b.IncrementsBy(-1)) .Entity() .Property(e => e.Id) @@ -195,6 +217,8 @@ public void Non_positive_block_sizes_are_not_allowed() var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); + modelBuilder.FinalizeModel(); + Assert.StartsWith( CoreStrings.HiLoBadBlockSize, Assert.Throws( @@ -204,13 +228,17 @@ public void Non_positive_block_sizes_are_not_allowed() [ConditionalFact] public void Block_size_is_obtained_from_specified_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .HasSequence("DaneelOlivaw", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal(11, cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.IncrementBy); @@ -219,12 +247,16 @@ public void Block_size_is_obtained_from_specified_model_default_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo() .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("EntityFrameworkHiLoSequence", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -233,12 +265,16 @@ public void Sequence_name_is_obtained_from_default_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_named_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -247,12 +283,16 @@ public void Sequence_name_is_obtained_from_named_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo() .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("EntityFrameworkHiLoSequence", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -261,12 +301,16 @@ public void Sequence_name_is_obtained_from_model_default_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_named_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -275,13 +319,17 @@ public void Sequence_name_is_obtained_from_named_model_default_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_specified_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .HasSequence("DaneelOlivaw", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -290,13 +338,17 @@ public void Sequence_name_is_obtained_from_specified_sequence() [ConditionalFact] public void Sequence_name_is_obtained_from_specified_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw") .HasSequence("DaneelOlivaw", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -305,12 +357,16 @@ public void Sequence_name_is_obtained_from_specified_model_default_sequence() [ConditionalFact] public void Schema_qualified_sequence_name_is_obtained_from_named_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw", "R") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -320,12 +376,16 @@ public void Schema_qualified_sequence_name_is_obtained_from_named_sequence() [ConditionalFact] public void Schema_qualified_sequence_name_is_obtained_from_named_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw", "R") .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -335,13 +395,17 @@ public void Schema_qualified_sequence_name_is_obtained_from_named_model_default_ [ConditionalFact] public void Schema_qualified_sequence_name_is_obtained_from_specified_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .HasSequence("DaneelOlivaw", "R", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .ForSqlServerUseSequenceHiLo("DaneelOlivaw", "R") .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); @@ -351,13 +415,17 @@ public void Schema_qualified_sequence_name_is_obtained_from_specified_sequence() [ConditionalFact] public void Schema_qualified_sequence_name_is_obtained_from_specified_model_default_sequence() { - var property = CreateConventionModelBuilder() + var modelBuilder = CreateConventionModelBuilder(); + + var property = modelBuilder .ForSqlServerUseSequenceHiLo("DaneelOlivaw", "R") .HasSequence("DaneelOlivaw", "R", b => b.IncrementsBy(11)) .Entity() .Property(e => e.Id) .Metadata; + modelBuilder.FinalizeModel(); + var cache = new SqlServerValueGeneratorCache(new ValueGeneratorCacheDependencies()); Assert.Equal("DaneelOlivaw", cache.GetOrAddSequenceState(property, CreateConnection()).Sequence.Name); diff --git a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorSelectorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorSelectorTest.cs index 98cf7ca99da..b12a2c899e2 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorSelectorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorSelectorTest.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.ValueGeneration; @@ -20,36 +20,59 @@ public class SqlServerValueGeneratorSelectorTest [ConditionalFact] public void Returns_built_in_generators_for_types_setup_for_value_generation() { - var model = BuildModel(); + AssertGenerator("Id"); + AssertGenerator("Custom"); + AssertGenerator("Long"); + AssertGenerator("Short"); + AssertGenerator("Byte"); + AssertGenerator("NullableInt"); + AssertGenerator("NullableLong"); + AssertGenerator("NullableShort"); + AssertGenerator("NullableByte"); + AssertGenerator("Decimal"); + AssertGenerator("String"); + AssertGenerator("Guid"); + AssertGenerator("Binary"); + } + + private void AssertGenerator(string propertyName, bool setSequences = false) + { + var builder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + builder.Entity( + b => + { + b.Property(e => e.Custom).HasValueGenerator(); + b.Property(propertyName).ValueGeneratedOnAdd(); + b.HasKey(propertyName); + }); + + if (setSequences) + { + builder.ForSqlServerUseSequenceHiLo(); + Assert.NotNull(builder.Model.FindSequence(SqlServerModelExtensions.DefaultHiLoSequenceName)); + } + + var model = builder.FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType(selector.Select(entityType.FindProperty("Id"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Custom"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Long"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Short"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Byte"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("NullableInt"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("NullableLong"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("NullableShort"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("NullableByte"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Decimal"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("String"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Guid"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Binary"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("AlwaysIdentity"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("AlwaysSequence"), entityType)); + Assert.IsType(selector.Select(entityType.FindProperty(propertyName), entityType)); } [ConditionalFact] public void Returns_temp_guid_generator_when_default_sql_set() { - var model = BuildModel(); + var builder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + builder.Entity( + b => + { + b.Property(e => e.Guid).HasDefaultValueSql("newid()"); + b.HasKey(e => e.Guid); + }); + var model = builder.FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); - entityType.FindProperty("Guid").SetDefaultValueSql("newid()"); - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); Assert.IsType(selector.Select(entityType.FindProperty("Guid"), entityType)); @@ -58,11 +81,16 @@ public void Returns_temp_guid_generator_when_default_sql_set() [ConditionalFact] public void Returns_temp_string_generator_when_default_sql_set() { - var model = BuildModel(); + var builder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + builder.Entity( + b => + { + b.Property(e => e.String).ValueGeneratedOnAdd().HasDefaultValueSql("Foo"); + b.HasKey(e => e.String); + }); + var model = builder.FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); - entityType.FindProperty("String").SetDefaultValueSql("Foo"); - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); var generator = selector.Select(entityType.FindProperty("String"), entityType); @@ -73,11 +101,16 @@ public void Returns_temp_string_generator_when_default_sql_set() [ConditionalFact] public void Returns_temp_binary_generator_when_default_sql_set() { - var model = BuildModel(); + var builder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + builder.Entity( + b => + { + b.HasKey(e => e.Binary); + b.Property(e => e.Binary).HasDefaultValueSql("Foo").ValueGeneratedOnAdd(); + }); + var model = builder.FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); - entityType.FindProperty("Binary").SetDefaultValueSql("Foo"); - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); var generator = selector.Select(entityType.FindProperty("Binary"), entityType); @@ -88,82 +121,58 @@ public void Returns_temp_binary_generator_when_default_sql_set() [ConditionalFact] public void Returns_sequence_value_generators_when_configured_for_model() { - var model = BuildModel(); - model.SetSqlServerValueGenerationStrategy(SqlServerValueGenerationStrategy.SequenceHiLo); - model.FindSequence(SqlServerModelExtensions.DefaultHiLoSequenceName); - var entityType = model.FindEntityType(typeof(AnEntity)); - - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - - Assert.IsType>(selector.Select(entityType.FindProperty("Id"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Custom"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("Long"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("Short"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("Byte"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("NullableInt"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("NullableLong"), entityType)); - Assert.IsType>( - selector.Select(entityType.FindProperty("NullableShort"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("NullableByte"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("Decimal"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("String"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Guid"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("Binary"), entityType)); - Assert.IsType(selector.Select(entityType.FindProperty("AlwaysIdentity"), entityType)); - Assert.IsType>(selector.Select(entityType.FindProperty("AlwaysSequence"), entityType)); + AssertGenerator>("Id", setSequences: true); + AssertGenerator("Custom", setSequences: true); + AssertGenerator>("Long", setSequences: true); + AssertGenerator>("Short", setSequences: true); + AssertGenerator>("Byte", setSequences: true); + AssertGenerator>("NullableInt", setSequences: true); + AssertGenerator>("NullableLong", setSequences: true); + AssertGenerator>("NullableShort", setSequences: true); + AssertGenerator>("NullableByte", setSequences: true); + AssertGenerator>("Decimal", setSequences: true); + AssertGenerator("String", setSequences: true); + AssertGenerator("Guid", setSequences: true); + AssertGenerator("Binary", setSequences: true); } [ConditionalFact] public void Throws_for_unsupported_combinations() { - var model = BuildModel(); + var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + builder.Entity( + b => + { + b.Property(e => e.Random).ValueGeneratedOnAdd(); + b.HasKey(e => e.Random); + }); + var model = builder.FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); + var selector = InMemoryTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); Assert.Equal( - CoreStrings.NoValueGenerator("Random", "AnEntity", typeof(Random).Name), + CoreStrings.NoValueGenerator("Random", "AnEntity", "Something"), Assert.Throws(() => selector.Select(entityType.FindProperty("Random"), entityType)).Message); } [ConditionalFact] public void Returns_generator_configured_on_model_when_property_is_identity() - { - var model = SqlServerTestHelpers.Instance.BuildModelFor(); - model.SetSqlServerValueGenerationStrategy(SqlServerValueGenerationStrategy.SequenceHiLo); - model.AddSequence(SqlServerModelExtensions.DefaultHiLoSequenceName); - var entityType = model.FindEntityType(typeof(AnEntity)); - - var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - - Assert.IsType>(selector.Select(entityType.FindProperty("Id"), entityType)); - } - - private static IMutableModel BuildModel(bool generateValues = true) { var builder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); - builder.Ignore(); - builder.Entity().Property(e => e.Custom).HasValueGenerator(); + builder.Entity(); - var model = builder.Model; - model.AddSequence(SqlServerModelExtensions.DefaultHiLoSequenceName); + builder + .ForSqlServerUseSequenceHiLo() + .HasSequence(SqlServerModelExtensions.DefaultHiLoSequenceName); + var model = builder.ForSqlServerUseSequenceHiLo().FinalizeModel(); var entityType = model.FindEntityType(typeof(AnEntity)); - entityType.AddProperty("Random", typeof(Random)); - - foreach (var property in entityType.GetProperties()) - { - property.ValueGenerated = generateValues ? ValueGenerated.OnAdd : ValueGenerated.Never; - } - - entityType.FindProperty("AlwaysIdentity").ValueGenerated = ValueGenerated.OnAdd; - entityType.FindProperty("AlwaysIdentity").SetSqlServerValueGenerationStrategy(SqlServerValueGenerationStrategy.IdentityColumn); - entityType.FindProperty("AlwaysSequence").ValueGenerated = ValueGenerated.OnAdd; - entityType.FindProperty("AlwaysSequence").SetSqlServerValueGenerationStrategy(SqlServerValueGenerationStrategy.SequenceHiLo); + var selector = SqlServerTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - return model; + Assert.IsType>(selector.Select(entityType.FindProperty("Id"), entityType)); } private class AnEntity @@ -182,18 +191,19 @@ private class AnEntity public byte[] Binary { get; set; } public float Float { get; set; } public decimal Decimal { get; set; } - public int AlwaysIdentity { get; set; } - public int AlwaysSequence { get; set; } - public Random Random { get; set; } + + [NotMapped] + public Something Random { get; set; } } - private class CustomValueGenerator : ValueGenerator + private struct Something { - public override int Next(EntityEntry entry) - { - throw new NotImplementedException(); - } + public int Id { get; set; } + } + private class CustomValueGenerator : ValueGenerator + { + public override int Next(EntityEntry entry) => throw new NotImplementedException(); public override bool GeneratesTemporaryValues => false; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs index 310a8b25db7..3bf8a2428a2 100644 --- a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs @@ -62,7 +62,7 @@ public override ModelBuilder Key_and_MaxLength_64_produce_nvarchar_64() var property = GetProperty(modelBuilder, "PersonFirstName"); - var storeType = property.FindRelationalMapping().StoreType; + var storeType = property.GetRelationalTypeMapping().StoreType; Assert.Equal("TEXT", storeType); @@ -75,7 +75,7 @@ public override ModelBuilder Timestamp_takes_precedence_over_MaxLength() var property = GetProperty(modelBuilder, "MaxTimestamp"); - var storeType = property.FindRelationalMapping().StoreType; + var storeType = property.GetRelationalTypeMapping().StoreType; Assert.Equal("BLOB", storeType); diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs index 987a9489e86..25774450efb 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs @@ -27,6 +27,7 @@ public SqliteMigrationAnnotationProviderTest() public void Adds_Autoincrement_for_OnAdd_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAdd().Metadata; + _modelBuilder.FinalizeModel(); Assert.Contains(_provider.For(property), a => a.Name == _autoincrement.Name && (bool)a.Value); } @@ -35,6 +36,7 @@ public void Adds_Autoincrement_for_OnAdd_integer_property() public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAddOrUpdate().Metadata; + _modelBuilder.FinalizeModel(); Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); } @@ -43,6 +45,7 @@ public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnUpdate().Metadata; + _modelBuilder.FinalizeModel(); Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); } @@ -51,6 +54,7 @@ public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() public void Does_not_add_Autoincrement_for_Never_value_generated_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedNever().Metadata; + _modelBuilder.FinalizeModel(); Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); } @@ -59,6 +63,7 @@ public void Does_not_add_Autoincrement_for_Never_value_generated_integer_propert public void Does_not_add_Autoincrement_for_default_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).Metadata; + _modelBuilder.FinalizeModel(); Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); } @@ -67,6 +72,7 @@ public void Does_not_add_Autoincrement_for_default_integer_property() public void Does_not_add_Autoincrement_for_non_integer_OnAdd_property() { var property = _modelBuilder.Entity().Property(e => e.StringProp).ValueGeneratedOnAdd().Metadata; + _modelBuilder.FinalizeModel(); Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalClrEntityEntryTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalClrEntityEntryTest.cs index 20f34dbd465..3f935ad5eda 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalClrEntityEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalClrEntityEntryTest.cs @@ -1,103 +1,190 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.DependencyInjection; +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Infrastructure; using Xunit; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { - public class InternalClrEntityEntryTest : InternalEntityEntryTestBase + public class InternalClrEntityEntryTest : InternalEntityEntryTestBase< + InternalClrEntityEntryTest.SomeEntity, + InternalClrEntityEntryTest.SomeSimpleEntityBase, + InternalClrEntityEntryTest.SomeDependentEntity, + InternalClrEntityEntryTest.SomeMoreDependentEntity, + InternalClrEntityEntryTest.Root, + InternalClrEntityEntryTest.FirstDependent, + InternalClrEntityEntryTest.SecondDependent, + InternalClrEntityEntryTest.CompositeRoot, + InternalClrEntityEntryTest.CompositeFirstDependent, + InternalClrEntityEntryTest.SomeCompositeEntityBase, + InternalClrEntityEntryTest.CompositeSecondDependent, + InternalClrEntityEntryTest.KClrContext, + InternalClrEntityEntryTest.KClrSnapContext> { [ConditionalFact] - public void Can_get_entity() - { - var model = BuildModel(); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + public virtual void All_original_values_can_be_accessed_for_entity_that_does_full_change_tracking_if_eager_values_on() + => AllOriginalValuesTest(new FullNotificationEntity()); - var entity = new SomeEntity(); - var entry = CreateInternalEntry(configuration, model.FindEntityType(typeof(SomeEntity).FullName), entity); + [ConditionalFact] + public virtual void Required_original_values_can_be_accessed_for_entity_that_does_full_change_tracking() + => OriginalValuesTest(new FullNotificationEntity()); - Assert.Same(entity, entry.Entity); - } + [ConditionalFact] + public virtual void Required_original_values_can_be_accessed_for_entity_that_does_changed_only_notification() + => OriginalValuesTest(new ChangedOnlyEntity()); [ConditionalFact] - public void Can_set_and_get_property_value_from_CLR_object() - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + public virtual void Required_original_values_can_be_accessed_generically_for_entity_that_does_full_change_tracking() + => GenericOriginalValuesTest(new FullNotificationEntity()); - var entity = new SomeEntity - { - Id = 77, - Name = "Magic Tree House" - }; - var entry = CreateInternalEntry(configuration, entityType, entity); + [ConditionalFact] + public virtual void Required_original_values_can_be_accessed_generically_for_entity_that_does_changed_only_notification() + => GenericOriginalValuesTest(new ChangedOnlyEntity()); + + [ConditionalFact] + public virtual void Null_original_values_are_handled_for_entity_that_does_full_change_tracking() + => NullOriginalValuesTest(new FullNotificationEntity()); - Assert.Equal(77, entry[keyProperty]); - Assert.Equal("Magic Tree House", entry[nonKeyProperty]); + [ConditionalFact] + public virtual void Null_original_values_are_handled_for_entity_that_does_changed_only_notification() + => NullOriginalValuesTest(new ChangedOnlyEntity()); - entry[keyProperty] = 78; - entry[nonKeyProperty] = "Normal Tree House"; + [ConditionalFact] + public virtual void Null_original_values_are_handled_generically_for_entity_that_does_full_change_tracking() + => GenericNullOriginalValuesTest(new FullNotificationEntity()); - Assert.Equal(78, entity.Id); - Assert.Equal("Normal Tree House", entity.Name); - } + [ConditionalFact] + public virtual void Null_original_values_are_handled_generically_for_entity_that_does_changed_only_notification() + => GenericNullOriginalValuesTest(new ChangedOnlyEntity()); + + [ConditionalFact] + public virtual void Setting_property_using_state_entry_always_marks_as_modified_full_notifications() + => SetPropertyInternalEntityEntryTest(new FullNotificationEntity()); + + [ConditionalFact] + public virtual void Setting_property_using_state_entry_always_marks_as_modified_changed_notifications() + => SetPropertyInternalEntityEntryTest(new ChangedOnlyEntity()); + + [ConditionalFact] + public void All_original_values_can_be_accessed_for_entity_that_does_changed_only_notifications() + => AllOriginalValuesTest(new ChangedOnlyEntity()); [ConditionalFact] - public void Asking_for_entity_instance_causes_it_to_be_materialized() + public virtual void Temporary_values_are_not_reset_when_entity_is_detached() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new KClrContext()) + { + var entity = new SomeEntity(); + var entry = context.Add(entity).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); + entry.SetEntityState(EntityState.Added); + entry.SetTemporaryValue(keyProperty, -1); + + Assert.NotNull(entry[keyProperty]); + Assert.Equal(0, entity.Id); + Assert.Equal(-1, entry[keyProperty]); + + entry.SetEntityState(EntityState.Detached); - var entity = (SomeEntity)entry.Entity; + Assert.Equal(0, entity.Id); + Assert.Equal(-1, entry[keyProperty]); - Assert.Equal(1, entity.Id); - Assert.Equal("Kool", entity.Name); + entry.SetEntityState(EntityState.Added); + + Assert.Equal(0, entity.Id); + Assert.Equal(-1, entry[keyProperty]); + } } - [ConditionalFact] - public void All_original_values_can_be_accessed_for_entity_that_does_no_notification() + [ConditionalTheory] + [InlineData(EntityState.Unchanged)] + [InlineData(EntityState.Detached)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Deleted)] + public void AcceptChanges_handles_different_entity_states_for_owned_types(EntityState entityState) { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); + using (var context = new KClrContext()) + { + var ownerEntry = context.Entry( + new OwnerClass + { + Id = 1, + Owned = new OwnedClass + { + Value = "Kool" + } + }).GetInfrastructure(); + + var entry = context.Entry(((OwnerClass)ownerEntry.Entity).Owned).GetInfrastructure(); + var valueProperty = entry.EntityType.FindProperty(nameof(OwnedClass.Value)); + + entry.SetEntityState(entityState); + + if (entityState != EntityState.Unchanged) + { + entry[valueProperty] = "Pickle"; + } + + entry.SetOriginalValue(valueProperty, "Cheese"); - AllOriginalValuesTest( - model, entityType, new SomeEntity + entry.AcceptChanges(); + + Assert.Equal( + entityState == EntityState.Deleted || entityState == EntityState.Detached + ? EntityState.Detached + : EntityState.Unchanged, + entry.EntityState); + if (entityState == EntityState.Unchanged) { - Id = 1, - Name = "Kool" - }); + Assert.Equal("Kool", entry[valueProperty]); + Assert.Equal("Kool", entry.GetOriginalValue(valueProperty)); + } + else + { + Assert.Equal("Pickle", entry[valueProperty]); + Assert.Equal( + entityState == EntityState.Detached || entityState == EntityState.Deleted ? "Cheese" : "Pickle", + entry.GetOriginalValue(valueProperty)); + } + } } [ConditionalFact] - public void All_original_values_can_be_accessed_for_entity_that_does_changed_only_notifications() + public void Setting_an_explicit_value_on_the_entity_marks_property_as_not_temporary() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(ChangedOnlyEntity).FullName); + using (var context = new KClrContext()) + { + var entry = context.Entry(new SomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - AllOriginalValuesTest( - model, entityType, new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }); + var entity = (SomeEntity)entry.Entity; + + entry.SetEntityState(EntityState.Added); + entry.SetTemporaryValue(keyProperty, -1); + + Assert.True(entry.HasTemporaryValue(keyProperty)); + + entity.Id = 77; + + context.GetService().DetectChanges(entry); + + Assert.False(entry.HasTemporaryValue(keyProperty)); + + entry.SetEntityState(EntityState.Unchanged); // Does not throw + + var nameProperty = entry.EntityType.FindProperty(nameof(SomeEntity.Name)); + Assert.True(entry.HasDefaultValue(nameProperty)); + + entity.Name = "Name"; + + Assert.False(entry.HasDefaultValue(nameProperty)); + } } [ConditionalFact] @@ -127,37 +214,311 @@ public void Setting_CLR_property_with_full_notifications_does_not_require_Detect Name = "Kool" }, needsDetectChanges: false); - [ConditionalFact] - public void Setting_an_explicit_value_on_the_entity_marks_property_as_not_temporary() + private void SetPropertyClrTest(TEntity entity, bool needsDetectChanges) + where TEntity : class, ISomeEntity { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new KClrContext()) + { + var entry = context.Attach(entity).GetInfrastructure(); + var nameProperty = entry.EntityType.FindProperty("Name"); - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); + Assert.False(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); - var entity = (SomeEntity)entry.Entity; + entity.Name = "Kool"; - entry.SetEntityState(EntityState.Added); - entry.SetTemporaryValue(keyProperty, -1); + Assert.False(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.True(entry.HasTemporaryValue(keyProperty)); + entity.Name = "Beans"; - entity.Id = 77; + if (needsDetectChanges) + { + Assert.False(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); - configuration.GetRequiredService().DetectChanges(entry); + context.GetService().DetectChanges(entry); + } - Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Modified, entry.EntityState); + } + } - entry.SetEntityState(EntityState.Unchanged); // Does not throw + public class SomeCompositeEntityBase + { + public int Id1 { get; set; } + public string Id2 { get; set; } + } - var nameProperty = entityType.FindProperty(nameof(SomeEntity.Name)); - Assert.True(entry.HasDefaultValue(nameProperty)); + public class SomeDependentEntity : SomeCompositeEntityBase + { + public int SomeEntityId { get; set; } + public int JustAProperty { get; set; } + } - entity.Name = "Name"; + public class SomeMoreDependentEntity : SomeSimpleEntityBase + { + public int Fk1 { get; set; } + public string Fk2 { get; set; } + } - Assert.False(entry.HasDefaultValue(nameProperty)); + public class FullNotificationEntity : INotifyPropertyChanging, INotifyPropertyChanged, ISomeEntity + { + private int _id; + private string _name; + + public int Id + { + get => _id; + set + { + if (_id != value) + { + NotifyChanging(); + _id = value; + NotifyChanged(); + } + } + } + + public string Name + { + get => _name; + set + { + if (_name != value) + { + NotifyChanging(); + _name = value; + NotifyChanged(); + } + } + } + + public event PropertyChangingEventHandler PropertyChanging; + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyChanged([CallerMemberName] string propertyName = "") + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + private void NotifyChanging([CallerMemberName] string propertyName = "") + => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); + } + + public class ChangedOnlyEntity : INotifyPropertyChanged, ISomeEntity + { + private int _id; + private string _name; + + public int Id + { + get => _id; + set + { + if (_id != value) + { + _id = value; + NotifyChanged(); + } + } + } + + public string Name + { + get => _name; + set + { + if (_name != value) + { + _name = value; + NotifyChanged(); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyChanged([CallerMemberName] string propertyName = "") + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public class Root : IRoot + { + public int Id { get; set; } + + public FirstDependent First { get; set; } + + IFirstDependent IRoot.First + { + get => First; + set => First = (FirstDependent)value; + } + } + + public class FirstDependent : IFirstDependent + { + public int Id { get; set; } + + public Root Root { get; set; } + + IRoot IFirstDependent.Root + { + get => Root; + set => Root = (Root)value; + } + + public SecondDependent Second { get; set; } + + ISecondDependent IFirstDependent.Second + { + get => Second; + set => Second = (SecondDependent)value; + } + } + + public class SecondDependent : ISecondDependent + { + public int Id { get; set; } + + public FirstDependent First { get; set; } + + IFirstDependent ISecondDependent.First + { + get => First; + set => First = (FirstDependent)value; + } + } + + public class CompositeRoot : ICompositeRoot + { + public int Id1 { get; set; } + public string Id2 { get; set; } + + public ICompositeFirstDependent First { get; set; } + } + + public class CompositeFirstDependent : ICompositeFirstDependent + { + public int Id1 { get; set; } + public string Id2 { get; set; } + + public int RootId1 { get; set; } + public string RootId2 { get; set; } + + public CompositeRoot Root { get; set; } + + ICompositeRoot ICompositeFirstDependent.Root + { + get => Root; + set => Root = (CompositeRoot)value; + } + + public CompositeSecondDependent Second { get; set; } + + ICompositeSecondDependent ICompositeFirstDependent.Second + { + get => Second; + set => Second = (CompositeSecondDependent)value; + } + } + + public class CompositeSecondDependent : ICompositeSecondDependent + { + public int Id1 { get; set; } + public string Id2 { get; set; } + + public int FirstId1 { get; set; } + public string FirstId2 { get; set; } + + public CompositeFirstDependent First { get; set; } + + ICompositeFirstDependent ICompositeSecondDependent.First + { + get => First; + set => First = (CompositeFirstDependent)value; + } + } + + public class OwnerClass + { + public int Id { get; set; } + public virtual OwnedClass Owned { get; set; } + } + + public class OwnedClass + { + public string Value { get; set; } + } + + public interface ISomeEntity + { + int Id { get; set; } + string Name { get; set; } + } + + public class SomeSimpleEntityBase + { + public int Id { get; set; } + } + + public class SomeEntity : SomeSimpleEntityBase, ISomeEntity + { + public string Name { get; set; } + } + + public class KClrContext : KContext + { + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Name).IsConcurrencyToken(); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Name).IsConcurrencyToken(); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); + }); + + modelBuilder.Entity( + eb => + { + eb.HasKey(e => e.Id); + var owned = eb.OwnsOne(e => e.Owned); + owned.WithOwner().HasForeignKey("Id"); + owned.HasKey("Id"); + owned.Property(e => e.Value); + }); + } + } + + public class KClrSnapContext : KContext + { + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Name).IsConcurrencyToken(); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Name).IsConcurrencyToken(); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); + }); + } } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs index 10156877a11..d2d08463a97 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs @@ -3,18 +3,12 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.Update; -using Microsoft.Extensions.DependencyInjection; using Xunit; // ReSharper disable UnusedMember.Global @@ -23,213 +17,229 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { - public abstract class InternalEntityEntryTestBase + public abstract class InternalEntityEntryTestBase< + TSomeEntity, + TSomeSimpleEntityBase, + TSomeDependentEntity, + TSomeMoreDependentEntity, + TRoot, + TFirstDependent, + TSecondDependent, + TCompositeRoot, + TCompositeFirstDependent, + TSomeCompositeEntityBase, + TCompositeSecondDependent, + TKContext, + TKSnapContext> + where TSomeEntity : class, new() + where TSomeSimpleEntityBase : class, new() + where TSomeDependentEntity : class, new() + where TSomeMoreDependentEntity : class, new() + where TRoot : class, IRoot, new() + where TFirstDependent: class, IFirstDependent, new() + where TCompositeRoot : class, ICompositeRoot, new() + where TCompositeFirstDependent: class, ICompositeFirstDependent, new() + where TSecondDependent : class, ISecondDependent, new() + where TSomeCompositeEntityBase : class, new() + where TCompositeSecondDependent : class, ICompositeSecondDependent, new() + where TKContext : DbContext, new() + where TKSnapContext : DbContext, new() { [ConditionalFact] public virtual void Store_setting_null_for_non_nullable_store_generated_property_throws() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - keyProperty.ValueGenerated = ValueGenerated.OnAdd; - - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - var entry = CreateInternalEntry(contextServices, entityType, new SomeEntity()); - entry.SetEntityState(EntityState.Added); - entry.PrepareToSave(); + entry.PrepareToSave(); - Assert.Equal( - CoreStrings.ValueCannotBeNull("Id", keyProperty.DeclaringEntityType.DisplayName(), typeof(int).DisplayName()), - Assert.Throws(() => entry.SetStoreGeneratedValue(keyProperty, null)).Message); + Assert.Equal( + CoreStrings.ValueCannotBeNull("Id", "SomeSimpleEntityBase", "int"), + Assert.Throws( + () => entry.SetStoreGeneratedValue(keyProperty, null)).Message); + } } [ConditionalFact] public virtual void Changing_state_from_Unknown_causes_entity_to_start_tracking() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - var entry = CreateInternalEntry(contextServices, entityType, new SomeEntity()); - entry[keyProperty] = 1; + entry[keyProperty] = 1; - entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Added); - Assert.Equal(EntityState.Added, entry.EntityState); - Assert.Contains(entry, contextServices.GetRequiredService().Entries); + Assert.Equal(EntityState.Added, entry.EntityState); + Assert.Contains(entry, context.GetService().Entries); + } } [ConditionalFact] public virtual void Changing_state_to_Unknown_causes_entity_to_stop_tracking() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - var entry = CreateInternalEntry(contextServices, entityType, new SomeEntity()); - entry[keyProperty] = 1; + entry[keyProperty] = 1; - entry.SetEntityState(EntityState.Added); - entry.SetEntityState(EntityState.Detached); + entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Detached); - Assert.Equal(EntityState.Detached, entry.EntityState); - Assert.DoesNotContain(entry, contextServices.GetRequiredService().Entries); + Assert.Equal(EntityState.Detached, entry.EntityState); + Assert.DoesNotContain(entry, context.GetService().Entries); + } } [ConditionalFact] // GitHub #251, #1247 public virtual void Changing_state_from_Added_to_Deleted_does_what_you_ask() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(contextServices, entityType, new SomeEntity()); - entry[keyProperty] = 1; + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); - entry.SetEntityState(EntityState.Added); - entry.SetEntityState(EntityState.Deleted); + entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Deleted); - Assert.Equal(EntityState.Deleted, entry.EntityState); - Assert.Contains(entry, contextServices.GetRequiredService().Entries); + Assert.Equal(EntityState.Deleted, entry.EntityState); + Assert.Contains(entry, context.GetService().Entries); + } } [ConditionalFact] public virtual void Changing_state_to_Modified_or_Unchanged_causes_all_properties_to_be_marked_accordingly() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - entry[keyProperty] = 1; + var keyProperty = entry.EntityType.FindProperty("Id"); + var nonKeyProperty = entry.EntityType.FindProperty("Name"); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry.SetEntityState(EntityState.Modified); + entry.SetEntityState(EntityState.Modified); - Assert.False(entry.IsModified(keyProperty)); - Assert.NotEqual(nonKeyProperty.IsShadowProperty(), entry.IsModified(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.NotEqual(nonKeyProperty.IsShadowProperty(), entry.IsModified(nonKeyProperty)); - entry.SetEntityState(EntityState.Unchanged, true); + entry.SetEntityState(EntityState.Unchanged, true); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry.SetPropertyModified(nonKeyProperty); + entry.SetPropertyModified(nonKeyProperty); - Assert.Equal(EntityState.Modified, entry.EntityState); - Assert.False(entry.IsModified(keyProperty)); - Assert.True(entry.IsModified(nonKeyProperty)); + Assert.Equal(EntityState.Modified, entry.EntityState); + Assert.False(entry.IsModified(keyProperty)); + Assert.True(entry.IsModified(nonKeyProperty)); + } } [ConditionalFact] public virtual void Key_properties_throw_immediately_if_modified() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - - entry[keyProperty] = 1; + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - entry.SetEntityState(EntityState.Modified); + entry.SetEntityState(EntityState.Modified); - Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(keyProperty)); - entry.SetEntityState(EntityState.Unchanged, true); + entry.SetEntityState(EntityState.Unchanged, true); - Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(keyProperty)); - Assert.Equal( - CoreStrings.KeyReadOnly("Id", entityType.DisplayName()), - Assert.Throws(() => entry.SetPropertyModified(keyProperty)).Message); + Assert.Equal( + CoreStrings.KeyReadOnly("Id", "SomeEntity"), + Assert.Throws( + () => entry.SetPropertyModified(keyProperty)).Message); - Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.False(entry.IsModified(keyProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); + Assert.False(entry.IsModified(keyProperty)); - Assert.Equal( - CoreStrings.KeyReadOnly("Id", entityType.DisplayName()), - Assert.Throws(() => entry[keyProperty] = 2).Message); + Assert.Equal( + CoreStrings.KeyReadOnly("Id", "SomeEntity"), + Assert.Throws( + () => entry[keyProperty] = 2).Message); - Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.False(entry.IsModified(keyProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); + Assert.False(entry.IsModified(keyProperty)); + } } [ConditionalFact] public virtual void Added_entities_can_have_temporary_values() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); + var nonKeyProperty = entry.EntityType.FindProperty("Name"); - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - entry[keyProperty] = 1; + entry[keyProperty] = 1; - Assert.False(entry.HasTemporaryValue(keyProperty)); - Assert.False(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Added); - Assert.False(entry.HasTemporaryValue(keyProperty)); - Assert.False(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry.SetTemporaryValue(keyProperty, 1); + entry.SetTemporaryValue(keyProperty, 1); - Assert.True(entry.HasTemporaryValue(keyProperty)); - Assert.False(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry.SetTemporaryValue(nonKeyProperty, "Temp"); - entry[keyProperty] = 1; + entry.SetTemporaryValue(nonKeyProperty, "Temp"); + entry[keyProperty] = 1; - Assert.False(entry.HasTemporaryValue(keyProperty)); - Assert.True(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - entry[nonKeyProperty] = "I Am A Real Person!"; + entry[nonKeyProperty] = "I Am A Real Person!"; - entry.SetEntityState(EntityState.Unchanged); + entry.SetEntityState(EntityState.Unchanged); - Assert.False(entry.HasTemporaryValue(keyProperty)); - Assert.False(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); - // Can't change the key... - Assert.Throws(() => entry.SetTemporaryValue(keyProperty, -1)); - entry.SetTemporaryValue(nonKeyProperty, "Temp"); + // Can't change the key... + Assert.Throws(() => entry.SetTemporaryValue(keyProperty, -1)); + entry.SetTemporaryValue(nonKeyProperty, "Temp"); - Assert.True(entry.HasTemporaryValue(keyProperty)); - Assert.True(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.True(entry.IsModified(nonKeyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.True(entry.IsModified(nonKeyProperty)); - entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Added); - Assert.True(entry.HasTemporaryValue(keyProperty)); - Assert.True(entry.HasTemporaryValue(nonKeyProperty)); - Assert.False(entry.IsModified(keyProperty)); - Assert.False(entry.IsModified(nonKeyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(nonKeyProperty)); + Assert.False(entry.IsModified(keyProperty)); + Assert.False(entry.IsModified(nonKeyProperty)); + } } [ConditionalTheory] @@ -238,1354 +248,883 @@ public virtual void Added_entities_can_have_temporary_values() [InlineData(EntityState.Deleted)] public virtual void Changing_state_with_temp_value_throws(EntityState targetState) { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - entry.SetEntityState(EntityState.Added); - entry.SetTemporaryValue(keyProperty, -1); + entry.SetEntityState(EntityState.Added); + entry.SetTemporaryValue(keyProperty, -1); - Assert.Equal( - CoreStrings.TempValuePersists("Id", entityType.DisplayName(), targetState.ToString()), - Assert.Throws(() => entry.SetEntityState(targetState)).Message); + Assert.Equal( + CoreStrings.TempValuePersists("Id", "SomeEntity", targetState.ToString()), + Assert.Throws(() => entry.SetEntityState(targetState)).Message); + } } [ConditionalFact] public virtual void Detaching_with_temp_values_does_not_throw() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - entry[keyProperty] = 1; + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - entry.SetEntityState(EntityState.Added); - entry.SetTemporaryValue(keyProperty, -1); + entry[keyProperty] = 1; + entry.SetEntityState(EntityState.Added); + entry.SetTemporaryValue(keyProperty, -1); - Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); - entry.SetEntityState(EntityState.Detached); + entry.SetEntityState(EntityState.Detached); - Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); - entry[keyProperty] = 1; - entry.SetEntityState(EntityState.Unchanged); + entry[keyProperty] = 1; + entry.SetEntityState(EntityState.Unchanged); - Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); + } } [ConditionalFact] public virtual void Setting_an_explicit_value_marks_property_as_not_temporary() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - entry.SetEntityState(EntityState.Added); - entry.SetTemporaryValue(keyProperty, -1); + entry.SetEntityState(EntityState.Added); + entry.SetTemporaryValue(keyProperty, -1); - Assert.True(entry.HasTemporaryValue(keyProperty)); + Assert.True(entry.HasTemporaryValue(keyProperty)); - entry[keyProperty] = 77; + entry[keyProperty] = 77; - Assert.False(entry.HasTemporaryValue(keyProperty)); + Assert.False(entry.HasTemporaryValue(keyProperty)); - entry.SetEntityState(EntityState.Unchanged); // Does not throw + entry.SetEntityState(EntityState.Unchanged); // Does not throw + } } [ConditionalFact] public virtual void Key_properties_share_value_generation_space_with_base() { - var model = BuildModel(finalize: false); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var baseEntityType = model.FindEntityType(typeof(SomeSimpleEntityBase).FullName); - var altKeyProperty = baseEntityType.AddProperty("NonId", typeof(int)); - altKeyProperty.ValueGenerated = ValueGenerated.OnAdd; - baseEntityType.AddKey(altKeyProperty); - model.FinalizeModel(); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - - Assert.Equal(0, entry[keyProperty]); - - Assert.Equal(0, entry[altKeyProperty]); - - entry.SetEntityState(EntityState.Added); - - Assert.NotNull(entry[keyProperty]); - Assert.NotEqual(0, entry[keyProperty]); - Assert.Equal(entry[keyProperty], entry[altKeyProperty]); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); + var altKeyProperty = entry.EntityType.FindProperty("NonId"); - var baseEntry = CreateInternalEntry(configuration, baseEntityType, new SomeSimpleEntityBase()); + Assert.NotEqual(0, entry[keyProperty]); + Assert.Equal(entry[keyProperty], entry[altKeyProperty]); - baseEntry.SetEntityState(EntityState.Added); + var baseEntry = context.Add(new TSomeSimpleEntityBase()).GetInfrastructure(); - Assert.NotNull(baseEntry[keyProperty]); - Assert.NotEqual(0, baseEntry[keyProperty]); - Assert.Equal(baseEntry[keyProperty], baseEntry[altKeyProperty]); - Assert.NotEqual(entry[keyProperty], baseEntry[keyProperty]); - Assert.NotEqual(entry[altKeyProperty], baseEntry[altKeyProperty]); + Assert.NotNull(baseEntry[keyProperty]); + Assert.NotEqual(0, baseEntry[keyProperty]); + Assert.Equal(baseEntry[keyProperty], baseEntry[altKeyProperty]); + Assert.NotEqual(entry[keyProperty], baseEntry[keyProperty]); + Assert.NotEqual(entry[altKeyProperty], baseEntry[altKeyProperty]); + } } [ConditionalFact] public virtual void Value_generation_does_not_happen_if_property_has_non_default_value() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); - entry[keyProperty] = 31143; + entry[keyProperty] = 31143; - entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Added); - Assert.Equal(31143, entry[keyProperty]); + Assert.Equal(31143, entry[keyProperty]); + } } [ConditionalFact] - public virtual void Temporary_values_are_npt_reset_when_entity_is_detached() + public virtual void Modified_values_are_reset_when_entity_is_changed_to_Added() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entity = new SomeEntity(); - var entry = CreateInternalEntry(configuration, entityType, entity); - - entry.SetEntityState(EntityState.Added); - entry.SetTemporaryValue(keyProperty, -1); - - Assert.NotNull(entry[keyProperty]); - Assert.Equal(0, entity.Id); - Assert.Equal(-1, entry[keyProperty]); + using (var context = new TKContext()) + { + var entry = context.Update(new TSomeEntity()).GetInfrastructure(); + var property = entry.EntityType.FindProperty("Name"); - entry.SetEntityState(EntityState.Detached); + entry[entry.EntityType.FindProperty("Id")] = 1; - Assert.Equal(0, entity.Id); - Assert.Equal(-1, entry[keyProperty]); + entry.SetEntityState(EntityState.Modified); + entry.SetPropertyModified(property); - entry.SetEntityState(EntityState.Added); + entry.SetEntityState(EntityState.Added); - Assert.Equal(0, entity.Id); - Assert.Equal(-1, entry[keyProperty]); + Assert.False(entry.HasTemporaryValue(property)); + } } [ConditionalFact] - public virtual void Modified_values_are_reset_when_entity_is_changed_to_Added() + public virtual void Changing_state_to_Added_triggers_value_generation_for_any_property() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var property = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - entry[entityType.FindProperty("Id")] = 1; + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeDependentEntity()).GetInfrastructure(); - entry.SetEntityState(EntityState.Modified); - entry.SetPropertyModified(property); + var entityType = entry.EntityType; + entry[entityType.FindProperty("Id1")] = 77; + entry[entityType.FindProperty("Id2")] = "Ready Salted"; + entry.SetEntityState(EntityState.Added); - entry.SetEntityState(EntityState.Added); + var property = entityType.FindProperty("JustAProperty"); - Assert.False(entry.HasTemporaryValue(property)); + Assert.NotEqual(0, entry[property]); + } } [ConditionalFact] - public virtual void Changing_state_to_Added_triggers_value_generation_for_any_property() + public virtual void Notification_that_an_FK_property_has_changed_updates_the_snapshot() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeDependentEntity).FullName); - var keyProperties = new[] { entityType.FindProperty("Id1"), entityType.FindProperty("Id2") }; - var fkProperty = entityType.FindProperty("SomeEntityId"); - var property = entityType.FindProperty("JustAProperty"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeDependentEntity()).GetInfrastructure(); - var entry = CreateInternalEntry(configuration, entityType, new SomeDependentEntity()); - entry[keyProperties[0]] = 77; - entry[keyProperties[1]] = "ReadySalted"; - entry[fkProperty] = 0; + var entityType = entry.EntityType; + entry[entityType.FindProperty("Id1")] = 77; + entry[entityType.FindProperty("Id2")] = "Ready Salted"; + entry.SetEntityState(EntityState.Added); - Assert.Equal(0, entry[property]); + var fkProperty = entityType.FindProperty("SomeEntityId"); - entry.SetEntityState(EntityState.Added); + entry[fkProperty] = 77; + entry.SetRelationshipSnapshotValue(fkProperty, 78); - Assert.NotNull(entry[property]); - Assert.NotEqual(0, entry[property]); - } + entry[fkProperty] = 79; - [ConditionalFact] - public virtual void Notification_that_an_FK_property_has_changed_updates_the_snapshot() - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeDependentEntity).FullName); - var fkProperty = entityType.FindProperty("SomeEntityId"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeDependentEntity()); - entry[entityType.FindProperty("Id1")] = 66; - entry[entityType.FindProperty("Id2")] = "Bar"; - entry.SetEntityState(EntityState.Added); - entry[fkProperty] = 77; - entry.SetRelationshipSnapshotValue(fkProperty, 78); - - entry[fkProperty] = 79; - - var keyValue = entry.GetRelationshipSnapshotValue(entityType.GetForeignKeys().Single().Properties.Single()); - Assert.Equal(79, keyValue); + var keyValue = entry.GetRelationshipSnapshotValue(entityType.GetForeignKeys().Single().Properties.Single()); + Assert.Equal(79, keyValue); + } } [ConditionalFact] public virtual void Setting_property_to_the_same_value_does_not_update_the_snapshot() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeDependentEntity).FullName); - var fkProperty = entityType.FindProperty("SomeEntityId"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeDependentEntity()).GetInfrastructure(); - var entry = CreateInternalEntry(configuration, entityType, new SomeDependentEntity()); - entry[fkProperty] = 77; - entry.SetRelationshipSnapshotValue(fkProperty, 78); + var entityType = entry.EntityType; + entry[entityType.FindProperty("Id1")] = 77; + entry[entityType.FindProperty("Id2")] = "Ready Salted"; + entry.SetEntityState(EntityState.Unchanged); - entry[fkProperty] = 77; + var fkProperty = entityType.FindProperty("SomeEntityId"); - var keyValue = entry.GetRelationshipSnapshotValue(entityType.GetForeignKeys().Single().Properties.Single()); - Assert.Equal(78, keyValue); - } + entry[fkProperty] = 77; + entry.SetRelationshipSnapshotValue(fkProperty, 78); - [ConditionalFact] - public virtual void Can_get_property_value_after_creation_from_value_buffer() - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - Assert.Equal(1, entry[keyProperty]); - } + entry[fkProperty] = 77; - [ConditionalFact] - public virtual void Can_set_property_value_after_creation_from_value_buffer() - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry[nameProperty] = "Mule"; - - Assert.Equal("Mule", entry[nameProperty]); + var keyValue = entry.GetRelationshipSnapshotValue(entityType.GetForeignKeys().Single().Properties.Single()); + Assert.Equal(78, keyValue); + } } [ConditionalFact] - public virtual void Can_set_and_get_property_values() + public virtual void Can_get_property_value_after_creation_from_value_buffer() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - - entry[keyProperty] = 77; - entry[nonKeyProperty] = "Magic Tree House"; + using (var context = new TKContext()) + { + var stateManager = context.GetService(); + var entityType = context.Model.FindEntityType(typeof(TSomeEntity)); - Assert.Equal(77, entry[keyProperty]); - Assert.Equal("Magic Tree House", entry[nonKeyProperty]); + var entry = stateManager.CreateEntry( + new Dictionary + { + { + "Id", 1 + }, + { + "Name", "Kool" + } + }, + entityType + ); + + var keyProperty = entityType.FindProperty("Id"); + var property = entityType.FindProperty("Name"); + + Assert.Equal(1, entry[keyProperty]); + Assert.Equal("Kool", entry[property]); + } } [ConditionalFact] - public virtual void Can_set_and_get_property_values_genericly() + public virtual void Can_set_property_value_after_creation_from_value_buffer() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); - - entry[keyProperty] = 77; - entry[nonKeyProperty] = "Magic Tree House"; + using (var context = new TKContext()) + { + var stateManager = context.GetService(); + var entityType = context.Model.FindEntityType(typeof(TSomeEntity)); - Assert.Equal(77, entry.GetCurrentValue(keyProperty)); - Assert.Equal("Magic Tree House", entry.GetCurrentValue(nonKeyProperty)); + var entry = stateManager.CreateEntry( + new Dictionary + { + { + "Id", 1 + }, + { + "Name", "Kool" + } + }, + entityType + ); + + var nameProperty = entityType.FindProperty("Name"); + entry[nameProperty] = "Mule"; + + Assert.Equal("Mule", entry[nameProperty]); + } } [ConditionalFact] public virtual void Can_get_value_buffer_from_properties() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.FindProperty("Id"); - var nonKeyProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeEntity()); + using (var context = new TKContext()) + { + var entry = context.Add(new TSomeEntity()).GetInfrastructure(); + var keyProperty = entry.EntityType.FindProperty("Id"); + var nonKeyProperty = entry.EntityType.FindProperty("Name"); - entry[keyProperty] = 77; - entry[nonKeyProperty] = "Magic Tree House"; + entry[keyProperty] = 77; + entry[nonKeyProperty] = "Magic Tree House"; - Assert.Equal(new object[] { 77, "Magic Tree House" }, CreateValueBuffer(entry)); + Assert.Equal( + new object[] + { + 77, "SomeEntity", 1, "Magic Tree House" + }, + CreateValueBuffer(entry)); + } } private static object[] CreateValueBuffer(IUpdateEntry entry) => entry.EntityType.GetProperties().Select(entry.GetCurrentValue).ToArray(); - [ConditionalFact] - public virtual void All_original_values_can_be_accessed_for_entity_that_does_full_change_tracking_if_eager_values_on() - { - var model = BuildModel(finalize: false); - var entityType = model.FindEntityType(typeof(FullNotificationEntity).FullName); - entityType.SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - model.FinalizeModel(); - - AllOriginalValuesTest( - model, entityType, new FullNotificationEntity - { - Id = 1, - Name = "Kool" - }); - } - - protected void AllOriginalValuesTest(IModel model, IEntityType entityType, object entity) + protected void AllOriginalValuesTest(object entity) { - var idProperty = entityType.FindProperty("Id"); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - entity, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry.SetEntityState(EntityState.Unchanged); + using (var context = new TKSnapContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + var idProperty = entry.EntityType.FindProperty("Id"); + var nameProperty = entry.EntityType.FindProperty("Name"); - Assert.Equal(1, entry.GetOriginalValue(idProperty)); - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal(1, entry[idProperty]); - Assert.Equal("Kool", entry[nameProperty]); + entry[idProperty] = 1; + entry[nameProperty] = "Kool"; + entry.SetEntityState(EntityState.Unchanged); - entry[nameProperty] = "Beans"; + Assert.Equal(1, entry.GetOriginalValue(idProperty)); + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal(1, entry[idProperty]); + Assert.Equal("Kool", entry[nameProperty]); - Assert.Equal(1, entry.GetOriginalValue(idProperty)); - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal(1, entry[idProperty]); - Assert.Equal("Beans", entry[nameProperty]); + entry[nameProperty] = "Beans"; - entry.SetOriginalValue(nameProperty, "Franks"); + Assert.Equal(1, entry.GetOriginalValue(idProperty)); + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal(1, entry[idProperty]); + Assert.Equal("Beans", entry[nameProperty]); - Assert.Equal(1, entry.GetOriginalValue(idProperty)); - Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); - Assert.Equal(1, entry[idProperty]); - Assert.Equal("Beans", entry[nameProperty]); - } + entry.SetOriginalValue(nameProperty, "Franks"); - [ConditionalFact] - public virtual void Required_original_values_can_be_accessed_for_entity_that_does_full_change_tracking() - { - var model = BuildModel(); - OriginalValuesTest( - model, model.FindEntityType(typeof(FullNotificationEntity).FullName), new FullNotificationEntity - { - Id = 1, - Name = "Kool" - }); - } - - [ConditionalFact] - public virtual void Required_original_values_can_be_accessed_for_entity_that_does_changed_only_notification() - { - var model = BuildModel(); - OriginalValuesTest( - model, model.FindEntityType(typeof(ChangedOnlyEntity).FullName), new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }); + Assert.Equal(1, entry.GetOriginalValue(idProperty)); + Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); + Assert.Equal(1, entry[idProperty]); + Assert.Equal("Beans", entry[nameProperty]); + } } [ConditionalFact] public virtual void Required_original_values_can_be_accessed_for_entity_that_does_no_notification() - { - var model = BuildModel(); - OriginalValuesTest( - model, model.FindEntityType(typeof(SomeEntity).FullName), new SomeEntity - { - Id = 1, - Name = "Kool" - }); - } + => OriginalValuesTest(new TSomeEntity()); - protected void OriginalValuesTest(IModel model, IEntityType entityType, object entity) + protected void OriginalValuesTest(object entity) { - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, "Kool" })); - entry.SetEntityState(EntityState.Unchanged); - - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Kool", entry[nameProperty]); + using (var context = new TKContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + entry[entry.EntityType.FindProperty("Id")] = 1; + var nameProperty = entry.EntityType.FindProperty("Name"); + entry[nameProperty] = "Kool"; + entry.SetEntityState(EntityState.Unchanged); - entry[nameProperty] = "Beans"; + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Kool", entry[nameProperty]); - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry[nameProperty]); + entry[nameProperty] = "Beans"; - entry.SetOriginalValue(nameProperty, "Franks"); + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry[nameProperty]); - Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry[nameProperty]); - } + entry.SetOriginalValue(nameProperty, "Franks"); - [ConditionalFact] - public virtual void Required_original_values_can_be_accessed_generically_for_entity_that_does_full_change_tracking() - { - var model = BuildModel(); - GenericOriginalValuesTest( - model, model.FindEntityType(typeof(FullNotificationEntity).FullName), new FullNotificationEntity - { - Id = 1, - Name = "Kool" - }); - } - - [ConditionalFact] - public virtual void Required_original_values_can_be_accessed_generically_for_entity_that_does_changed_only_notification() - { - var model = BuildModel(); - GenericOriginalValuesTest( - model, model.FindEntityType(typeof(ChangedOnlyEntity).FullName), new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }); + Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry[nameProperty]); + } } [ConditionalFact] public virtual void Required_original_values_can_be_accessed_generically_for_entity_that_does_no_notification() - { - var model = BuildModel(); - GenericOriginalValuesTest( - model, model.FindEntityType(typeof(SomeEntity).FullName), new SomeEntity - { - Id = 1, - Name = "Kool" - }); - } + => GenericOriginalValuesTest(new TSomeEntity()); - protected void GenericOriginalValuesTest(IModel model, IEntityType entityType, object entity) + protected void GenericOriginalValuesTest(object entity) { - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, "Kool" })); - entry.SetEntityState(EntityState.Unchanged); - - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Kool", entry.GetCurrentValue(nameProperty)); + using (var context = new TKContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + var idProperty = entry.EntityType.FindProperty("Id"); + var nameProperty = entry.EntityType.FindProperty("Name"); - entry[nameProperty] = "Beans"; + entry[idProperty] = 77; + entry[nameProperty] = "Kool"; + entry.SetEntityState(EntityState.Unchanged); - Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Kool", entry.GetCurrentValue(nameProperty)); - entry.SetOriginalValue(nameProperty, "Franks"); + entry[nameProperty] = "Beans"; - Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); - } + Assert.Equal("Kool", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); - [ConditionalFact] - public virtual void Null_original_values_are_handled_for_entity_that_does_full_change_tracking() - { - var model = BuildModel(); - NullOriginalValuesTest( - model, model.FindEntityType(typeof(FullNotificationEntity).FullName), new FullNotificationEntity - { - Id = 1 - }); - } + entry.SetOriginalValue(nameProperty, "Franks"); - [ConditionalFact] - public virtual void Null_original_values_are_handled_for_entity_that_does_changed_only_notification() - { - var model = BuildModel(); - NullOriginalValuesTest( - model, model.FindEntityType(typeof(ChangedOnlyEntity).FullName), new ChangedOnlyEntity - { - Id = 1 - }); + Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + } } [ConditionalFact] public virtual void Null_original_values_are_handled_for_entity_that_does_no_notification() - { - var model = BuildModel(); - NullOriginalValuesTest( - model, model.FindEntityType(typeof(SomeEntity).FullName), new SomeEntity - { - Id = 1 - }); - } + => NullOriginalValuesTest(new TSomeEntity()); - protected void NullOriginalValuesTest(IModel model, IEntityType entityType, object entity) + protected void NullOriginalValuesTest(object entity) { - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, null })); - entry.SetEntityState(EntityState.Unchanged); - - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Null(entry[nameProperty]); + using (var context = new TKContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + var idProperty = entry.EntityType.FindProperty("Id"); + var nameProperty = entry.EntityType.FindProperty("Name"); - entry[nameProperty] = "Beans"; + entry[idProperty] = 77; + entry.SetEntityState(EntityState.Unchanged); - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry[nameProperty]); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Null(entry[nameProperty]); - entry.SetOriginalValue(nameProperty, "Franks"); + entry[nameProperty] = "Beans"; - Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry[nameProperty]); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry[nameProperty]); - entry.SetOriginalValue(nameProperty, null); + entry.SetOriginalValue(nameProperty, "Franks"); - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry[nameProperty]); - } + Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry[nameProperty]); - [ConditionalFact] - public virtual void Null_original_values_are_handled_generically_for_entity_that_does_full_change_tracking() - { - var model = BuildModel(); - GenericNullOriginalValuesTest( - model, model.FindEntityType(typeof(FullNotificationEntity).FullName), new FullNotificationEntity - { - Id = 1 - }); - } + entry.SetOriginalValue(nameProperty, null); - [ConditionalFact] - public virtual void Null_original_values_are_handled_generically_for_entity_that_does_changed_only_notification() - { - var model = BuildModel(); - GenericNullOriginalValuesTest( - model, model.FindEntityType(typeof(ChangedOnlyEntity).FullName), new ChangedOnlyEntity - { - Id = 1 - }); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry[nameProperty]); + } } [ConditionalFact] public virtual void Null_original_values_are_handled_generically_for_entity_that_does_no_notification() - { - var model = BuildModel(); - GenericNullOriginalValuesTest( - model, model.FindEntityType(typeof(SomeEntity).FullName), new SomeEntity - { - Id = 1 - }); - } + => GenericNullOriginalValuesTest(new TSomeEntity()); - protected void GenericNullOriginalValuesTest(IModel model, IEntityType entityType, object entity) + protected void GenericNullOriginalValuesTest(object entity) { - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + var idProperty = entry.EntityType.FindProperty("Id"); + var nameProperty = entry.EntityType.FindProperty("Name"); - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, null })); - entry.SetEntityState(EntityState.Unchanged); + entry[idProperty] = 77; + entry.SetEntityState(EntityState.Unchanged); - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Null(entry.GetCurrentValue(nameProperty)); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Null(entry.GetCurrentValue(nameProperty)); - entry[nameProperty] = "Beans"; + entry[nameProperty] = "Beans"; - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); - entry.SetOriginalValue(nameProperty, "Franks"); + entry.SetOriginalValue(nameProperty, "Franks"); - Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + Assert.Equal("Franks", entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); - entry.SetOriginalValue(nameProperty, null); + entry.SetOriginalValue(nameProperty, null); - Assert.Null(entry.GetOriginalValue(nameProperty)); - Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + Assert.Null(entry.GetOriginalValue(nameProperty)); + Assert.Equal("Beans", entry.GetCurrentValue(nameProperty)); + } } [ConditionalFact] - public virtual void Setting_property_using_state_entry_always_marks_as_modified() - { - var model = BuildModel(); - - SetPropertyInternalEntityEntryTest( - model, model.FindEntityType(typeof(FullNotificationEntity).FullName), new FullNotificationEntity - { - Id = 1, - Name = "Kool" - }); - SetPropertyInternalEntityEntryTest( - model, model.FindEntityType(typeof(ChangedOnlyEntity).FullName), new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }); - SetPropertyInternalEntityEntryTest( - model, model.FindEntityType(typeof(SomeEntity).FullName), new SomeEntity - { - Id = 1, - Name = "Kool" - }); - } + public virtual void Setting_property_using_state_entry_always_marks_as_modified_no_notifications() + => SetPropertyInternalEntityEntryTest(new TSomeEntity()); - protected void SetPropertyInternalEntityEntryTest(IModel model, IEntityType entityType, object entity) + protected void SetPropertyInternalEntityEntryTest(object entity) { - var idProperty = entityType.FindProperty("Id"); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, "Kool" })); - entry.SetEntityState(EntityState.Unchanged); - - Assert.False(entry.IsModified(idProperty)); - Assert.False(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Unchanged, entry.EntityState); - - entry[idProperty] = 1; - entry[nameProperty] = "Kool"; - - Assert.False(entry.IsModified(idProperty)); - Assert.False(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Unchanged, entry.EntityState); - - entry[nameProperty] = "Beans"; - - Assert.False(entry.IsModified(idProperty)); - Assert.True(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Modified, entry.EntityState); - } - - protected void SetPropertyClrTest(TEntity entity, bool needsDetectChanges) - where TEntity : ISomeEntity - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(TEntity)); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, entity, new ValueBuffer(new object[] { 1, "Kool" })); - entry.SetEntityState(EntityState.Unchanged); - - Assert.False(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Unchanged, entry.EntityState); - - entity.Name = "Kool"; - - Assert.False(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Unchanged, entry.EntityState); + using (var context = new TKContext()) + { + var entry = context.Entry(entity).GetInfrastructure(); + var idProperty = entry.EntityType.FindProperty("Id"); + var nameProperty = entry.EntityType.FindProperty("Name"); - entity.Name = "Beans"; + entry[idProperty] = 77; + entry[nameProperty] = "Kool"; + entry.SetEntityState(EntityState.Unchanged); - if (needsDetectChanges) - { + Assert.False(entry.IsModified(idProperty)); Assert.False(entry.IsModified(nameProperty)); Assert.Equal(EntityState.Unchanged, entry.EntityState); - configuration.GetRequiredService().DetectChanges(entry); - } - - Assert.True(entry.IsModified(nameProperty)); - Assert.Equal(EntityState.Modified, entry.EntityState); - } - - [ConditionalFact] - public virtual void AcceptChanges_does_nothing_for_unchanged_entities() - => AcceptChangesNoop(EntityState.Unchanged); - - [ConditionalFact] - public virtual void AcceptChanges_does_nothing_for_unknown_entities() - => AcceptChangesNoop(EntityState.Detached); - - private void AcceptChangesNoop(EntityState entityState) - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); + entry[nameProperty] = "Kool"; - entry.SetEntityState(entityState); - - entry.AcceptChanges(); + Assert.False(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.Equal(entityState, entry.EntityState); - } + entry[nameProperty] = "Beans"; - [ConditionalFact] - public virtual void AcceptChanges_makes_Modified_entities_Unchanged_and_resets_used_original_values() - { - AcceptChangesKeep(EntityState.Modified); + Assert.True(entry.IsModified(nameProperty)); + Assert.Equal(EntityState.Modified, entry.EntityState); + } } [ConditionalFact] - public virtual void AcceptChanges_makes_Added_entities_Unchanged() - { - AcceptChangesKeep(EntityState.Added); - } - - private void AcceptChangesKeep(EntityState entityState) + public void Can_get_entity() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry.SetEntityState(entityState); - - entry[nameProperty] = "Pickle"; - entry.SetOriginalValue(nameProperty, "Cheese"); - - entry.AcceptChanges(); - - Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.Equal("Pickle", entry[nameProperty]); - Assert.Equal("Pickle", entry.GetOriginalValue(nameProperty)); - } + using (var context = new TKContext()) + { + var entity = new TSomeEntity(); + var entry = context.Attach(entity).GetInfrastructure(); - [ConditionalFact] - public virtual void AcceptChanges_makes_Modified_entities_Unchanged_and_effectively_resets_unused_original_values() - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var nameProperty = entityType.FindProperty("Name"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry.SetEntityState(EntityState.Modified); - - entry[nameProperty] = "Pickle"; - - entry.AcceptChanges(); - - Assert.Equal(EntityState.Unchanged, entry.EntityState); - Assert.Equal("Pickle", entry[nameProperty]); - Assert.Equal("Pickle", entry.GetOriginalValue(nameProperty)); + Assert.Same(entity, entry.Entity); + } } [ConditionalFact] - public virtual void AcceptChanges_detaches_Deleted_entities() + public void Can_set_and_get_property_value_from_CLR_object() { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + using (var context = new TKContext()) + { + var entity = new TSomeEntity(); + var entry = context.Entry(entity).GetInfrastructure(); + var entityType = entry.EntityType; + var keyProperty = entityType.FindProperty("Id"); + var nonKeyProperty = entityType.FindProperty("Name"); - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); + entry[keyProperty] = 77; + entry[nonKeyProperty] = "Magic Tree House"; + entry.SetEntityState(EntityState.Added); - entry.SetEntityState(EntityState.Deleted); + Assert.Equal(77, entry[keyProperty]); + Assert.Equal("Magic Tree House", entry[nonKeyProperty]); - entry.AcceptChanges(); + entry[keyProperty] = 78; + entry[nonKeyProperty] = "Normal Tree House"; - Assert.Equal(EntityState.Detached, entry.EntityState); + Assert.Equal(78, entry[keyProperty]); + Assert.Equal("Normal Tree House", entry[nonKeyProperty]); + } } [ConditionalFact] - public virtual void AcceptChanges_does_nothing_for_unchanged_owned_entities() - => AcceptChangesOwned(EntityState.Unchanged); - - [ConditionalFact] - public virtual void AcceptChanges_does_nothing_for_unknown_owned_entities() - => AcceptChangesOwned(EntityState.Detached); - - [ConditionalFact] - public virtual void AcceptChanges_makes_Modified_owned_entities_Unchanged_and_resets_used_original_values() - => AcceptChangesOwned(EntityState.Modified); + public void All_original_values_can_be_accessed_for_entity_that_does_no_notification() + => AllOriginalValuesTest(new TSomeEntity()); [ConditionalFact] - public virtual void AcceptChanges_makes_Added_owned_entities_Unchanged() - => AcceptChangesOwned(EntityState.Added); + public virtual void AcceptChanges_does_nothing_for_unchanged_entities() + => AcceptChangesNoop(EntityState.Unchanged); [ConditionalFact] - public virtual void AcceptChanges_detaches_Deleted_owned_entities() - => AcceptChangesOwned(EntityState.Deleted); + public virtual void AcceptChanges_does_nothing_for_unknown_entities() + => AcceptChangesNoop(EntityState.Detached); - private void AcceptChangesOwned(EntityState entityState) + private void AcceptChangesNoop(EntityState entityState) { - var model = BuildModel(); - var ownerType = model.FindEntityType(typeof(OwnerClass).FullName); - var ownedType = ownerType.FindNavigation(nameof(OwnerClass.Owned)).GetTargetType(); - var valueProperty = ownedType.FindProperty(nameof(OwnedClass.Value)); - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - contextServices, - ownedType, - new OwnedClass - { - Value = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry.SetEntityState(entityState); - - if (entityState != EntityState.Unchanged) + using (var context = new TKContext()) { - entry[valueProperty] = "Pickle"; - } + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var entityType = entry.EntityType; + var keyProperty = entityType.FindProperty("Id"); + var nonKeyProperty = entityType.FindProperty("Name"); + entry[keyProperty] = 1; + entry[nonKeyProperty] = "Kool"; - entry.SetOriginalValue(valueProperty, "Cheese"); + entry.SetEntityState(entityState); - entry.AcceptChanges(); + entry.AcceptChanges(); - Assert.Equal( - entityState == EntityState.Deleted || entityState == EntityState.Detached ? EntityState.Detached : EntityState.Unchanged, - entry.EntityState); - if (entityState == EntityState.Unchanged) - { - Assert.Equal("Kool", entry[valueProperty]); - Assert.Equal("Kool", entry.GetOriginalValue(valueProperty)); - } - else - { - Assert.Equal("Pickle", entry[valueProperty]); - Assert.Equal( - entityState == EntityState.Detached || entityState == EntityState.Deleted ? "Cheese" : "Pickle", - entry.GetOriginalValue(valueProperty)); + Assert.Equal(entityState, entry.EntityState); } } - [ConditionalFact] - public virtual void Non_transparent_sidecar_does_not_intercept_normal_property_read_and_write() + [ConditionalTheory] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Added)] + public void AcceptChanges_makes_entities_Unchanged(EntityState entityState) { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var idProperty = entityType.FindProperty("Id"); - var nameProperty = entityType.FindProperty("Name"); - - var entry = CreateInternalEntry( - InMemoryTestHelpers.Instance.CreateContextServices(model), - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - entry.SetEntityState(EntityState.Added); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var entityType = entry.EntityType; + var keyProperty = entityType.FindProperty("Id"); + var nameProperty = entityType.FindProperty("Name"); - Assert.Equal(1, entry[idProperty]); - Assert.Equal("Kool", entry[nameProperty]); + entry[keyProperty] = 1; + entry[nameProperty] = "Kool"; - entry.SetOriginalValue(idProperty, 7); + entry.SetEntityState(entityState); - Assert.Equal(1, entry[idProperty]); - Assert.Equal("Kool", entry[nameProperty]); + entry[nameProperty] = "Pickle"; + entry.SetOriginalValue(nameProperty, "Cheese"); - entry[idProperty] = 77; + entry.AcceptChanges(); - Assert.Equal(77, entry[idProperty]); - Assert.Equal("Kool", entry[nameProperty]); + Assert.Equal(EntityState.Unchanged, entry.EntityState); + Assert.Equal("Pickle", entry[nameProperty]); + Assert.Equal("Pickle", entry.GetOriginalValue(nameProperty)); + } } - private static IModel BuildOneToOneModel() + [ConditionalFact] + public virtual void AcceptChanges_makes_Modified_entities_Unchanged_and_effectively_resets_unused_original_values() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var entityType = entry.EntityType; + var keyProperty = entityType.FindProperty("Id"); + var nameProperty = entityType.FindProperty("Name"); - modelBuilder - .Entity() - .HasOne(e => e.Second) - .WithOne(e => e.First) - .HasForeignKey(e => e.Id) - .OnDelete(DeleteBehavior.Cascade); + entry[keyProperty] = 1; + entry[nameProperty] = "Kool"; - modelBuilder - .Entity( - b => - { - b.Property(e => e.Id).ValueGeneratedNever(); + entry.SetEntityState(EntityState.Modified); - b.HasOne(e => e.First) - .WithOne(e => e.Root) - .HasForeignKey(e => e.Id); - }); + entry[nameProperty] = "Pickle"; + + entry.AcceptChanges(); - return modelBuilder.FinalizeModel(); + Assert.Equal(EntityState.Unchanged, entry.EntityState); + Assert.Equal("Pickle", entry[nameProperty]); + Assert.Equal("Pickle", entry.GetOriginalValue(nameProperty)); + } } - private static IModel BuildOneToOneCompositeModel(bool required) + [ConditionalFact] + public virtual void AcceptChanges_detaches_Deleted_entities() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + using (var context = new TKContext()) + { + var entry = context.Entry(new TSomeEntity()).GetInfrastructure(); + var entityType = entry.EntityType; + var keyProperty = entityType.FindProperty("Id"); + var nameProperty = entityType.FindProperty("Name"); - modelBuilder - .Entity() - .HasKey( - e => new - { - e.Id1, - e.Id2 - }); + entry[keyProperty] = 1; + entry[nameProperty] = "Kool"; - modelBuilder - .Entity() - .HasKey( - e => new - { - e.Id1, - e.Id2 - }); + entry.SetEntityState(EntityState.Deleted); - modelBuilder - .Entity() - .HasKey( - e => new - { - e.Id1, - e.Id2 - }); - - modelBuilder - .Entity() - .HasOne(e => e.First) - .WithOne(e => e.Root) - .HasForeignKey( - e => new - { - e.RootId1, - e.RootId2 - }) - .IsRequired(required); - - modelBuilder - .Entity() - .HasOne(e => e.Second) - .WithOne(e => e.First) - .HasForeignKey( - e => new - { - e.FirstId1, - e.FirstId2 - }) - .IsRequired(required); + entry.AcceptChanges(); - return modelBuilder.FinalizeModel(); + Assert.Equal(EntityState.Detached, entry.EntityState); + } } [ConditionalFact] public void Unchanged_entity_with_conceptually_null_FK_with_cascade_delete_is_marked_Deleted() { - var model = BuildOneToOneModel(); - var entityType = model.FindEntityType(typeof(SecondDependent).FullName); - var fkProperty = entityType.FindProperty("Id"); - - var entry = CreateInternalEntry(InMemoryTestHelpers.Instance.CreateContextServices(model), entityType, new SecondDependent()); + using (var context = new KcContext()) + { + var entry = context.Entry(new TSecondDependent()).GetInfrastructure(); + var fkProperty = entry.EntityType.FindProperty("Id"); - entry[fkProperty] = 77; - entry.SetEntityState(EntityState.Unchanged); + entry[fkProperty] = 77; + entry.SetEntityState(EntityState.Unchanged); - entry[fkProperty] = null; - entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); + entry[fkProperty] = null; + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); - Assert.Equal(EntityState.Deleted, entry.EntityState); + Assert.Equal(EntityState.Deleted, entry.EntityState); + } } [ConditionalFact] public void Added_entity_with_conceptually_null_FK_with_cascade_delete_is_detached() { - var model = BuildOneToOneModel(); - var entityType = model.FindEntityType(typeof(SecondDependent).FullName); - var fkProperty = entityType.FindProperty("Id"); - - var entry = CreateInternalEntry(InMemoryTestHelpers.Instance.CreateContextServices(model), entityType, new SecondDependent()); + using (var context = new KcContext()) + { + var entry = context.Entry(new TSecondDependent()).GetInfrastructure(); + var fkProperty = entry.EntityType.FindProperty("Id"); - entry[fkProperty] = 77; - entry.SetEntityState(EntityState.Added); + entry[fkProperty] = 77; + entry.SetEntityState(EntityState.Added); - entry[fkProperty] = null; - entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); + entry[fkProperty] = null; + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); - Assert.Equal(EntityState.Detached, entry.EntityState); + Assert.Equal(EntityState.Detached, entry.EntityState); + } } [ConditionalFact] public void Entity_with_partially_null_composite_FK_with_cascade_delete_is_marked_Deleted() { - var model = BuildOneToOneCompositeModel(required: true); - var entityType = model.FindEntityType(typeof(CompositeSecondDependent).FullName); - var fkProperty1 = entityType.FindProperty("FirstId1"); - var fkProperty2 = entityType.FindProperty("FirstId2"); - - var entry = CreateInternalEntry( - InMemoryTestHelpers.Instance.CreateContextServices(model), entityType, new CompositeSecondDependent()); + using (var context = new KcrContext()) + { + var entry = context.Entry(new TCompositeSecondDependent()).GetInfrastructure(); + var entityType = entry.EntityType; + var fkProperty1 = entityType.FindProperty("FirstId1"); + var fkProperty2 = entityType.FindProperty("FirstId2"); - entry[entityType.FindProperty("Id1")] = 66; - entry[entityType.FindProperty("Id2")] = "Bar"; - entry[fkProperty1] = 77; - entry[fkProperty2] = "Foo"; - entry.SetEntityState(EntityState.Unchanged); + entry[entityType.FindProperty("Id1")] = 66; + entry[entityType.FindProperty("Id2")] = "Bar"; + entry[fkProperty1] = 77; + entry[fkProperty2] = "Foo"; + entry.SetEntityState(EntityState.Unchanged); - entry[fkProperty1] = null; - entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); + entry[fkProperty1] = null; + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); - Assert.Equal(EntityState.Deleted, entry.EntityState); + Assert.Equal(EntityState.Deleted, entry.EntityState); + } } [ConditionalFact] public void Entity_with_partially_null_composite_FK_without_cascade_delete_is_orphaned() { - var model = BuildOneToOneCompositeModel(required: false); - var entityType = model.FindEntityType(typeof(CompositeSecondDependent).FullName); - var fkProperty1 = entityType.FindProperty("FirstId1"); - var fkProperty2 = entityType.FindProperty("FirstId2"); - - var entry = CreateInternalEntry( - InMemoryTestHelpers.Instance.CreateContextServices(model), entityType, new CompositeSecondDependent()); - - entry[entityType.FindProperty("Id1")] = 66; - entry[entityType.FindProperty("Id2")] = "Bar"; - entry[fkProperty1] = 77; - entry[fkProperty2] = "Foo"; - entry.SetEntityState(EntityState.Unchanged); - - entry[fkProperty1] = null; - entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); - - Assert.Equal(EntityState.Modified, entry.EntityState); - - Assert.Equal(77, entry[fkProperty1]); - Assert.Null(entry[fkProperty2]); - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class Root - { - public int Id { get; set; } - - public FirstDependent First { get; set; } - } - - // ReSharper disable once ClassNeverInstantiated.Local - private class FirstDependent - { - public int Id { get; set; } - - public Root Root { get; set; } + using (var context = new KcContext()) + { + var entry = context.Entry(new TCompositeSecondDependent()).GetInfrastructure(); + var entityType = entry.EntityType; + var fkProperty1 = entityType.FindProperty("FirstId1"); + var fkProperty2 = entityType.FindProperty("FirstId2"); - public SecondDependent Second { get; set; } - } + entry[entityType.FindProperty("Id1")] = 66; + entry[entityType.FindProperty("Id2")] = "Bar"; + entry[fkProperty1] = 77; + entry[fkProperty2] = "Foo"; + entry.SetEntityState(EntityState.Unchanged); - private class SecondDependent - { - public int Id { get; set; } + entry[fkProperty1] = null; + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); - public FirstDependent First { get; set; } - } + Assert.Equal(EntityState.Modified, entry.EntityState); - // ReSharper disable once ClassNeverInstantiated.Local - private class CompositeRoot - { - public int Id1 { get; set; } - public string Id2 { get; set; } - - public CompositeFirstDependent First { get; set; } + Assert.Equal(77, entry[fkProperty1]); + Assert.Null(entry[fkProperty2]); + } } - // ReSharper disable once ClassNeverInstantiated.Local - private class CompositeFirstDependent + public class KContext : DbContext { - public int Id1 { get; set; } - public string Id2 { get; set; } - - public int RootId1 { get; set; } - public string RootId2 { get; set; } - - // ReSharper disable once MemberHidesStaticFromOuterClass - public CompositeRoot Root { get; set; } - - public CompositeSecondDependent Second { get; set; } - } + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString()); - private class CompositeSecondDependent - { - public int Id1 { get; set; } - public string Id2 { get; set; } + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + b => + { + b.Property("Id"); + b.HasKey("Id"); + b.Property("NonId").ValueGeneratedOnAdd(); + b.HasAlternateKey("NonId"); + }); - public int FirstId1 { get; set; } - public string FirstId2 { get; set; } - public CompositeFirstDependent First { get; set; } - } + modelBuilder.Entity().Property("Name").IsConcurrencyToken().ValueGeneratedOnAdd(); - protected virtual InternalEntityEntry CreateInternalEntry( - IServiceProvider contextServices, IEntityType entityType, object entity) - => contextServices - .GetRequiredService() - .GetOrCreateEntry(entity, entityType); + modelBuilder.Entity( + b => + { + b.Property("Id1"); + b.Property("Id2"); + b.HasKey("Id1", "Id2"); + }); - protected virtual InternalEntityEntry CreateInternalEntry( - IServiceProvider contextServices, IEntityType entityType, object entity, in ValueBuffer valueBuffer) - => contextServices - .GetRequiredService() - .StartTrackingFromQuery(entityType, entity, valueBuffer, new HashSet()); + modelBuilder.Entity( + b => + { + b.Property("SomeEntityId"); + b.HasOne().WithMany().HasForeignKey("SomeEntityId"); + b.Property("JustAProperty").HasValueGenerator((p, e) => new InMemoryIntegerValueGenerator(p.GetIndex())); + }); - protected virtual IMutableModel BuildModel(bool finalize = true) - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - var model = modelBuilder.Model; - - var someSimpleEntityType = model.AddEntityType(typeof(SomeSimpleEntityBase)); - var simpleKeyProperty = someSimpleEntityType.AddProperty("Id", typeof(int)); - simpleKeyProperty.ValueGenerated = ValueGenerated.OnAdd; - someSimpleEntityType.SetPrimaryKey(simpleKeyProperty); - - var someCompositeEntityType = model.AddEntityType(typeof(SomeCompositeEntityBase)); - var compositeKeyProperty1 = someCompositeEntityType.AddProperty("Id1", typeof(int)); - var compositeKeyProperty2 = someCompositeEntityType.AddProperty("Id2", typeof(string)); - compositeKeyProperty2.IsNullable = false; - someCompositeEntityType.SetPrimaryKey(new[] { compositeKeyProperty1, compositeKeyProperty2 }); - - var entityType1 = model.AddEntityType(typeof(SomeEntity)); - entityType1.BaseType = someSimpleEntityType; - var property3 = entityType1.AddProperty("Name", typeof(string)); - property3.IsConcurrencyToken = true; - property3.ValueGenerated = ValueGenerated.OnAdd; - - var entityType2 = model.AddEntityType(typeof(SomeDependentEntity)); - entityType2.BaseType = someCompositeEntityType; - var fk = entityType2.AddProperty("SomeEntityId", typeof(int)); - entityType2.AddForeignKey(new[] { fk }, entityType1.FindPrimaryKey(), entityType1); - // TODO: declare this on the derived type - // #2611 - var justAProperty = someCompositeEntityType.AddProperty("JustAProperty", typeof(int)); - justAProperty.ValueGenerated = ValueGenerated.OnAdd; - someCompositeEntityType.AddKey(justAProperty); - - var entityType3 = model.AddEntityType(typeof(FullNotificationEntity)); - var property6 = entityType3.AddProperty("Id", typeof(int)); - entityType3.SetPrimaryKey(property6); - var property7 = entityType3.AddProperty("Name", typeof(string)); - property7.IsConcurrencyToken = true; - entityType3.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - - var entityType4 = model.AddEntityType(typeof(ChangedOnlyEntity)); - var property8 = entityType4.AddProperty("Id", typeof(int)); - entityType4.SetPrimaryKey(property8); - var property9 = entityType4.AddProperty("Name", typeof(string)); - property9.IsConcurrencyToken = true; - entityType4.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - - var entityType5 = model.AddEntityType(typeof(SomeMoreDependentEntity)); - entityType5.BaseType = someSimpleEntityType; - var fk5a = entityType5.AddProperty("Fk1", typeof(int)); - var fk5b = entityType5.AddProperty("Fk2", typeof(string)); - entityType5.AddForeignKey(new[] { fk5a, fk5b }, entityType2.FindPrimaryKey(), entityType2); - - modelBuilder.Entity( - eb => - { - eb.HasKey(e => e.Id); - var owned = eb.OwnsOne(e => e.Owned); - owned.WithOwner().HasForeignKey("Id"); - owned.HasKey("Id"); - owned.Property(e => e.Value); - }); - - return finalize ? (IMutableModel)model.FinalizeModel() : model; + modelBuilder.Entity( + b => + { + b.Property("Fk11"); + b.Property("Fk2"); + b.HasOne().WithMany().HasForeignKey("Fk1", "Fk2"); + }); + } } - protected interface ISomeEntity + public class KcContext : KContext { - int Id { get; set; } - string Name { get; set; } - } + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + b => + { + b.Property("Id"); + b.HasOne(e => (TSecondDependent)e.Second) + .WithOne(e => (TFirstDependent)e.First) + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade); + }); - protected class SomeSimpleEntityBase - { - public int Id { get; set; } - } + modelBuilder + .Entity( + b => + { + b.Property("Id").ValueGeneratedNever(); + b.HasOne(e => (TFirstDependent)e.First) + .WithOne(e => (TRoot)e.Root) + .HasForeignKey("Id"); + }); + + modelBuilder.Entity( + b => + { + b.Property("Id1"); + b.Property("Id2"); + b.HasKey("Id1", "Id2"); + }); - protected class SomeEntity : SomeSimpleEntityBase, ISomeEntity - { - public string Name { get; set; } - } + modelBuilder.Entity( + b => + { + b.Property("Id1"); + b.Property("Id2"); + b.Property("RootId1"); + b.Property("RootId2"); + b.HasKey("Id1", "Id2"); + }); - protected class SomeCompositeEntityBase - { - public int Id1 { get; set; } - public string Id2 { get; set; } - } + modelBuilder.Entity( + b => + { + b.Property("FirstId1"); + b.Property("FirstId2"); + b.Property("Id1"); + b.Property("Id2"); + b.HasKey("Id1", "Id2"); + }); - protected class SomeDependentEntity : SomeCompositeEntityBase - { - public int SomeEntityId { get; set; } - public int JustAProperty { get; set; } - } + modelBuilder.Entity( + b => + { + b.HasOne(e => (TCompositeFirstDependent)e.First) + .WithOne(e => (TCompositeRoot)e.Root) + .HasForeignKey("RootId1", "RootId2") + .IsRequired(false); + }); - protected class SomeMoreDependentEntity : SomeSimpleEntityBase - { - public int Fk1 { get; set; } - public string Fk2 { get; set; } + modelBuilder.Entity( + b => + { + b.HasOne(e => (TCompositeSecondDependent)e.Second) + .WithOne(e => (TCompositeFirstDependent)e.First) + .HasForeignKey("FirstId1", "FirstId2") + .IsRequired(false); + }); + } } - protected class FullNotificationEntity : INotifyPropertyChanging, INotifyPropertyChanged, ISomeEntity + public class KcrContext : KcContext { - private int _id; - private string _name; - - public int Id + protected internal override void OnModelCreating(ModelBuilder modelBuilder) { - get => _id; - set - { - if (_id != value) + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + b => { - NotifyChanging(); - _id = value; - NotifyChanged(); - } - } - } + b.Property("Id1"); + b.Property("Id2"); + b.HasKey("Id1", "Id2"); + b.HasOne(e => (TCompositeFirstDependent)e.First) + .WithOne(e => (TCompositeRoot)e.Root) + .HasForeignKey("RootId1", "RootId2") + .IsRequired(); + }); - public string Name - { - get => _name; - set - { - if (_name != value) + modelBuilder.Entity( + b => { - NotifyChanging(); - _name = value; - NotifyChanged(); - } - } + b.HasOne(e => (TCompositeSecondDependent)e.Second) + .WithOne(e => (TCompositeFirstDependent)e.First) + .HasForeignKey("FirstId1", "FirstId2") + .IsRequired(); + }); } - - public event PropertyChangingEventHandler PropertyChanging; - public event PropertyChangedEventHandler PropertyChanged; - - private void NotifyChanged([CallerMemberName] string propertyName = "") - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - - private void NotifyChanging([CallerMemberName] string propertyName = "") - => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); } - protected class ChangedOnlyEntity : INotifyPropertyChanged, ISomeEntity + public class KSnapContext : KContext { - private int _id; - private string _name; - - public int Id + protected internal override void OnModelCreating(ModelBuilder modelBuilder) { - get => _id; - set - { - if (_id != value) + modelBuilder.Entity( + b => { - _id = value; - NotifyChanged(); - } - } + b.Property("Id"); + b.HasKey("Id"); + b.Property("Name").IsConcurrencyToken(); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); + }); } + } + } - public string Name - { - get => _name; - set - { - if (_name != value) - { - _name = value; - NotifyChanged(); - } - } - } + public interface IRoot + { + IFirstDependent First { get; set; } + } - public event PropertyChangedEventHandler PropertyChanged; + public interface ICompositeSecondDependent + { + ICompositeFirstDependent First { get; set; } + } - private void NotifyChanged([CallerMemberName] string propertyName = "") - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + public interface IFirstDependent + { + IRoot Root { get; set; } + ISecondDependent Second { get; set; } + } - protected class OwnerClass - { - public int Id { get; set; } - public virtual OwnedClass Owned { get; set; } - } + public interface ISecondDependent + { + IFirstDependent First { get; set; } + } - protected class OwnedClass - { - public string Value { get; set; } - } + public interface ICompositeRoot + { + ICompositeFirstDependent First { get; set; } + } + + public interface ICompositeFirstDependent + { + ICompositeRoot Root { get; set; } + ICompositeSecondDependent Second { get; set; } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalMixedEntityEntryTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalMixedEntityEntryTest.cs index 64c027dedee..5b3c832b385 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalMixedEntityEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalMixedEntityEntryTest.cs @@ -1,199 +1,125 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit; - -// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { - public class InternalMixedEntityEntryTest : InternalEntityEntryTestBase + public class InternalMixedEntityEntryTest : InternalEntityEntryTestBase< + InternalMixedEntityEntryTest.SomeEntity, + InternalMixedEntityEntryTest.SomeSimpleEntityBase, + InternalMixedEntityEntryTest.SomeDependentEntity, + InternalMixedEntityEntryTest.SomeMoreDependentEntity, + InternalMixedEntityEntryTest.Root, + InternalMixedEntityEntryTest.FirstDependent, + InternalMixedEntityEntryTest.SecondDependent, + InternalMixedEntityEntryTest.CompositeRoot, + InternalMixedEntityEntryTest.CompositeFirstDependent, + InternalMixedEntityEntryTest.SomeCompositeEntityBase, + InternalMixedEntityEntryTest.CompositeSecondDependent, + InternalMixedEntityEntryTest.KMixedContext, + InternalMixedEntityEntryTest.KMixedSnapContext> { - [ConditionalFact] - public void Can_get_entity() + public class SomeCompositeEntityBase { - var model = BuildModel(); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); + } + + public class SomeDependentEntity : SomeCompositeEntityBase + { + } - var entity = new SomeEntity(); - var entry = CreateInternalEntry(configuration, model.FindEntityType(typeof(SomeEntity).FullName), entity); + public class SomeMoreDependentEntity : SomeSimpleEntityBase + { + } + + public class Root : IRoot + { + public FirstDependent First { get; set; } - Assert.Same(entity, entry.Entity); + IFirstDependent IRoot.First + { + get => First; + set => First = (FirstDependent)value; + } } - [ConditionalFact] - public void Can_set_and_get_property_value_from_CLR_object() + public class FirstDependent : IFirstDependent { - var model = BuildModel(finalize: false); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var keyProperty = entityType.AddProperty("Id_", typeof(int)); - var nonKeyProperty = entityType.FindProperty("Name"); - model.FinalizeModel(); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entity = new SomeEntity + public Root Root { get; set; } + + IRoot IFirstDependent.Root { - Id = 77, - Name = "Magic Tree House" - }; - var entry = CreateInternalEntry(configuration, entityType, entity); + get => Root; + set => Root = (Root)value; + } - Assert.Equal(0, entry[keyProperty]); // In shadow - Assert.Equal("Magic Tree House", entry[nonKeyProperty]); + public SecondDependent Second { get; set; } + + ISecondDependent IFirstDependent.Second + { + get => Second; + set => Second = (SecondDependent)value; + } + } - entry[keyProperty] = 78; - entry[nonKeyProperty] = "Normal Tree House"; + public class SecondDependent : ISecondDependent + { + public FirstDependent First { get; set; } - Assert.Equal(77, entity.Id); // In shadow - Assert.Equal("Normal Tree House", entity.Name); + IFirstDependent ISecondDependent.First + { + get => First; + set => First = (FirstDependent)value; + } + } + + public class CompositeRoot : ICompositeRoot + { + public ICompositeFirstDependent First { get; set; } + } + + public class CompositeFirstDependent : ICompositeFirstDependent + { + public CompositeRoot Root { get; set; } + + ICompositeRoot ICompositeFirstDependent.Root + { + get => Root; + set => Root = (CompositeRoot)value; + } + + public CompositeSecondDependent Second { get; set; } + + ICompositeSecondDependent ICompositeFirstDependent.Second + { + get => Second; + set => Second = (CompositeSecondDependent)value; + } + } + + public class CompositeSecondDependent : ICompositeSecondDependent + { + public CompositeFirstDependent First { get; set; } + + ICompositeFirstDependent ICompositeSecondDependent.First + { + get => First; + set => First = (CompositeFirstDependent)value; + } } - [ConditionalFact] - public void Asking_for_entity_instance_causes_it_to_be_materialized() + public class SomeSimpleEntityBase { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry( - configuration, - entityType, - new SomeEntity - { - Id = 1, - Name = "Kool" - }, - new ValueBuffer(new object[] { 1, "Kool" })); - - var entity = (SomeEntity)entry.Entity; - - Assert.Equal("Kool", entity.Name); } - [ConditionalFact] - public void All_original_values_can_be_accessed_for_entity_that_does_no_notification() + public class SomeEntity : SomeSimpleEntityBase { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeEntity).FullName); - - AllOriginalValuesTest( - model, entityType, new SomeEntity - { - Id = 1, - Name = "Kool" - }); } - [ConditionalFact] - public void All_original_values_can_be_accessed_for_entity_that_does_changed_only_notifications_if_eager_values_on() + public class KMixedContext : KContext { - var model = BuildModel(finalize: false); - var entityType = model.FindEntityType(typeof(ChangedOnlyEntity).FullName); - entityType.SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - model.FinalizeModel(); - - AllOriginalValuesTest( - model, entityType, new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }); } - [ConditionalFact] - public void Setting_CLR_property_with_snapshot_change_tracking_requires_DetectChanges() - => SetPropertyClrTest( - new SomeEntity - { - Id = 1, - Name = "Kool" - }, needsDetectChanges: true); - - [ConditionalFact] - public void Setting_CLR_property_with_changed_only_notifications_does_not_require_DetectChanges() - => SetPropertyClrTest( - new ChangedOnlyEntity - { - Id = 1, - Name = "Kool" - }, needsDetectChanges: false); - - [ConditionalFact] - public void Setting_CLR_property_with_full_notifications_does_not_require_DetectChanges() - => SetPropertyClrTest( - new FullNotificationEntity - { - Id = 1, - Name = "Kool" - }, needsDetectChanges: false); - - protected override IMutableModel BuildModel(bool finalize = true) + public class KMixedSnapContext : KContext { - var modelBuilder = new ModelBuilder(new ConventionSet()); - var model = modelBuilder.Model; - - var someSimpleEntityType = model.AddEntityType(typeof(SomeSimpleEntityBase)); - var simpleKeyProperty = someSimpleEntityType.AddProperty("Id", typeof(int)); - simpleKeyProperty.ValueGenerated = ValueGenerated.OnAdd; - someSimpleEntityType.SetPrimaryKey(simpleKeyProperty); - - var someCompositeEntityType = model.AddEntityType(typeof(SomeCompositeEntityBase)); - var compositeKeyProperty1 = someCompositeEntityType.AddProperty("Id1", typeof(int)); - var compositeKeyProperty2 = someCompositeEntityType.AddProperty("Id2", typeof(string)); - compositeKeyProperty2.IsNullable = false; - someCompositeEntityType.SetPrimaryKey(new[] { compositeKeyProperty1, compositeKeyProperty2 }); - - var entityType1 = model.AddEntityType(typeof(SomeEntity)); - entityType1.BaseType = someSimpleEntityType; - var property3 = entityType1.AddProperty("Name", typeof(string)); - property3.IsConcurrencyToken = false; - property3.ValueGenerated = ValueGenerated.OnAdd; - - var entityType2 = model.AddEntityType(typeof(SomeDependentEntity)); - entityType2.BaseType = someCompositeEntityType; - var fk = entityType2.AddProperty("SomeEntityId", typeof(int)); - entityType2.AddForeignKey(new[] { fk }, entityType1.FindPrimaryKey(), entityType1); - // TODO: declare this on the derived type - // #2611 - var justAProperty = someCompositeEntityType.AddProperty("JustAProperty", typeof(int)); - justAProperty.ValueGenerated = ValueGenerated.OnAdd; - someCompositeEntityType.AddKey(justAProperty); - - var entityType3 = model.AddEntityType(typeof(FullNotificationEntity)); - var property6 = entityType3.AddProperty("Id", typeof(int)); - entityType3.SetPrimaryKey(property6); - var property7 = entityType3.AddProperty("Name", typeof(string)); - property7.IsConcurrencyToken = true; - entityType3.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - - var entityType4 = model.AddEntityType(typeof(ChangedOnlyEntity)); - var property8 = entityType4.AddProperty("Id", typeof(int)); - entityType4.SetPrimaryKey(property8); - var property9 = entityType4.AddProperty("Name", typeof(string)); - property9.IsConcurrencyToken = true; - entityType4.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - - var entityType5 = model.AddEntityType(typeof(SomeMoreDependentEntity)); - entityType5.BaseType = someSimpleEntityType; - var fk5a = entityType5.AddProperty("Fk1", typeof(int)); - var fk5b = entityType5.AddProperty("Fk2", typeof(string)); - entityType5.AddForeignKey(new[] { fk5a, fk5b }, entityType2.FindPrimaryKey(), entityType2); - - modelBuilder.Entity( - eb => - { - eb.HasKey(e => e.Id); - var owned = eb.OwnsOne(e => e.Owned); - owned.WithOwner().HasForeignKey("Id"); - owned.HasKey("Id"); - owned.Property(e => e.Value); - }); - - return finalize ? (IMutableModel)model.FinalizeModel() : model; } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalShadowEntityEntryTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalShadowEntityEntryTest.cs deleted file mode 100644 index e06d706989e..00000000000 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalShadowEntityEntryTest.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - public class InternalShadowEntityEntryTest : InternalEntityEntryTestBase - { - protected override IMutableModel BuildModel(bool finalize = true) - { - var modelBuilder = new ModelBuilder(new ConventionSet()); - var model = modelBuilder.Model; - - var someSimpleEntityType = model.AddEntityType(typeof(SomeSimpleEntityBase).FullName); - var simpleKeyProperty = someSimpleEntityType.AddProperty("Id", typeof(int)); - simpleKeyProperty.ValueGenerated = ValueGenerated.OnAdd; - someSimpleEntityType.SetPrimaryKey(simpleKeyProperty); - - var someCompositeEntityType = model.AddEntityType(typeof(SomeCompositeEntityBase).FullName); - var compositeKeyProperty1 = someCompositeEntityType.AddProperty("Id1", typeof(int)); - var compositeKeyProperty2 = someCompositeEntityType.AddProperty("Id2", typeof(string)); - compositeKeyProperty2.IsNullable = false; - someCompositeEntityType.SetPrimaryKey(new[] { compositeKeyProperty1, compositeKeyProperty2 }); - - var entityType1 = model.AddEntityType(typeof(SomeEntity).FullName); - entityType1.BaseType = someSimpleEntityType; - var property3 = entityType1.AddProperty("Name", typeof(string)); - property3.IsConcurrencyToken = true; - property3.ValueGenerated = ValueGenerated.OnAdd; - - var entityType2 = model.AddEntityType(typeof(SomeDependentEntity).FullName); - entityType2.BaseType = someCompositeEntityType; - var fk = entityType2.AddProperty("SomeEntityId", typeof(int)); - entityType2.AddForeignKey(new[] { fk }, entityType1.FindPrimaryKey(), entityType1); - // TODO: declare this on the derived type - // #2611 - var justAProperty = someCompositeEntityType.AddProperty("JustAProperty", typeof(int)); - justAProperty.ValueGenerated = ValueGenerated.OnAdd; - someCompositeEntityType.AddKey(justAProperty); - - var entityType3 = model.AddEntityType(typeof(FullNotificationEntity)); - entityType3.SetPrimaryKey(entityType3.AddProperty("Id", typeof(int))); - var property6 = entityType3.AddProperty("Name", typeof(string)); - property6.IsConcurrencyToken = true; - entityType3.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - - var entityType4 = model.AddEntityType(typeof(ChangedOnlyEntity)); - entityType4.SetPrimaryKey(entityType4.AddProperty("Id", typeof(int))); - var property8 = entityType4.AddProperty("Name", typeof(string)); - property8.IsConcurrencyToken = true; - entityType4.SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - - var entityType5 = model.AddEntityType(typeof(SomeMoreDependentEntity).FullName); - entityType5.BaseType = someSimpleEntityType; - var fk5a = entityType5.AddProperty("Fk1", typeof(int)); - var fk5b = entityType5.AddProperty("Fk2", typeof(string)); - entityType5.AddForeignKey(new[] { fk5a, fk5b }, entityType2.FindPrimaryKey(), entityType2); - - modelBuilder.Entity( - typeof(OwnerClass).FullName, eb => - { - eb.Property(nameof(OwnerClass.Id)); - eb.HasKey(nameof(OwnerClass.Id)); - var owned = eb.OwnsOne(typeof(OwnedClass).FullName, nameof(OwnerClass.Owned)); - owned.WithOwner().HasForeignKey("Id"); - owned.HasKey("Id"); - owned.Property(nameof(OwnedClass.Value)); - }); - - return finalize ? (IMutableModel)model.FinalizeModel() : model; - } - } -} diff --git a/test/EFCore.Tests/Extensions/PropertyExtensionsTest.cs b/test/EFCore.Tests/Extensions/PropertyExtensionsTest.cs index d0d25e2761d..4694c9fb00b 100644 --- a/test/EFCore.Tests/Extensions/PropertyExtensionsTest.cs +++ b/test/EFCore.Tests/Extensions/PropertyExtensionsTest.cs @@ -16,6 +16,20 @@ namespace Microsoft.EntityFrameworkCore { public class PropertyExtensionsTest { + [ConditionalFact] + public virtual void Asking_for_type_mapping_before_finalize_throws() + { + var model = CreateModel(); + + var entityType = model.AddEntityType("Entity"); + var property = entityType.AddProperty("Property", typeof(int)); + + Assert.Equal( + CoreStrings.ModelNotFinalized(nameof(PropertyExtensions.GetTypeMapping)), + Assert.Throws( + () => property.GetTypeMapping()).Message); + } + [ConditionalFact] public virtual void Properties_can_have_store_type_set() { diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index cc8cdc4cafb..bcc4e0f575a 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; using Xunit; // ReSharper disable InconsistentNaming @@ -32,7 +33,6 @@ private class FakeProperty : IProperty, IClrPropertyGetter public Type ClrType { get; } public IEntityType DeclaringEntityType { get; } public bool IsNullable { get; } - public bool IsStoreGeneratedAlways { get; } public ValueGenerated ValueGenerated { get; } public bool IsConcurrencyToken { get; } public PropertyInfo PropertyInfo { get; } @@ -42,8 +42,9 @@ private class FakeProperty : IProperty, IClrPropertyGetter [ConditionalFact] public void Delegate_getter_is_returned_for_IProperty_property() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(Customer)); - var idProperty = entityType.AddProperty("Id", typeof(int)); + var modelBuilder = new ModelBuilder(InMemoryConventionSetBuilder.Build()); + var idProperty = modelBuilder.Entity().Property(e => e.Id).Metadata; + modelBuilder.FinalizeModel(); Assert.Equal( 7, new ClrPropertyGetterFactory().Create(idProperty).GetClrValue( @@ -67,8 +68,10 @@ public void Delegate_getter_is_returned_for_property_info() [ConditionalFact] public void Delegate_getter_is_returned_for_IProperty_struct_property() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(Customer)); - var fuelProperty = entityType.AddProperty("Fuel", typeof(Fuel)); + var modelBuilder = new ModelBuilder(InMemoryConventionSetBuilder.Build()); + modelBuilder.Entity().Property(e => e.Id); + var fuelProperty = modelBuilder.Entity().Property(e => e.Fuel).Metadata; + modelBuilder.FinalizeModel(); Assert.Equal( new Fuel(1.0), diff --git a/test/EFCore.Tests/Metadata/Internal/IndexedPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/IndexedPropertyGetterFactoryTest.cs index b7e8dc3a851..497bda05177 100644 --- a/test/EFCore.Tests/Metadata/Internal/IndexedPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/IndexedPropertyGetterFactoryTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; using Xunit; // ReSharper disable InconsistentNaming @@ -13,10 +14,12 @@ public class IndexedPropertyGetterFactoryTest [ConditionalFact] public void Delegate_getter_is_returned_for_indexed_property() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(IndexedClass)); - entityType.AddProperty("Id", typeof(int)); + var modelBuilder = new ModelBuilder(InMemoryConventionSetBuilder.Build()); + modelBuilder.Entity().Property(e => e.Id); + var entityType = modelBuilder.Model.FindEntityType(typeof(IndexedClass)); var propertyA = entityType.AddIndexedProperty("PropertyA", typeof(string)); var propertyB = entityType.AddIndexedProperty("PropertyB", typeof(int)); + modelBuilder.FinalizeModel(); var indexedClass = new IndexedClass(); Assert.Equal( @@ -49,6 +52,7 @@ private class IndexedClass public object this[string name] { get => _internalValues[name]; + set => _internalValues[name] = value; } } diff --git a/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs b/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs index c86c8fcbb35..1394763a4b1 100644 --- a/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs +++ b/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs @@ -79,25 +79,22 @@ public void Throws_for_unsupported_combinations() var selector = contextServices.GetRequiredService(); Assert.Equal( - CoreStrings.NoValueGenerator("Random", "AnEntity", typeof(Random).Name), + CoreStrings.NoValueGenerator("Random", "AnEntity", "char"), Assert.Throws(() => selector.Select(entityType.FindProperty("Random"), entityType)).Message); } - private static IMutableModel BuildModel(bool generateValues = true) + private static IModel BuildModel(bool generateValues = true) { var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); - builder.Ignore(); builder.Entity().Property(e => e.Custom).HasValueGenerator(); - var model = builder.Model; - var entityType = model.FindEntityType(typeof(AnEntity)); - entityType.AddProperty("Random", typeof(Random)); + var entityType = builder.Model.FindEntityType(typeof(AnEntity)); foreach (var property in entityType.GetProperties()) { property.ValueGenerated = generateValues ? ValueGenerated.OnAdd : ValueGenerated.Never; } - return model; + return builder.FinalizeModel(); } private class AnEntity @@ -133,7 +130,7 @@ private class AnEntity public DateTime? NullableDateTime { get; set; } public DateTimeOffset DateTimeOffset { get; set; } public DateTimeOffset? NullableDateTimeOffset { get; set; } - public Random Random { get; set; } + public char Random { get; set; } } private class CustomValueGenerator : ValueGenerator