From b6732af7dc4a0c21c907db9f5a0087d20d7e249c Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 3 Jun 2021 16:41:18 -0700 Subject: [PATCH] Don't remove default table mapping for TVF return entity types. Preserve null mappings in the model snapshot. Add non-generic ToSqlQuery overload. Output Fluent API for ToSqlQuery in the model snapshot. Fix SetFunctionName setting incorrect annotation. Fixes #23408 --- .../Design/CSharpSnapshotGenerator.cs | 56 +++++++++++---- .../Design/MigrationsCodeGenerator.cs | 1 + .../Design/AnnotationCodeGenerator.cs | 14 ++-- .../RelationalEntityTypeBuilderExtensions.cs | 17 ++++- .../RelationalEntityTypeExtensions.cs | 2 +- .../TableValuedDbFunctionConvention.cs | 5 -- .../Design/CSharpMigrationsGeneratorTest.cs | 64 ++++++++++++++--- .../Migrations/ModelSnapshotSqlServerTest.cs | 68 +++++++++++++++---- .../TableValuedDbFunctionConventionTest.cs | 4 +- .../Internal/MigrationsModelDifferTest.cs | 10 ++- 10 files changed, 185 insertions(+), 56 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 3985cc48312..631cf6d662b 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -825,28 +825,35 @@ protected virtual void GenerateEntityTypeAnnotations( || entityType.BaseType == null) { var tableName = (string?)tableNameAnnotation?.Value ?? entityType.GetTableName(); - if (tableName != null) + if (tableName != null + || tableNameAnnotation != null) { stringBuilder .AppendLine() .Append(builderName) .Append(".ToTable(") - .Append(Code.Literal(tableName)); + .Append(Code.UnknownLiteral(tableName)); if (tableNameAnnotation != null) { annotations.Remove(tableNameAnnotation.Name); } + var isExcludedAnnotation = annotations.Find(RelationalAnnotationNames.IsTableExcludedFromMigrations); var schemaAnnotation = annotations.Find(RelationalAnnotationNames.Schema); - if (schemaAnnotation?.Value != null) + if (schemaAnnotation != null) { stringBuilder - .Append(", ") - .Append(Code.Literal((string)schemaAnnotation.Value)); - annotations.Remove(schemaAnnotation.Name); + .Append(", "); + + if (schemaAnnotation.Value == null + && ((bool?)isExcludedAnnotation?.Value) != true) + { + stringBuilder.Append("(string)"); + } + + stringBuilder.Append(Code.UnknownLiteral(schemaAnnotation.Value)); } - var isExcludedAnnotation = annotations.Find(RelationalAnnotationNames.IsTableExcludedFromMigrations); if (isExcludedAnnotation != null) { if (((bool?)isExcludedAnnotation.Value) == true) @@ -870,19 +877,21 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(");"); } } + annotations.Remove(RelationalAnnotationNames.Schema); var viewNameAnnotation = annotations.Find(RelationalAnnotationNames.ViewName); if (viewNameAnnotation?.Value != null || entityType.BaseType == null) { var viewName = (string?)viewNameAnnotation?.Value ?? entityType.GetViewName(); - if (viewName != null) + if (viewName != null + || viewNameAnnotation != null) { stringBuilder .AppendLine() .Append(builderName) .Append(".ToView(") - .Append(Code.Literal(viewName)); + .Append(Code.UnknownLiteral(viewName)); if (viewNameAnnotation != null) { annotations.Remove(viewNameAnnotation.Name); @@ -900,25 +909,48 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(");"); } } + annotations.Remove(RelationalAnnotationNames.ViewSchema); + annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql); var functionNameAnnotation = annotations.Find(RelationalAnnotationNames.FunctionName); if (functionNameAnnotation?.Value != null || entityType.BaseType == null) { var functionName = (string?)functionNameAnnotation?.Value ?? entityType.GetFunctionName(); - if (functionName != null) + if (functionName != null + || functionNameAnnotation != null) { stringBuilder .AppendLine() .Append(builderName) .Append(".ToFunction(") - .Append(Code.Literal(functionName)); + .Append(Code.UnknownLiteral(functionName)) + .AppendLine(");"); if (functionNameAnnotation != null) { annotations.Remove(functionNameAnnotation.Name); } + } + } - stringBuilder.AppendLine(");"); + var sqlQueryAnnotation = annotations.Find(RelationalAnnotationNames.SqlQuery); + if (sqlQueryAnnotation?.Value != null + || entityType.BaseType == null) + { + var sqlQuery = (string?)sqlQueryAnnotation?.Value ?? entityType.GetSqlQuery(); + if (sqlQuery != null + || sqlQueryAnnotation != null) + { + stringBuilder + .AppendLine() + .Append(builderName) + .Append(".ToSqlQuery(") + .Append(Code.UnknownLiteral(sqlQuery)) + .AppendLine(");"); + if (sqlQueryAnnotation != null) + { + annotations.Remove(sqlQueryAnnotation.Name); + } } } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index 2798b7be04a..24985ba253d 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -215,6 +215,7 @@ private static IEnumerable GetAnnotatables(IModel model) private IEnumerable GetAnnotationNamespaces(IEnumerable items) => items.SelectMany( i => Dependencies.AnnotationCodeGenerator.FilterIgnoredAnnotations(i.GetAnnotations()) + .Where(a => a.Value != null) .Select(a => new { Annotatable = i, Annotation = a }) .SelectMany(a => GetProviderType(a.Annotatable, a.Annotation.Value!.GetType()).GetNamespaces())); diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index f066d494f96..d2b713f5172 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -54,9 +54,7 @@ public AnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) /// public virtual IEnumerable FilterIgnoredAnnotations(IEnumerable annotations) => annotations.Where( - a => !( - a.Value is null - || CoreAnnotationNames.AllNames.Contains(a.Name) + a => !(CoreAnnotationNames.AllNames.Contains(a.Name) || _ignoredRelationalAnnotations.Contains(a.Name))); /// @@ -676,12 +674,14 @@ private static void GenerateSimpleFluentApiCall( string methodName, List methodCallCodeFragments) { - if (annotations.TryGetValue(annotationName, out var annotation) - && annotation.Value is object annotationValue) + if (annotations.TryGetValue(annotationName, out var annotation)) { annotations.Remove(annotationName); - methodCallCodeFragments.Add( - new MethodCallCodeFragment(methodName, annotationValue)); + if (annotation.Value is object annotationValue) + { + methodCallCodeFragments.Add( + new MethodCallCodeFragment(methodName, annotationValue)); + } } } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index 4ac89b407cb..a6cd84696ad 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -691,10 +691,9 @@ public static bool CanSetViewSchema( /// The builder for the entity type being configured. /// The SQL query that will provide the underlying data for the entity type. /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToSqlQuery( - this EntityTypeBuilder entityTypeBuilder, + public static EntityTypeBuilder ToSqlQuery( + this EntityTypeBuilder entityTypeBuilder, string query) - where TEntity : class { Check.NotNull(query, nameof(query)); @@ -703,6 +702,18 @@ public static EntityTypeBuilder ToSqlQuery( return entityTypeBuilder; } + /// + /// Configures a SQL string used to provide data for the entity type. + /// + /// The builder for the entity type being configured. + /// The SQL query that will provide the underlying data for the entity type. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToSqlQuery( + this EntityTypeBuilder entityTypeBuilder, + string query) + where TEntity : class + => (EntityTypeBuilder)ToSqlQuery((EntityTypeBuilder)entityTypeBuilder, query); + /// /// Configures a SQL string used to provide data for the entity type. /// diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index ed4cc697437..d44cf895115 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -547,7 +547,7 @@ public static void SetFunctionName(this IMutableEntityType entityType, string? n string? name, bool fromDataAnnotation = false) => (string?)entityType.SetAnnotation( - RelationalAnnotationNames.ViewName, + RelationalAnnotationNames.FunctionName, Check.NullButNotEmpty(name, nameof(name)), fromDataAnnotation)?.Value; diff --git a/src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs index 003939a35ac..dcd421bf928 100644 --- a/src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs @@ -90,11 +90,6 @@ private void ProcessDbFunctionAdded( entityType = entityTypeBuilder.Metadata; } - - if (entityType.GetFunctionName() == null) - { - entityTypeBuilder.ToTable(null); - } } } } diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index a6cd6c30b83..69cadcc416a 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -70,19 +70,37 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.ViewColumnMappings, RelationalAnnotationNames.SqlQueryColumnMappings, RelationalAnnotationNames.FunctionColumnMappings, + RelationalAnnotationNames.DefaultColumnMappings, + RelationalAnnotationNames.TableMappings, + RelationalAnnotationNames.ViewMappings, + RelationalAnnotationNames.FunctionMappings, + RelationalAnnotationNames.SqlQueryMappings, + RelationalAnnotationNames.DefaultMappings, + RelationalAnnotationNames.ForeignKeyMappings, + RelationalAnnotationNames.TableIndexMappings, + RelationalAnnotationNames.UniqueConstraintMappings, RelationalAnnotationNames.RelationalOverrides, RelationalAnnotationNames.DefaultValueSql, RelationalAnnotationNames.ComputedColumnSql, RelationalAnnotationNames.DefaultValue, RelationalAnnotationNames.Name, +#pragma warning disable CS0618 // Type or member is obsolete + RelationalAnnotationNames.SequencePrefix, +#pragma warning restore CS0618 // Type or member is obsolete RelationalAnnotationNames.Sequences, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.DefaultSchema, RelationalAnnotationNames.Filter, +#pragma warning disable CS0618 // Type or member is obsolete + RelationalAnnotationNames.DbFunction, +#pragma warning restore CS0618 // Type or member is obsolete RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.MaxIdentifierLength, RelationalAnnotationNames.IsFixedLength, - RelationalAnnotationNames.Collation + RelationalAnnotationNames.Collation, + RelationalAnnotationNames.IsStored, + RelationalAnnotationNames.RelationalModel, + RelationalAnnotationNames.ModelDependencies }; // Add a line here if the code generator is supposed to handle this annotation @@ -140,18 +158,22 @@ public void Test_new_annotations_handled_for_entity_types() }, { RelationalAnnotationNames.FunctionName, - (null, "") + (null, _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null);" + _nl) }, { RelationalAnnotationNames.SqlQuery, - (null, "") + (null, _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null);" + _nl) + }, + { + CoreAnnotationNames.PropertyAccessMode, + (null, _toTable) } }; MissingAnnotationCheck( b => b.Entity().Metadata, notForEntityType, forEntityType, - _toTable, + a => _toTable, (g, m, b) => g.TestGenerateEntityTypeAnnotations("modelBuilder", (IEntityType)m, b)); } @@ -176,20 +198,41 @@ public void Test_new_annotations_handled_for_properties() CoreAnnotationNames.AmbiguousNavigations, CoreAnnotationNames.DuplicateServiceProperties, RelationalAnnotationNames.TableName, + RelationalAnnotationNames.IsTableExcludedFromMigrations, RelationalAnnotationNames.ViewName, RelationalAnnotationNames.Schema, RelationalAnnotationNames.ViewSchema, + RelationalAnnotationNames.ViewDefinitionSql, + RelationalAnnotationNames.FunctionName, + RelationalAnnotationNames.SqlQuery, RelationalAnnotationNames.DefaultSchema, RelationalAnnotationNames.DefaultMappings, + RelationalAnnotationNames.TableColumnMappings, + RelationalAnnotationNames.ViewColumnMappings, + RelationalAnnotationNames.SqlQueryColumnMappings, + RelationalAnnotationNames.FunctionColumnMappings, + RelationalAnnotationNames.DefaultColumnMappings, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.ViewMappings, + RelationalAnnotationNames.FunctionMappings, RelationalAnnotationNames.SqlQueryMappings, + RelationalAnnotationNames.ForeignKeyMappings, + RelationalAnnotationNames.TableIndexMappings, + RelationalAnnotationNames.UniqueConstraintMappings, RelationalAnnotationNames.Name, RelationalAnnotationNames.Sequences, +#pragma warning disable CS0618 // Type or member is obsolete + RelationalAnnotationNames.SequencePrefix, +#pragma warning restore CS0618 // Type or member is obsolete RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Filter, +#pragma warning disable CS0618 // Type or member is obsolete + RelationalAnnotationNames.DbFunction, +#pragma warning restore CS0618 // Type or member is obsolete RelationalAnnotationNames.DbFunctions, - RelationalAnnotationNames.MaxIdentifierLength + RelationalAnnotationNames.MaxIdentifierLength, + RelationalAnnotationNames.RelationalModel, + RelationalAnnotationNames.ModelDependencies }; var columnMapping = @@ -201,6 +244,7 @@ public void Test_new_annotations_handled_for_properties() { { CoreAnnotationNames.MaxLength, (256, $@"{_nl}.{nameof(PropertyBuilder.HasMaxLength)}(256){columnMapping}") }, { CoreAnnotationNames.Precision, (4, $@"{_nl}.{nameof(PropertyBuilder.HasPrecision)}(4){columnMapping}") }, + { CoreAnnotationNames.Scale, (null, $@"{columnMapping}") }, { CoreAnnotationNames.Unicode, (false, $@"{_nl}.{nameof(PropertyBuilder.IsUnicode)}(false){columnMapping}") }, { CoreAnnotationNames.ValueConverter, (new ValueConverter(v => v, v => (int)v), @@ -242,13 +286,17 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.Collation, ("Some Collation", $@"{columnMapping}{_nl}.{nameof(RelationalPropertyBuilderExtensions.UseCollation)}(""Some Collation"")") + }, + { + RelationalAnnotationNames.IsStored, + (null, $@"{columnMapping}{_nl}.HasAnnotation(""{RelationalAnnotationNames.IsStored}"", null)") } }; MissingAnnotationCheck( b => b.Entity().Property(e => e.Id).Metadata, notForProperty, forProperty, - $"{columnMapping}", + a => $"{columnMapping}", (g, m, b) => g.TestGeneratePropertyAnnotations((IProperty)m, b)); } @@ -256,7 +304,7 @@ private static void MissingAnnotationCheck( Func createMetadataItem, HashSet invalidAnnotations, Dictionary validAnnotations, - string generationDefault, + Func generationDefault, Action test) { var sqlServerTypeMappingSource = new SqlServerTypeMappingSource( @@ -314,7 +362,7 @@ private static void MissingAnnotationCheck( Assert.Equal( validAnnotations.ContainsKey(annotationName) ? validAnnotations[annotationName].Expected - : generationDefault, + : generationDefault(annotationName), sb.ToString()); } catch (Exception e) diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 2a99c04d80c..240da8a3cb6 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -8,11 +8,11 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Design; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -536,7 +536,7 @@ public void TVF_types_are_stored_in_the_model_snapshot() nameof(GetCountByYear), BindingFlags.NonPublic | BindingFlags.Static)); - builder.Entity().HasNoKey(); + builder.Entity().HasNoKey().ToTable(null); }, AddBoilerPlate( GetHeading() @@ -545,6 +545,8 @@ public void TVF_types_are_stored_in_the_model_snapshot() { b.Property(""Something"") .HasColumnType(""nvarchar(max)""); + + b.ToTable(null, (string)null); });"), o => Assert.Null(o.GetEntityTypes().Single().GetFunctionName())); } @@ -573,6 +575,27 @@ public void Entity_types_mapped_to_functions_are_stored_in_the_model_snapshot() o => Assert.Equal("GetCount", o.GetEntityTypes().Single().GetFunctionName())); } + [ConditionalFact] + public void Entity_types_mapped_to_queries_are_stored_in_the_model_snapshot() + { + Test( + builder => builder.Entity().Ignore(e => e.EntityWithTwoProperties).ToSqlQuery("query"), + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + b.HasKey(""Id""); + + b.ToSqlQuery(""query""); + });"), + o => Assert.Equal("query", o.GetEntityTypes().Single().GetSqlQuery())); + } + [ConditionalFact] public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() { @@ -1419,7 +1442,7 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i b.HasIndex(""RightsId""); - b.ToTable(""MyJoinTable""); + b.ToTable(""MyJoinTable"", (string)null); }); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft"", b => @@ -1605,7 +1628,7 @@ public virtual void Shared_columns_are_stored_in_the_snapshot() b.HasKey(""Id""); - b.ToTable(""EntityWithProperties""); + b.ToTable(""EntityWithProperties"", (string)null); }); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", b => @@ -1620,7 +1643,7 @@ public virtual void Shared_columns_are_stored_in_the_snapshot() b.HasKey(""Id""); - b.ToTable(""EntityWithProperties""); + b.ToTable(""EntityWithProperties"", (string)null); }); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", b => @@ -2130,7 +2153,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() b.HasKey(""Id"") .HasName(""PK_Custom""); - b.ToTable(""EntityWithOneProperty"", t => t.ExcludeFromMigrations()); + b.ToTable(""EntityWithOneProperty"", null, t => t.ExcludeFromMigrations()); b.HasData( new @@ -2146,7 +2169,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() b.HasKey(""Id""); - b.ToTable(""EntityWithStringKey"", t => t.ExcludeFromMigrations()); + b.ToTable(""EntityWithStringKey"", null, t => t.ExcludeFromMigrations()); }); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => @@ -2223,7 +2246,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() b1.HasIndex(""EntityWithStringKeyId""); - b1.ToTable(""EntityWithStringProperty"", excludedFromMigrations: true); + b1.ToTable(""EntityWithStringProperty"", null, excludedFromMigrations: true); b1.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", ""EntityWithOneProperty"") .WithOne() @@ -3369,7 +3392,7 @@ public virtual void Property_without_column_type() b.HasKey(""Id""); - b.ToTable(""Buildings""); + b.ToTable(""Buildings"", (string)null); });"), o => { @@ -3406,7 +3429,7 @@ public virtual void Property_with_identity_column() b.HasKey(""Id""); - b.ToTable(""Buildings""); + b.ToTable(""Buildings"", (string)null); });"), o => { @@ -3446,7 +3469,7 @@ public virtual void Property_with_identity_column_custom_seed() b.HasKey(""Id""); - b.ToTable(""Buildings""); + b.ToTable(""Buildings"", (string)null); });"), o => { @@ -3486,7 +3509,7 @@ public virtual void Property_with_identity_column_custom_increment() b.HasKey(""Id""); - b.ToTable(""Buildings""); + b.ToTable(""Buildings"", (string)null); });"), o => { @@ -3525,7 +3548,7 @@ public virtual void Property_with_identity_column_custom_seed_increment() b.HasKey(""Id""); - b.ToTable(""Buildings""); + b.ToTable(""Buildings"", (string)null); });"), o => { @@ -5456,6 +5479,16 @@ protected void Test(IModel model, string expectedCode, Action as var modelFromSnapshot = BuildModelFromSnapshotSource(code); assert(modelFromSnapshot, model); + + var targetOptionsBuilder = TestHelpers + .AddProviderOptions(new DbContextOptionsBuilder()) + .UseModel(model) + .EnableSensitiveDataLogging(); + + var modelDiffer = CreateModelDiffer(targetOptionsBuilder.Options); + + var noopOperations = modelDiffer.GetDifferences(modelFromSnapshot.GetRelationalModel(), model.GetRelationalModel()); + Assert.Empty(noopOperations); } protected IModel BuildModelFromSnapshotSource(string code) @@ -5484,16 +5517,21 @@ protected IModel BuildModelFromSnapshotSource(string code) Activator.CreateInstance(factoryType), new object[] { builder }); - var services = SqlServerTestHelpers.Instance.CreateContextServices(); + var services = TestHelpers.CreateContextServices(); var processor = new SnapshotModelProcessor(new TestOperationReporter(), services.GetService()); return processor.Process(builder.Model); } protected TestHelpers.TestModelBuilder CreateConventionalModelBuilder() - => SqlServerTestHelpers.Instance.CreateConventionBuilder(customServices: new ServiceCollection() + => TestHelpers.CreateConventionBuilder(customServices: new ServiceCollection() .AddEntityFrameworkSqlServerNetTopologySuite()); + protected virtual MigrationsModelDiffer CreateModelDiffer(DbContextOptions options) + => (MigrationsModelDiffer)TestHelpers.CreateContext(options).GetService(); + + protected TestHelpers TestHelpers => SqlServerTestHelpers.Instance; + protected CSharpMigrationsGenerator CreateMigrationsGenerator() { var sqlServerTypeMappingSource = new SqlServerTypeMappingSource( diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs index 3fd6e7cdf5e..0a06e927b80 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal public class TableValuedDbFunctionConventionTest { [ConditionalFact] - public void Configures_return_entity_as_not_mapped() + public void Does_not_configure_return_entity_as_not_mapped() { var modelBuilder = CreateModelBuilder(); modelBuilder.HasDbFunction( @@ -30,7 +30,7 @@ public void Configures_return_entity_as_not_mapped() var entityType = model.FindEntityType(typeof(KeylessEntity)); Assert.Null(entityType.FindPrimaryKey()); - Assert.Empty(entityType.GetViewOrTableMappings()); + Assert.Equal("KeylessEntity", entityType.GetViewOrTableMappings().Single().Table.Name); } [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index bf9cda895e4..604edd98d10 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -10557,15 +10557,19 @@ private static IQueryable GetCountByYear(int id) => throw new NotImplementedException(); [ConditionalFact] - public void Model_differ_does_not_detect_table_valued_function_result_type() + public void Model_differ_does_not_detect_entity_type_mapped_to_TVF() { Execute( _ => { }, modelBuilder => - modelBuilder.HasDbFunction( + { + var function = modelBuilder.HasDbFunction( typeof(MigrationsModelDifferTest).GetMethod( nameof(GetCountByYear), - BindingFlags.NonPublic | BindingFlags.Static)), + BindingFlags.NonPublic | BindingFlags.Static)).Metadata; + + modelBuilder.Entity().ToFunction(function.ModelName); + }, result => Assert.Equal(0, result.Count), skipSourceConventions: true); }